Skip to content

Commit 0b1b8e3

Browse files
committed
feat: commondbconnector unit test
1 parent 6c4eead commit 0b1b8e3

File tree

2 files changed

+419
-3
lines changed

2 files changed

+419
-3
lines changed
Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
package io.tapdata.common;
2+
3+
import io.tapdata.entity.codec.TapCodecsRegistry;
4+
import io.tapdata.entity.schema.TapConstraint;
5+
import io.tapdata.entity.schema.TapConstraintMapping;
6+
import io.tapdata.entity.schema.TapTable;
7+
import io.tapdata.kit.DbKit;
8+
import io.tapdata.pdk.apis.context.TapConnectionContext;
9+
import io.tapdata.pdk.apis.entity.ConnectionOptions;
10+
import io.tapdata.pdk.apis.entity.TestItem;
11+
import io.tapdata.pdk.apis.functions.ConnectorFunctions;
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.DisplayName;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.ExtendWith;
16+
import org.mockito.Mock;
17+
import org.mockito.MockedStatic;
18+
import org.mockito.junit.jupiter.MockitoExtension;
19+
20+
import java.lang.reflect.Method;
21+
import java.util.function.Consumer;
22+
23+
import static org.junit.jupiter.api.Assertions.*;
24+
import static org.mockito.ArgumentMatchers.*;
25+
import static org.mockito.Mockito.mockStatic;
26+
import static org.mockito.Mockito.when;
27+
28+
/**
29+
* getCreateConstraintSql方法的单元测试
30+
*
31+
* @author TapData
32+
*/
33+
@ExtendWith(MockitoExtension.class)
34+
@DisplayName("CommonDbConnector.getCreateConstraintSql()方法测试")
35+
class CommonDbConnectorGetCreateConstraintSqlTest {
36+
37+
@Mock
38+
private CommonDbConfig commonDbConfig;
39+
40+
private TestableCommonDbConnector connector;
41+
private Method getCreateConstraintSqlMethod;
42+
43+
@BeforeEach
44+
void setUp() throws Exception {
45+
connector = new TestableCommonDbConnector() {
46+
@Override
47+
public ConnectionOptions connectionTest(TapConnectionContext connectionContext, Consumer<TestItem> consumer) throws Throwable {
48+
return null;
49+
}
50+
51+
@Override
52+
public void registerCapabilities(ConnectorFunctions connectorFunctions, TapCodecsRegistry codecRegistry) {
53+
54+
}
55+
56+
@Override
57+
public void onStart(TapConnectionContext connectionContext) throws Throwable {
58+
59+
}
60+
61+
@Override
62+
public void onStop(TapConnectionContext connectionContext) throws Throwable {
63+
64+
}
65+
};
66+
connector.commonDbConfig = commonDbConfig;
67+
68+
// 获取私有方法
69+
getCreateConstraintSqlMethod = CommonDbConnector.class.getDeclaredMethod(
70+
"getCreateConstraintSql", TapTable.class, TapConstraint.class);
71+
getCreateConstraintSqlMethod.setAccessible(true);
72+
}
73+
74+
@Test
75+
@DisplayName("测试基本外键约束SQL生成")
76+
void testBasicForeignKeyConstraint() throws Exception {
77+
// 准备测试数据
78+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
79+
80+
TapTable tapTable = createTestTable("users");
81+
TapConstraint constraint = createBasicConstraint();
82+
83+
// 执行测试
84+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
85+
86+
// 验证结果
87+
String expected = "alter table `test_schema`.`users` add constraint `fk_user_dept` " +
88+
"foreign key (`dept_id`) references `test_schema`.`departments`(`id`)";
89+
assertEquals(expected, result);
90+
}
91+
92+
@Test
93+
@DisplayName("测试多列外键约束SQL生成")
94+
void testMultiColumnForeignKeyConstraint() throws Exception {
95+
// 准备测试数据
96+
when(commonDbConfig.getEscapeChar()).thenReturn('"');
97+
98+
TapTable tapTable = createTestTable("order_items");
99+
TapConstraint constraint = createMultiColumnConstraint();
100+
101+
// 执行测试
102+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
103+
104+
// 验证结果
105+
String expected = "alter table \"test_schema\".\"order_items\" add constraint \"fk_order_product\" " +
106+
"foreign key (\"order_id\",\"product_id\") references \"test_schema\".\"order_products\"(\"order_id\",\"product_id\")";
107+
assertEquals(expected, result);
108+
}
109+
110+
@Test
111+
@DisplayName("测试带ON UPDATE和ON DELETE的约束SQL生成")
112+
void testConstraintWithCascadeOptions() throws Exception {
113+
// 准备测试数据
114+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
115+
116+
TapTable tapTable = createTestTable("employees");
117+
TapConstraint constraint = createConstraintWithCascadeOptions();
118+
119+
// 执行测试
120+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
121+
122+
// 验证结果
123+
String expected = "alter table `test_schema`.`employees` add constraint `fk_emp_manager` " +
124+
"foreign key (`manager_id`) references `test_schema`.`employees`(`id`) " +
125+
"on update cascade on delete set null";
126+
assertEquals(expected, result.toLowerCase());
127+
}
128+
129+
@Test
130+
@DisplayName("测试约束名为空时自动生成约束名")
131+
void testAutoGenerateConstraintName() throws Exception {
132+
// 准备测试数据
133+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
134+
135+
TapTable tapTable = createTestTable("users");
136+
TapConstraint constraint = createConstraintWithoutName();
137+
138+
try (MockedStatic<DbKit> dbKitMock = mockStatic(DbKit.class)) {
139+
dbKitMock.when(() -> DbKit.buildForeignKeyName(eq("users"), eq(constraint), eq(32)))
140+
.thenReturn("FK_users_dept_id_12345678");
141+
142+
// 执行测试
143+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
144+
145+
// 验证结果
146+
assertTrue(result.contains("add constraint `FK_users_dept_id_12345678`"));
147+
assertTrue(result.contains("foreign key (`dept_id`)"));
148+
assertTrue(result.contains("references `test_schema`.`departments`(`id`)"));
149+
}
150+
}
151+
152+
@Test
153+
@DisplayName("测试ON UPDATE和ON DELETE选项的下划线替换")
154+
void testCascadeOptionsUnderscoreReplacement() throws Exception {
155+
// 准备测试数据
156+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
157+
158+
TapTable tapTable = createTestTable("users");
159+
TapConstraint constraint = new TapConstraint("fk_test", TapConstraint.ConstraintType.FOREIGN_KEY);
160+
constraint.referencesTable("departments");
161+
constraint.add(new TapConstraintMapping().foreignKey("dept_id").referenceKey("id"));
162+
constraint.onUpdate("SET_NULL"); // 包含下划线
163+
constraint.onDelete("NO_ACTION"); // 包含下划线
164+
165+
// 执行测试
166+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
167+
168+
// 验证结果 - 下划线应该被替换为空格
169+
assertTrue(result.contains("on update SET NULL"));
170+
assertTrue(result.contains("on delete NO ACTION"));
171+
}
172+
173+
// ==================== 辅助方法 ====================
174+
175+
/**
176+
* 创建测试表
177+
*/
178+
private TapTable createTestTable(String tableName) {
179+
return new TapTable(tableName);
180+
}
181+
182+
/**
183+
* 创建基本的外键约束
184+
*/
185+
private TapConstraint createBasicConstraint() {
186+
TapConstraint constraint = new TapConstraint("fk_user_dept", TapConstraint.ConstraintType.FOREIGN_KEY);
187+
constraint.referencesTable("departments");
188+
constraint.add(new TapConstraintMapping()
189+
.foreignKey("dept_id")
190+
.referenceKey("id"));
191+
return constraint;
192+
}
193+
194+
/**
195+
* 创建多列外键约束
196+
*/
197+
private TapConstraint createMultiColumnConstraint() {
198+
TapConstraint constraint = new TapConstraint("fk_order_product", TapConstraint.ConstraintType.FOREIGN_KEY);
199+
constraint.referencesTable("order_products");
200+
constraint.add(new TapConstraintMapping()
201+
.foreignKey("order_id")
202+
.referenceKey("order_id"));
203+
constraint.add(new TapConstraintMapping()
204+
.foreignKey("product_id")
205+
.referenceKey("product_id"));
206+
return constraint;
207+
}
208+
209+
/**
210+
* 创建带级联选项的约束
211+
*/
212+
private TapConstraint createConstraintWithCascadeOptions() {
213+
TapConstraint constraint = new TapConstraint("fk_emp_manager", TapConstraint.ConstraintType.FOREIGN_KEY);
214+
constraint.referencesTable("employees");
215+
constraint.add(new TapConstraintMapping()
216+
.foreignKey("manager_id")
217+
.referenceKey("id"));
218+
constraint.onUpdate("CASCADE");
219+
constraint.onDelete("SET_NULL");
220+
return constraint;
221+
}
222+
223+
/**
224+
* 创建没有约束名的约束
225+
*/
226+
private TapConstraint createConstraintWithoutName() {
227+
TapConstraint constraint = new TapConstraint(null, TapConstraint.ConstraintType.FOREIGN_KEY);
228+
constraint.referencesTable("departments");
229+
constraint.add(new TapConstraintMapping()
230+
.foreignKey("dept_id")
231+
.referenceKey("id"));
232+
return constraint;
233+
}
234+
235+
@Test
236+
@DisplayName("测试特殊字符在表名和字段名中的处理")
237+
void testSpecialCharactersInNames() throws Exception {
238+
// 准备测试数据
239+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
240+
241+
TapTable tapTable = createTestTable("user-table");
242+
TapConstraint constraint = new TapConstraint("fk-test", TapConstraint.ConstraintType.FOREIGN_KEY);
243+
constraint.referencesTable("dept-table");
244+
constraint.add(new TapConstraintMapping()
245+
.foreignKey("dept-id")
246+
.referenceKey("id-field"));
247+
248+
// 执行测试
249+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
250+
251+
// 验证结果
252+
assertTrue(result.contains("`user-table`"));
253+
assertTrue(result.contains("`fk-test`"));
254+
assertTrue(result.contains("`dept-id`"));
255+
assertTrue(result.contains("`dept-table`"));
256+
assertTrue(result.contains("`id-field`"));
257+
}
258+
259+
@Test
260+
@DisplayName("测试NULL值的处理")
261+
void testNullValueHandling() throws Exception {
262+
// 准备测试数据
263+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
264+
265+
TapTable tapTable = createTestTable("users");
266+
TapConstraint constraint = new TapConstraint("fk_test", TapConstraint.ConstraintType.FOREIGN_KEY);
267+
constraint.referencesTable("departments");
268+
constraint.add(new TapConstraintMapping()
269+
.foreignKey("dept_id")
270+
.referenceKey("id"));
271+
// onUpdate和onDelete保持为null
272+
273+
// 执行测试
274+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
275+
276+
// 验证结果 - 不应该包含ON UPDATE或ON DELETE子句
277+
assertFalse(result.contains("on update"));
278+
assertFalse(result.contains("on delete"));
279+
assertTrue(result.endsWith("references `test_schema`.`departments`(`id`)"));
280+
}
281+
282+
@Test
283+
@DisplayName("测试约束名长度限制场景")
284+
void testConstraintNameLengthLimit() throws Exception {
285+
// 准备测试数据
286+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
287+
288+
TapTable tapTable = createTestTable("very_long_table_name_that_exceeds_normal_limits");
289+
TapConstraint constraint = createConstraintWithoutName();
290+
291+
try (MockedStatic<DbKit> dbKitMock = mockStatic(DbKit.class)) {
292+
// 模拟DbKit返回截断后的约束名
293+
dbKitMock.when(() -> DbKit.buildForeignKeyName(anyString(), any(TapConstraint.class), eq(32)))
294+
.thenReturn("FK_very_long_table_name_12345");
295+
296+
// 执行测试
297+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
298+
299+
// 验证结果
300+
assertTrue(result.contains("add constraint `FK_very_long_table_name_12345`"));
301+
}
302+
}
303+
304+
@Test
305+
@DisplayName("测试单个字符的约束名")
306+
void testSingleCharacterConstraintName() throws Exception {
307+
// 准备测试数据
308+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
309+
310+
TapTable tapTable = createTestTable("t");
311+
TapConstraint constraint = new TapConstraint("f", TapConstraint.ConstraintType.FOREIGN_KEY);
312+
constraint.referencesTable("d");
313+
constraint.add(new TapConstraintMapping()
314+
.foreignKey("i")
315+
.referenceKey("j"));
316+
317+
// 执行测试
318+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
319+
320+
// 验证结果
321+
String expected = "alter table `test_schema`.`t` add constraint `f` " +
322+
"foreign key (`i`) references `test_schema`.`d`(`j`)";
323+
assertEquals(expected, result);
324+
}
325+
326+
@Test
327+
@DisplayName("测试空字符串约束名的处理")
328+
void testEmptyStringConstraintName() throws Exception {
329+
// 准备测试数据
330+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
331+
332+
TapTable tapTable = createTestTable("users");
333+
TapConstraint constraint = new TapConstraint("", TapConstraint.ConstraintType.FOREIGN_KEY);
334+
constraint.referencesTable("departments");
335+
constraint.add(new TapConstraintMapping()
336+
.foreignKey("dept_id")
337+
.referenceKey("id"));
338+
339+
try (MockedStatic<DbKit> dbKitMock = mockStatic(DbKit.class)) {
340+
dbKitMock.when(() -> DbKit.buildForeignKeyName(eq("users"), eq(constraint), eq(32)))
341+
.thenReturn("FK_users_dept_id_auto");
342+
343+
// 执行测试
344+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
345+
346+
// 验证结果 - 应该使用自动生成的约束名
347+
assertTrue(result.contains("add constraint `FK_users_dept_id_auto`"));
348+
}
349+
}
350+
351+
@Test
352+
@DisplayName("测试所有级联选项的组合")
353+
void testAllCascadeOptionsCombinations() throws Exception {
354+
// 准备测试数据
355+
when(commonDbConfig.getEscapeChar()).thenReturn('`');
356+
357+
TapTable tapTable = createTestTable("users");
358+
359+
// 测试所有可能的级联选项组合
360+
String[][] cascadeOptions = {
361+
{"CASCADE", "CASCADE"},
362+
{"SET_NULL", "RESTRICT"},
363+
{"NO_ACTION", "SET_DEFAULT"},
364+
{"RESTRICT", "NO_ACTION"}
365+
};
366+
367+
for (String[] options : cascadeOptions) {
368+
TapConstraint constraint = new TapConstraint("fk_test", TapConstraint.ConstraintType.FOREIGN_KEY);
369+
constraint.referencesTable("departments");
370+
constraint.add(new TapConstraintMapping()
371+
.foreignKey("dept_id")
372+
.referenceKey("id"));
373+
constraint.onUpdate(options[0]);
374+
constraint.onDelete(options[1]);
375+
376+
// 执行测试
377+
String result = (String) getCreateConstraintSqlMethod.invoke(connector, tapTable, constraint);
378+
379+
// 验证结果
380+
assertTrue(result.contains("on update " + options[0].replace("_", " ")));
381+
assertTrue(result.contains("on delete " + options[1].replace("_", " ")));
382+
}
383+
}
384+
385+
/**
386+
* 可测试的CommonDbConnector子类
387+
*/
388+
private static abstract class TestableCommonDbConnector extends CommonDbConnector {
389+
@Override
390+
protected String getSchemaAndTable(String tableName) {
391+
return commonDbConfig.getEscapeChar() + "test_schema" + commonDbConfig.getEscapeChar() + "." + commonDbConfig.getEscapeChar() + tableName + commonDbConfig.getEscapeChar();
392+
}
393+
}
394+
}

0 commit comments

Comments
 (0)