migration-test.vala 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645
  1. using Invercargill.DataStructures;
  2. using InvercargillSql;
  3. using InvercargillSql.Migrations;
  4. using InvercargillSql.Dialects;
  5. using InvercargillSql.Orm;
  6. // ============================================================================
  7. // TEST MIGRATION CLASSES
  8. // ============================================================================
  9. // --- Auth namespace migrations ---
  10. /**
  11. * Auth V1: Create users table - base migration with no dependencies
  12. */
  13. public class Test_Auth_V1 : Migration {
  14. public override string migration_namespace { get { return "auth"; } }
  15. public override uint64 serial { get { return 1; } }
  16. public override string name { get { return "CreateUsers"; } }
  17. public override void up(MigrationBuilder b) throws SqlError {
  18. b.create_table("users", t => {
  19. t.column<int64?>("id").primary_key().auto_increment();
  20. t.column<string>("email").not_null().unique();
  21. t.column<string>("password_hash").not_null();
  22. });
  23. }
  24. public override void down(MigrationBuilder b) throws SqlError {
  25. b.drop_table("users");
  26. }
  27. }
  28. /**
  29. * Auth V2: Add roles table - depends on auth:1 (within-namespace)
  30. */
  31. public class Test_Auth_V2 : Migration {
  32. public override string migration_namespace { get { return "auth"; } }
  33. public override uint64 serial { get { return 2; } }
  34. public override string name { get { return "AddRoles"; } }
  35. public override string[] dependencies { owned get { return {"auth:1"}; } }
  36. public override void up(MigrationBuilder b) throws SqlError {
  37. b.create_table("roles", t => {
  38. t.column<int64?>("id").primary_key().auto_increment();
  39. t.column<string>("name").not_null().unique();
  40. });
  41. b.alter_table("users", t => {
  42. t.add_column<int64?>("role_id").references("roles", "id");
  43. });
  44. }
  45. public override void down(MigrationBuilder b) throws SqlError {
  46. b.alter_table("users", t => {
  47. t.drop_column("role_id");
  48. });
  49. b.drop_table("roles");
  50. }
  51. }
  52. /**
  53. * Auth V3: Add sessions table - depends on auth:2
  54. */
  55. public class Test_Auth_V3 : Migration {
  56. public override string migration_namespace { get { return "auth"; } }
  57. public override uint64 serial { get { return 3; } }
  58. public override string name { get { return "AddSessions"; } }
  59. public override string[] dependencies { owned get { return {"auth:2"}; } }
  60. public override void up(MigrationBuilder b) throws SqlError {
  61. b.create_table("sessions", t => {
  62. t.column<int64?>("id").primary_key().auto_increment();
  63. t.column<int64?>("user_id").not_null().references("users", "id");
  64. t.column<string>("token").not_null();
  65. });
  66. }
  67. public override void down(MigrationBuilder b) throws SqlError {
  68. b.drop_table("sessions");
  69. }
  70. }
  71. // --- App namespace migrations ---
  72. /**
  73. * App V1: Create orders table - depends on "auth" namespace (any migration)
  74. */
  75. public class Test_App_V1 : Migration {
  76. public override string migration_namespace { get { return "app"; } }
  77. public override uint64 serial { get { return 1; } }
  78. public override string name { get { return "CreateOrders"; } }
  79. public override string[] dependencies { owned get { return {"auth"}; } }
  80. public override void up(MigrationBuilder b) throws SqlError {
  81. b.create_table("orders", t => {
  82. t.column<int64?>("id").primary_key().auto_increment();
  83. t.column<int64?>("user_id").not_null().references("users", "id");
  84. t.column<double?>("total").not_null();
  85. t.column<string>("status").not_null();
  86. t.index("idx_orders_user_id").on_column("user_id");
  87. });
  88. }
  89. public override void down(MigrationBuilder b) throws SqlError {
  90. b.drop_table("orders");
  91. }
  92. }
  93. /**
  94. * App V2: Create order items - depends on auth:1 and app:1
  95. */
  96. public class Test_App_V2 : Migration {
  97. public override string migration_namespace { get { return "app"; } }
  98. public override uint64 serial { get { return 2; } }
  99. public override string name { get { return "CreateOrderItems"; } }
  100. public override string[] dependencies { owned get { return {"auth:1", "app:1"}; } }
  101. public override void up(MigrationBuilder b) throws SqlError {
  102. b.create_table("order_items", t => {
  103. t.column<int64?>("id").primary_key().auto_increment();
  104. t.column<int64?>("order_id").not_null().references("orders", "id");
  105. t.column<string>("product_name").not_null();
  106. t.column<int>("quantity").not_null();
  107. t.column<double?>("price").not_null();
  108. });
  109. }
  110. public override void down(MigrationBuilder b) throws SqlError {
  111. b.drop_table("order_items");
  112. }
  113. }
  114. // --- Logging namespace migrations ---
  115. /**
  116. * Logging V1: Create audit log - no dependencies
  117. */
  118. public class Test_Logging_V1 : Migration {
  119. public override string migration_namespace { get { return "logging"; } }
  120. public override uint64 serial { get { return 1; } }
  121. public override string name { get { return "CreateAuditLog"; } }
  122. // No dependencies
  123. public override void up(MigrationBuilder b) throws SqlError {
  124. b.create_table("audit_log", t => {
  125. t.column<int64?>("id").primary_key().auto_increment();
  126. t.column<string>("action").not_null();
  127. t.column<int64?>("user_id");
  128. t.column<string>("details");
  129. });
  130. }
  131. public override void down(MigrationBuilder b) throws SqlError {
  132. b.drop_table("audit_log");
  133. }
  134. }
  135. /**
  136. * Logging V2: Add timestamps - depends on logging:1
  137. */
  138. public class Test_Logging_V2 : Migration {
  139. public override string migration_namespace { get { return "logging"; } }
  140. public override uint64 serial { get { return 2; } }
  141. public override string name { get { return "AddTimestamps"; } }
  142. public override string[] dependencies { owned get { return {"logging:1"}; } }
  143. public override void up(MigrationBuilder b) throws SqlError {
  144. b.alter_table("audit_log", t => {
  145. t.add_column<string>("created_at").not_null();
  146. });
  147. }
  148. public override void down(MigrationBuilder b) throws SqlError {
  149. b.alter_table("audit_log", t => {
  150. t.drop_column("created_at");
  151. });
  152. }
  153. }
  154. // --- Circular dependency test migrations ---
  155. /**
  156. * Circular A: depends on Circular B
  157. */
  158. public class Test_Circular_A : Migration {
  159. public override string migration_namespace { get { return "circular"; } }
  160. public override uint64 serial { get { return 1; } }
  161. public override string name { get { return "CircularA"; } }
  162. public override string[] dependencies { owned get { return {"circular:2"}; } }
  163. public override void up(MigrationBuilder b) throws SqlError {
  164. b.create_table("circular_a", t => {
  165. t.column<int64?>("id").primary_key().auto_increment();
  166. });
  167. }
  168. public override void down(MigrationBuilder b) throws SqlError {
  169. b.drop_table("circular_a");
  170. }
  171. }
  172. /**
  173. * Circular B: depends on Circular A - creates a cycle!
  174. */
  175. public class Test_Circular_B : Migration {
  176. public override string migration_namespace { get { return "circular"; } }
  177. public override uint64 serial { get { return 2; } }
  178. public override string name { get { return "CircularB"; } }
  179. public override string[] dependencies { owned get { return {"circular:1"}; } }
  180. public override void up(MigrationBuilder b) throws SqlError {
  181. b.create_table("circular_b", t => {
  182. t.column<int64?>("id").primary_key().auto_increment();
  183. });
  184. }
  185. public override void down(MigrationBuilder b) throws SqlError {
  186. b.drop_table("circular_b");
  187. }
  188. }
  189. /**
  190. * Three-way cycle: A -> B -> C -> A
  191. */
  192. public class Test_Cycle_A : Migration {
  193. public override string migration_namespace { get { return "cycle"; } }
  194. public override uint64 serial { get { return 1; } }
  195. public override string name { get { return "CycleA"; } }
  196. public override string[] dependencies { owned get { return {"cycle:3"}; } } // depends on C
  197. public override void up(MigrationBuilder b) throws SqlError {
  198. b.create_table("cycle_a", t => {
  199. t.column<int64?>("id").primary_key().auto_increment();
  200. });
  201. }
  202. public override void down(MigrationBuilder b) throws SqlError {
  203. b.drop_table("cycle_a");
  204. }
  205. }
  206. /**
  207. * Three-way cycle: B
  208. */
  209. public class Test_Cycle_B : Migration {
  210. public override string migration_namespace { get { return "cycle"; } }
  211. public override uint64 serial { get { return 2; } }
  212. public override string name { get { return "CycleB"; } }
  213. public override string[] dependencies { owned get { return {"cycle:1"}; } } // depends on A
  214. public override void up(MigrationBuilder b) throws SqlError {
  215. b.create_table("cycle_b", t => {
  216. t.column<int64?>("id").primary_key().auto_increment();
  217. });
  218. }
  219. public override void down(MigrationBuilder b) throws SqlError {
  220. b.drop_table("cycle_b");
  221. }
  222. }
  223. /**
  224. * Three-way cycle: C
  225. */
  226. public class Test_Cycle_C : Migration {
  227. public override string migration_namespace { get { return "cycle"; } }
  228. public override uint64 serial { get { return 3; } }
  229. public override string name { get { return "CycleC"; } }
  230. public override string[] dependencies { owned get { return {"cycle:2"}; } } // depends on B
  231. public override void up(MigrationBuilder b) throws SqlError {
  232. b.create_table("cycle_c", t => {
  233. t.column<int64?>("id").primary_key().auto_increment();
  234. });
  235. }
  236. public override void down(MigrationBuilder b) throws SqlError {
  237. b.drop_table("cycle_c");
  238. }
  239. }
  240. /**
  241. * Self-dependency: depends on itself
  242. */
  243. public class Test_Self_Dependency : Migration {
  244. public override string migration_namespace { get { return "selfdep"; } }
  245. public override uint64 serial { get { return 1; } }
  246. public override string name { get { return "SelfDependency"; } }
  247. public override string[] dependencies { owned get { return {"selfdep:1"}; } } // depends on itself!
  248. public override void up(MigrationBuilder b) throws SqlError {
  249. b.create_table("self_dep", t => {
  250. t.column<int64?>("id").primary_key().auto_increment();
  251. });
  252. }
  253. public override void down(MigrationBuilder b) throws SqlError {
  254. b.drop_table("self_dep");
  255. }
  256. }
  257. // --- Missing dependency test migrations ---
  258. /**
  259. * Missing namespace dependency
  260. */
  261. public class Test_Missing_Namespace : Migration {
  262. public override string migration_namespace { get { return "missing"; } }
  263. public override uint64 serial { get { return 1; } }
  264. public override string name { get { return "MissingNamespace"; } }
  265. public override string[] dependencies { owned get { return {"nonexistent"}; } } // namespace doesn't exist
  266. public override void up(MigrationBuilder b) throws SqlError {
  267. b.create_table("missing_test", t => {
  268. t.column<int64?>("id").primary_key().auto_increment();
  269. });
  270. }
  271. public override void down(MigrationBuilder b) throws SqlError {
  272. b.drop_table("missing_test");
  273. }
  274. }
  275. /**
  276. * Missing serial dependency
  277. */
  278. public class Test_Missing_Serial : Migration {
  279. public override string migration_namespace { get { return "missing_serial"; } }
  280. public override uint64 serial { get { return 1; } }
  281. public override string name { get { return "MissingSerial"; } }
  282. public override string[] dependencies { owned get { return {"auth:999"}; } } // serial 999 doesn't exist
  283. public override void up(MigrationBuilder b) throws SqlError {
  284. b.create_table("missing_serial_test", t => {
  285. t.column<int64?>("id").primary_key().auto_increment();
  286. });
  287. }
  288. public override void down(MigrationBuilder b) throws SqlError {
  289. b.drop_table("missing_serial_test");
  290. }
  291. }
  292. // ============================================================================
  293. // TEST MAIN
  294. // ============================================================================
  295. public int main(string[] args) {
  296. Test.init(ref args);
  297. // ========================================
  298. // 1. Dependency Parsing Tests
  299. // ========================================
  300. Test.add_func("/migrations/dependency/parse_namespace", test_parse_namespace);
  301. Test.add_func("/migrations/dependency/parse_namespace_serial", test_parse_namespace_serial);
  302. Test.add_func("/migrations/dependency/parse_serial_zero", test_parse_serial_zero);
  303. Test.add_func("/migrations/dependency/parse_empty_namespace", test_parse_empty_namespace);
  304. Test.add_func("/migrations/dependency/parse_empty_serial", test_parse_empty_serial);
  305. Test.add_func("/migrations/dependency/parse_invalid_serial", test_parse_invalid_serial);
  306. Test.add_func("/migrations/dependency/parse_multiple_colons", test_parse_multiple_colons);
  307. Test.add_func("/migrations/dependency/to_string", test_dependency_to_string);
  308. Test.add_func("/migrations/dependency/equality", test_dependency_equality);
  309. // ========================================
  310. // 2. Single Namespace Migration Tests
  311. // ========================================
  312. Test.add_func("/migrations/single/register", test_single_namespace_register);
  313. Test.add_func("/migrations/single/migrate_to_latest", test_single_namespace_migrate_to_latest);
  314. Test.add_func("/migrations/single/get_applied", test_single_namespace_get_applied);
  315. Test.add_func("/migrations/single/get_pending", test_single_namespace_get_pending);
  316. Test.add_func("/migrations/single/rollback", test_single_namespace_rollback);
  317. Test.add_func("/migrations/single/get_current_serial", test_single_namespace_get_current_serial);
  318. // ========================================
  319. // 3. Multi-Namespace Migration Tests
  320. // ========================================
  321. Test.add_func("/migrations/multi/register_multiple_namespaces", test_multi_register_namespaces);
  322. Test.add_func("/migrations/multi/migrate_to_latest_all", test_multi_migrate_to_latest_all);
  323. Test.add_func("/migrations/multi/migrate_specific_namespace", test_multi_migrate_specific_namespace);
  324. Test.add_func("/migrations/multi/filter_applied_by_namespace", test_multi_filter_applied_by_namespace);
  325. Test.add_func("/migrations/multi/filter_pending_by_namespace", test_multi_filter_pending_by_namespace);
  326. // ========================================
  327. // 4. Cross-Namespace Dependency Tests
  328. // ========================================
  329. Test.add_func("/migrations/cross_namespace/namespace_dependency", test_cross_namespace_dependency);
  330. Test.add_func("/migrations/cross_namespace/specific_serial_dependency", test_cross_namespace_specific_serial);
  331. Test.add_func("/migrations/cross_namespace/multiple_dependencies", test_cross_namespace_multiple_deps);
  332. Test.add_func("/migrations/cross_namespace/dependency_order", test_cross_namespace_order);
  333. Test.add_func("/migrations/cross_namespace/missing_namespace_error", test_cross_namespace_missing_namespace);
  334. Test.add_func("/migrations/cross_namespace/missing_serial_error", test_cross_namespace_missing_serial);
  335. // ========================================
  336. // 5. Circular Dependency Detection Tests
  337. // ========================================
  338. Test.add_func("/migrations/circular/two_migration_cycle", test_circular_two_migration);
  339. Test.add_func("/migrations/circular/three_migration_cycle", test_circular_three_migration);
  340. Test.add_func("/migrations/circular/self_dependency", test_circular_self_dependency);
  341. Test.add_func("/migrations/circular/error_message_includes_path", test_circular_error_message);
  342. // ========================================
  343. // 6. Time-Based Rollback Tests
  344. // ========================================
  345. Test.add_func("/migrations/rollback/to_specific_migration", test_rollback_to_specific);
  346. Test.add_func("/migrations/rollback/last_n_migrations", test_rollback_last_n);
  347. Test.add_func("/migrations/rollback/all_migrations", test_rollback_all);
  348. Test.add_func("/migrations/rollback/across_namespaces", test_rollback_across_namespaces);
  349. // ========================================
  350. // 7. Error Message Tests
  351. // ========================================
  352. Test.add_func("/migrations/errors/circular_dependency_message", test_error_circular_message);
  353. Test.add_func("/migrations/errors/unsatisfied_dependency_message", test_error_unsatisfied_message);
  354. Test.add_func("/migrations/errors/invalid_dependency_syntax", test_error_invalid_syntax);
  355. // ========================================
  356. // 8. SQL Generation Tests (retained from original)
  357. // ========================================
  358. Test.add_func("/migrations/sql/create_table", test_create_table_sql);
  359. Test.add_func("/migrations/sql/drop_table", test_drop_table_sql);
  360. Test.add_func("/migrations/sql/create_index", test_create_index_sql);
  361. Test.add_func("/migrations/sql/add_column", test_add_column_sql);
  362. Test.add_func("/migrations/sql/drop_column", test_drop_column_sql);
  363. Test.add_func("/migrations/sql/rename_column", test_rename_column_sql);
  364. // ========================================
  365. // 9. Foreign Key Tests (retained from original)
  366. // ========================================
  367. Test.add_func("/migrations/fk/auto_name", test_fk_creation_with_auto_generated_name);
  368. Test.add_func("/migrations/fk/explicit_name", test_fk_creation_with_explicit_name);
  369. Test.add_func("/migrations/fk/on_delete_actions", test_fk_on_delete_actions);
  370. Test.add_func("/migrations/fk/on_update_actions", test_fk_on_update_actions);
  371. Test.add_func("/migrations/fk/alter_table", test_fk_in_alter_table_add_column);
  372. // ========================================
  373. // 10. Indexed Column Tests (retained from original)
  374. // ========================================
  375. Test.add_func("/migrations/indexed/auto_name", test_indexed_with_auto_generated_name);
  376. Test.add_func("/migrations/indexed/custom_name", test_indexed_with_custom_name);
  377. Test.add_func("/migrations/indexed/unique", test_unique_indexed_creates_unique_index);
  378. Test.add_func("/migrations/indexed/drop", test_drop_index_on);
  379. return Test.run();
  380. }
  381. // ============================================================================
  382. // 1. DEPENDENCY PARSING TESTS
  383. // ============================================================================
  384. void test_parse_namespace() {
  385. try {
  386. var dep = Dependency.parse("auth");
  387. assert(dep.namespace == "auth");
  388. assert(dep.serial == null);
  389. } catch (SqlError e) {
  390. assert_not_reached();
  391. }
  392. }
  393. void test_parse_namespace_serial() {
  394. try {
  395. var dep = Dependency.parse("auth:2");
  396. assert(dep.namespace == "auth");
  397. assert(dep.serial != null);
  398. assert(dep.serial == 2);
  399. } catch (SqlError e) {
  400. assert_not_reached();
  401. }
  402. }
  403. void test_parse_serial_zero() {
  404. try {
  405. var dep = Dependency.parse("auth:0");
  406. assert(dep.namespace == "auth");
  407. assert(dep.serial != null);
  408. assert(dep.serial == 0); // Serial 0 is valid, not null
  409. } catch (SqlError e) {
  410. assert_not_reached();
  411. }
  412. }
  413. void test_parse_empty_namespace() {
  414. try {
  415. Dependency.parse(":1");
  416. assert_not_reached(); // Should have thrown
  417. } catch (SqlError e) {
  418. assert("namespace cannot be empty" in e.message || "Invalid dependency syntax" in e.message);
  419. }
  420. }
  421. void test_parse_empty_serial() {
  422. try {
  423. Dependency.parse("auth:");
  424. assert_not_reached(); // Should have thrown
  425. } catch (SqlError e) {
  426. assert("Invalid dependency syntax" in e.message);
  427. }
  428. }
  429. void test_parse_invalid_serial() {
  430. try {
  431. Dependency.parse("auth:abc");
  432. assert_not_reached(); // Should have thrown
  433. } catch (SqlError e) {
  434. assert("serial must be a number" in e.message || "Invalid dependency syntax" in e.message);
  435. }
  436. }
  437. void test_parse_multiple_colons() {
  438. try {
  439. Dependency.parse("auth:1:extra");
  440. assert_not_reached(); // Should have thrown
  441. } catch (SqlError e) {
  442. assert("Invalid dependency syntax" in e.message);
  443. }
  444. }
  445. void test_dependency_to_string() {
  446. try {
  447. var dep1 = Dependency.parse("auth");
  448. assert(dep1.to_string() == "auth");
  449. var dep2 = Dependency.parse("auth:2");
  450. assert(dep2.to_string() == "auth:2");
  451. var dep3 = Dependency.parse("logging:0");
  452. assert(dep3.to_string() == "logging:0");
  453. } catch (SqlError e) {
  454. assert_not_reached();
  455. }
  456. }
  457. void test_dependency_equality() {
  458. try {
  459. var dep1 = Dependency.parse("auth");
  460. var dep2 = Dependency.parse("auth");
  461. assert(dep1.equals_dependency(dep2));
  462. var dep3 = Dependency.parse("auth:1");
  463. var dep4 = Dependency.parse("auth:1");
  464. assert(dep3.equals_dependency(dep4));
  465. var dep5 = Dependency.parse("auth:1");
  466. var dep6 = Dependency.parse("auth:2");
  467. assert(!dep5.equals_dependency(dep6));
  468. var dep7 = Dependency.parse("auth");
  469. var dep8 = Dependency.parse("auth:1");
  470. assert(!dep7.equals_dependency(dep8));
  471. } catch (SqlError e) {
  472. assert_not_reached();
  473. }
  474. }
  475. // ============================================================================
  476. // 2. SINGLE NAMESPACE MIGRATION TESTS
  477. // ============================================================================
  478. void test_single_namespace_register() {
  479. try {
  480. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  481. var dialect = new SqliteDialect();
  482. var runner = new MigrationRunner(conn, dialect);
  483. runner.register_migration(new Test_Auth_V1());
  484. runner.register_migration(new Test_Auth_V2());
  485. // Current serial should be 0 (nothing applied yet)
  486. var current = runner.get_current_serial("auth");
  487. assert(current == 0);
  488. conn.close();
  489. } catch (SqlError e) {
  490. assert_not_reached();
  491. }
  492. }
  493. void test_single_namespace_migrate_to_latest() {
  494. try {
  495. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  496. var dialect = new SqliteDialect();
  497. var runner = new MigrationRunner(conn, dialect);
  498. runner.register_migration(new Test_Auth_V1());
  499. runner.register_migration(new Test_Auth_V2());
  500. runner.migrate_to_latest();
  501. // Both migrations should be applied
  502. var current = runner.get_current_serial("auth");
  503. assert(current == 2);
  504. // Verify tables exist
  505. var cmd = conn.create_command("SELECT name FROM sqlite_master WHERE type='table' AND name='users'");
  506. assert(cmd.execute_query().any());
  507. cmd = conn.create_command("SELECT name FROM sqlite_master WHERE type='table' AND name='roles'");
  508. assert(cmd.execute_query().any());
  509. conn.close();
  510. } catch (SqlError e) {
  511. stderr.printf("ERROR: %s\n", e.message);
  512. assert_not_reached();
  513. }
  514. }
  515. void test_single_namespace_get_applied() {
  516. try {
  517. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  518. var dialect = new SqliteDialect();
  519. var runner = new MigrationRunner(conn, dialect);
  520. runner.register_migration(new Test_Auth_V1());
  521. runner.register_migration(new Test_Auth_V2());
  522. runner.register_migration(new Test_Auth_V3());
  523. // Before migration: no applied
  524. var applied = runner.get_applied_migrations("auth");
  525. assert(applied.length == 0);
  526. runner.migrate_to_latest();
  527. // After migration: 3 applied in auth namespace
  528. applied = runner.get_applied_migrations("auth");
  529. assert(applied.length == 3);
  530. // Verify order (by application_order)
  531. assert(applied[0].serial == 1);
  532. assert(applied[1].serial == 2);
  533. assert(applied[2].serial == 3);
  534. conn.close();
  535. } catch (SqlError e) {
  536. assert_not_reached();
  537. }
  538. }
  539. void test_single_namespace_get_pending() {
  540. try {
  541. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  542. var dialect = new SqliteDialect();
  543. var runner = new MigrationRunner(conn, dialect);
  544. runner.register_migration(new Test_Auth_V1());
  545. runner.register_migration(new Test_Auth_V2());
  546. runner.register_migration(new Test_Auth_V3());
  547. // Before migration: all pending
  548. var pending = runner.get_pending_migrations("auth");
  549. assert(pending.length == 3);
  550. // Apply first migration only via migrate_to
  551. runner.migrate_to("auth", 1);
  552. // After: 2 pending (V2 and V3)
  553. pending = runner.get_pending_migrations("auth");
  554. assert(pending.length == 2);
  555. conn.close();
  556. } catch (SqlError e) {
  557. assert_not_reached();
  558. }
  559. }
  560. void test_single_namespace_rollback() {
  561. try {
  562. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  563. var dialect = new SqliteDialect();
  564. var runner = new MigrationRunner(conn, dialect);
  565. runner.register_migration(new Test_Auth_V1());
  566. runner.register_migration(new Test_Auth_V2());
  567. runner.register_migration(new Test_Auth_V3());
  568. runner.migrate_to_latest();
  569. assert(runner.get_current_serial("auth") == 3);
  570. // Rollback one step
  571. runner.rollback(1);
  572. assert(runner.get_current_serial("auth") == 2);
  573. // Rollback two more steps
  574. runner.rollback(2);
  575. assert(runner.get_current_serial("auth") == 0);
  576. conn.close();
  577. } catch (SqlError e) {
  578. assert_not_reached();
  579. }
  580. }
  581. void test_single_namespace_get_current_serial() {
  582. try {
  583. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  584. var dialect = new SqliteDialect();
  585. var runner = new MigrationRunner(conn, dialect);
  586. runner.register_migration(new Test_Auth_V1());
  587. runner.register_migration(new Test_Auth_V2());
  588. // No migrations applied
  589. assert(runner.get_current_serial("auth") == 0);
  590. // Apply first
  591. runner.migrate_to("auth", 1);
  592. assert(runner.get_current_serial("auth") == 1);
  593. // Apply second
  594. runner.migrate_to("auth", 2);
  595. assert(runner.get_current_serial("auth") == 2);
  596. // Unknown namespace returns 0
  597. assert(runner.get_current_serial("unknown") == 0);
  598. conn.close();
  599. } catch (SqlError e) {
  600. assert_not_reached();
  601. }
  602. }
  603. // ============================================================================
  604. // 3. MULTI-NAMESPACE MIGRATION TESTS
  605. // ============================================================================
  606. void test_multi_register_namespaces() {
  607. try {
  608. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  609. var dialect = new SqliteDialect();
  610. var runner = new MigrationRunner(conn, dialect);
  611. // Register migrations from multiple namespaces
  612. runner.register_migration(new Test_Auth_V1());
  613. runner.register_migration(new Test_Auth_V2());
  614. runner.register_migration(new Test_App_V1());
  615. runner.register_migration(new Test_App_V2());
  616. runner.register_migration(new Test_Logging_V1());
  617. // All should start at 0
  618. assert(runner.get_current_serial("auth") == 0);
  619. assert(runner.get_current_serial("app") == 0);
  620. assert(runner.get_current_serial("logging") == 0);
  621. conn.close();
  622. } catch (SqlError e) {
  623. assert_not_reached();
  624. }
  625. }
  626. void test_multi_migrate_to_latest_all() {
  627. try {
  628. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  629. var dialect = new SqliteDialect();
  630. var runner = new MigrationRunner(conn, dialect);
  631. // Register all migrations
  632. runner.register_migration(new Test_Auth_V1());
  633. runner.register_migration(new Test_Auth_V2());
  634. runner.register_migration(new Test_App_V1());
  635. runner.register_migration(new Test_App_V2());
  636. runner.register_migration(new Test_Logging_V1());
  637. runner.register_migration(new Test_Logging_V2());
  638. runner.migrate_to_latest();
  639. // All namespaces should be at their latest
  640. assert(runner.get_current_serial("auth") == 2);
  641. assert(runner.get_current_serial("app") == 2);
  642. assert(runner.get_current_serial("logging") == 2);
  643. // Verify all tables exist
  644. string[] expected_tables = {"users", "roles", "orders", "order_items", "audit_log"};
  645. foreach (var table in expected_tables) {
  646. var cmd = conn.create_command(
  647. "SELECT name FROM sqlite_master WHERE type='table' AND name='%s'".printf(table)
  648. );
  649. assert(cmd.execute_query().any());
  650. }
  651. conn.close();
  652. } catch (SqlError e) {
  653. assert_not_reached();
  654. }
  655. }
  656. void test_multi_migrate_specific_namespace() {
  657. try {
  658. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  659. var dialect = new SqliteDialect();
  660. var runner = new MigrationRunner(conn, dialect);
  661. runner.register_migration(new Test_Auth_V1());
  662. runner.register_migration(new Test_Auth_V2());
  663. runner.register_migration(new Test_App_V1()); // depends on "auth"
  664. runner.register_migration(new Test_Logging_V1()); // no dependencies
  665. // Migrate only the auth namespace
  666. runner.migrate_to_latest_for_namespace("auth");
  667. // Auth should be migrated
  668. assert(runner.get_current_serial("auth") == 2);
  669. // App and logging should not be migrated (even though logging has no deps)
  670. assert(runner.get_current_serial("app") == 0);
  671. assert(runner.get_current_serial("logging") == 0);
  672. conn.close();
  673. } catch (SqlError e) {
  674. assert_not_reached();
  675. }
  676. }
  677. void test_multi_filter_applied_by_namespace() {
  678. try {
  679. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  680. var dialect = new SqliteDialect();
  681. var runner = new MigrationRunner(conn, dialect);
  682. runner.register_migration(new Test_Auth_V1());
  683. runner.register_migration(new Test_Auth_V2());
  684. runner.register_migration(new Test_App_V1());
  685. runner.register_migration(new Test_Logging_V1());
  686. runner.migrate_to_latest();
  687. // Get applied for auth only
  688. var auth_applied = runner.get_applied_migrations("auth");
  689. assert(auth_applied.length == 2);
  690. foreach (var record in auth_applied) {
  691. assert(record.migration_namespace == "auth");
  692. }
  693. // Get applied for app only
  694. var app_applied = runner.get_applied_migrations("app");
  695. assert(app_applied.length == 1);
  696. foreach (var record in app_applied) {
  697. assert(record.migration_namespace == "app");
  698. }
  699. // Get all applied
  700. var all_applied = runner.get_applied_migrations();
  701. assert(all_applied.length == 4);
  702. conn.close();
  703. } catch (SqlError e) {
  704. assert_not_reached();
  705. }
  706. }
  707. void test_multi_filter_pending_by_namespace() {
  708. try {
  709. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  710. var dialect = new SqliteDialect();
  711. var runner = new MigrationRunner(conn, dialect);
  712. runner.register_migration(new Test_Auth_V1());
  713. runner.register_migration(new Test_Auth_V2());
  714. runner.register_migration(new Test_Auth_V3());
  715. runner.register_migration(new Test_App_V1());
  716. runner.register_migration(new Test_Logging_V1());
  717. // Apply only auth:1 and auth:2
  718. runner.migrate_to("auth", 2);
  719. // Pending for auth should be just V3
  720. var auth_pending = runner.get_pending_migrations("auth");
  721. assert(auth_pending.length == 1);
  722. assert(auth_pending[0].serial == 3);
  723. // Pending for app should be V1
  724. var app_pending = runner.get_pending_migrations("app");
  725. assert(app_pending.length == 1);
  726. // Pending for logging should be V1
  727. var log_pending = runner.get_pending_migrations("logging");
  728. assert(log_pending.length == 1);
  729. conn.close();
  730. } catch (SqlError e) {
  731. assert_not_reached();
  732. }
  733. }
  734. // ============================================================================
  735. // 4. CROSS-NAMESPACE DEPENDENCY TESTS
  736. // ============================================================================
  737. void test_cross_namespace_dependency() {
  738. try {
  739. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  740. var dialect = new SqliteDialect();
  741. var runner = new MigrationRunner(conn, dialect);
  742. // App V1 depends on "auth" namespace (any migration)
  743. runner.register_migration(new Test_Auth_V1());
  744. runner.register_migration(new Test_App_V1());
  745. runner.migrate_to_latest();
  746. // Both should be applied
  747. assert(runner.get_current_serial("auth") == 1);
  748. assert(runner.get_current_serial("app") == 1);
  749. // Verify auth was applied BEFORE app by checking application_order
  750. var all_applied = runner.get_applied_migrations();
  751. int64? auth_order = null;
  752. int64? app_order = null;
  753. foreach (var record in all_applied) {
  754. if (record.migration_namespace == "auth" && record.serial == 1) {
  755. auth_order = record.application_order;
  756. }
  757. if (record.migration_namespace == "app" && record.serial == 1) {
  758. app_order = record.application_order;
  759. }
  760. }
  761. assert(auth_order != null);
  762. assert(app_order != null);
  763. assert(auth_order < app_order); // Auth must be applied before app
  764. conn.close();
  765. } catch (SqlError e) {
  766. assert_not_reached();
  767. }
  768. }
  769. void test_cross_namespace_specific_serial() {
  770. try {
  771. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  772. var dialect = new SqliteDialect();
  773. var runner = new MigrationRunner(conn, dialect);
  774. // App V2 depends on auth:1 specifically
  775. runner.register_migration(new Test_Auth_V1());
  776. runner.register_migration(new Test_Auth_V2());
  777. runner.register_migration(new Test_App_V2()); // depends on auth:1, app:1
  778. runner.register_migration(new Test_App_V1()); // depends on auth
  779. runner.migrate_to_latest();
  780. // All should be applied
  781. assert(runner.get_current_serial("auth") == 2);
  782. assert(runner.get_current_serial("app") == 2);
  783. conn.close();
  784. } catch (SqlError e) {
  785. stderr.printf("ERROR in specific_serial: %s\n", e.message);
  786. assert_not_reached();
  787. }
  788. }
  789. void test_cross_namespace_multiple_deps() {
  790. try {
  791. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  792. var dialect = new SqliteDialect();
  793. var runner = new MigrationRunner(conn, dialect);
  794. // App V2 has multiple dependencies: auth:1 and app:1
  795. runner.register_migration(new Test_Auth_V1());
  796. runner.register_migration(new Test_App_V1());
  797. runner.register_migration(new Test_App_V2());
  798. runner.migrate_to_latest();
  799. // Verify all dependencies were applied before App V2
  800. var all_applied = runner.get_applied_migrations();
  801. int64? auth1_order = null;
  802. int64? app1_order = null;
  803. int64? app2_order = null;
  804. foreach (var record in all_applied) {
  805. if (record.migration_namespace == "auth" && record.serial == 1) auth1_order = record.application_order;
  806. if (record.migration_namespace == "app" && record.serial == 1) app1_order = record.application_order;
  807. if (record.migration_namespace == "app" && record.serial == 2) app2_order = record.application_order;
  808. }
  809. assert(auth1_order != null);
  810. assert(app1_order != null);
  811. assert(app2_order != null);
  812. // App V2 must come after both auth:1 and app:1
  813. assert(app2_order > auth1_order);
  814. assert(app2_order > app1_order);
  815. conn.close();
  816. } catch (SqlError e) {
  817. assert_not_reached();
  818. }
  819. }
  820. void test_cross_namespace_order() {
  821. try {
  822. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  823. var dialect = new SqliteDialect();
  824. var runner = new MigrationRunner(conn, dialect);
  825. // Register in random order
  826. runner.register_migration(new Test_App_V2()); // depends on auth:1, app:1
  827. runner.register_migration(new Test_Logging_V1()); // no deps
  828. runner.register_migration(new Test_Auth_V2()); // depends on auth:1
  829. runner.register_migration(new Test_App_V1()); // depends on auth
  830. runner.register_migration(new Test_Auth_V1()); // no deps
  831. runner.migrate_to_latest();
  832. // Verify correct order regardless of registration order
  833. var all_applied = runner.get_applied_migrations();
  834. // Build a map of application orders
  835. var orders = new Dictionary<string, int64?>();
  836. foreach (var record in all_applied) {
  837. string key = "%s:%s".printf(record.migration_namespace, record.serial.to_string());
  838. orders.set(key, record.application_order);
  839. }
  840. // Verify ordering constraints
  841. // auth:1 must come before auth:2
  842. assert(orders.get("auth:1") < orders.get("auth:2"));
  843. // auth:1 must come before app:1 (app:1 depends on "auth")
  844. assert(orders.get("auth:1") < orders.get("app:1"));
  845. // app:1 must come before app:2
  846. assert(orders.get("app:1") < orders.get("app:2"));
  847. // auth:1 must come before app:2 (app:2 depends on auth:1)
  848. assert(orders.get("auth:1") < orders.get("app:2"));
  849. conn.close();
  850. } catch (SqlError e) {
  851. assert_not_reached();
  852. }
  853. }
  854. void test_cross_namespace_missing_namespace() {
  855. try {
  856. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  857. var dialect = new SqliteDialect();
  858. var runner = new MigrationRunner(conn, dialect);
  859. // Register migration that depends on non-existent namespace
  860. runner.register_migration(new Test_Missing_Namespace());
  861. runner.validate_dependencies();
  862. assert_not_reached(); // Should have thrown
  863. } catch (SqlError e) {
  864. assert("requires namespace" in e.message || "Unsatisfied dependency" in e.message);
  865. }
  866. }
  867. void test_cross_namespace_missing_serial() {
  868. try {
  869. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  870. var dialect = new SqliteDialect();
  871. var runner = new MigrationRunner(conn, dialect);
  872. // Register auth:1 and migration that depends on auth:999
  873. runner.register_migration(new Test_Auth_V1());
  874. runner.register_migration(new Test_Missing_Serial());
  875. runner.validate_dependencies();
  876. assert_not_reached(); // Should have thrown
  877. } catch (SqlError e) {
  878. assert("serial" in e.message || "Unsatisfied dependency" in e.message);
  879. }
  880. }
  881. // ============================================================================
  882. // 5. CIRCULAR DEPENDENCY DETECTION TESTS
  883. // ============================================================================
  884. void test_circular_two_migration() {
  885. try {
  886. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  887. var dialect = new SqliteDialect();
  888. var runner = new MigrationRunner(conn, dialect);
  889. // Circular A depends on B, B depends on A
  890. runner.register_migration(new Test_Circular_A());
  891. runner.register_migration(new Test_Circular_B());
  892. runner.migrate_to_latest();
  893. assert_not_reached(); // Should have thrown
  894. } catch (SqlError e) {
  895. assert("Circular dependency" in e.message);
  896. }
  897. }
  898. void test_circular_three_migration() {
  899. try {
  900. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  901. var dialect = new SqliteDialect();
  902. var runner = new MigrationRunner(conn, dialect);
  903. // Three-way cycle: A -> B -> C -> A
  904. runner.register_migration(new Test_Cycle_A());
  905. runner.register_migration(new Test_Cycle_B());
  906. runner.register_migration(new Test_Cycle_C());
  907. runner.migrate_to_latest();
  908. assert_not_reached(); // Should have thrown
  909. } catch (SqlError e) {
  910. assert("Circular dependency" in e.message);
  911. }
  912. }
  913. void test_circular_self_dependency() {
  914. try {
  915. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  916. var dialect = new SqliteDialect();
  917. var runner = new MigrationRunner(conn, dialect);
  918. // Self-dependency
  919. runner.register_migration(new Test_Self_Dependency());
  920. runner.migrate_to_latest();
  921. assert_not_reached(); // Should have thrown
  922. } catch (SqlError e) {
  923. assert("Circular dependency" in e.message);
  924. }
  925. }
  926. void test_circular_error_message() {
  927. try {
  928. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  929. var dialect = new SqliteDialect();
  930. var runner = new MigrationRunner(conn, dialect);
  931. runner.register_migration(new Test_Circular_A());
  932. runner.register_migration(new Test_Circular_B());
  933. runner.migrate_to_latest();
  934. assert_not_reached();
  935. } catch (SqlError e) {
  936. // Error message should include the cycle path
  937. assert("Circular dependency" in e.message);
  938. // Should show the cycle with arrow notation
  939. assert("→" in e.message || "->" in e.message || "circular" in e.message.down());
  940. }
  941. }
  942. // ============================================================================
  943. // 6. TIME-BASED ROLLBACK TESTS
  944. // ============================================================================
  945. void test_rollback_to_specific() {
  946. try {
  947. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  948. var dialect = new SqliteDialect();
  949. var runner = new MigrationRunner(conn, dialect);
  950. runner.register_migration(new Test_Auth_V1());
  951. runner.register_migration(new Test_Auth_V2());
  952. runner.register_migration(new Test_Auth_V3());
  953. runner.migrate_to_latest();
  954. assert(runner.get_current_serial("auth") == 3);
  955. // Rollback to auth:1
  956. runner.rollback_to("auth", 1);
  957. assert(runner.get_current_serial("auth") == 1);
  958. // V2 and V3 should be rolled back (tables dropped)
  959. // Only users table should exist
  960. var cmd = conn.create_command("SELECT name FROM sqlite_master WHERE type='table' AND name='roles'");
  961. assert(!cmd.execute_query().any());
  962. conn.close();
  963. } catch (SqlError e) {
  964. assert_not_reached();
  965. }
  966. }
  967. void test_rollback_last_n() {
  968. try {
  969. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  970. var dialect = new SqliteDialect();
  971. var runner = new MigrationRunner(conn, dialect);
  972. runner.register_migration(new Test_Auth_V1());
  973. runner.register_migration(new Test_Auth_V2());
  974. runner.register_migration(new Test_Auth_V3());
  975. runner.migrate_to_latest();
  976. // Rollback last 2 migrations
  977. runner.rollback(2);
  978. assert(runner.get_current_serial("auth") == 1);
  979. conn.close();
  980. } catch (SqlError e) {
  981. assert_not_reached();
  982. }
  983. }
  984. void test_rollback_all() {
  985. try {
  986. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  987. var dialect = new SqliteDialect();
  988. var runner = new MigrationRunner(conn, dialect);
  989. runner.register_migration(new Test_Auth_V1());
  990. runner.register_migration(new Test_Auth_V2());
  991. runner.register_migration(new Test_App_V1());
  992. runner.register_migration(new Test_Logging_V1());
  993. runner.migrate_to_latest();
  994. // Rollback all
  995. runner.rollback_all();
  996. assert(runner.get_current_serial("auth") == 0);
  997. assert(runner.get_current_serial("app") == 0);
  998. assert(runner.get_current_serial("logging") == 0);
  999. // No migrations should be applied
  1000. var applied = runner.get_applied_migrations();
  1001. assert(applied.length == 0);
  1002. conn.close();
  1003. } catch (SqlError e) {
  1004. assert_not_reached();
  1005. }
  1006. }
  1007. void test_rollback_across_namespaces() {
  1008. try {
  1009. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1010. var dialect = new SqliteDialect();
  1011. var runner = new MigrationRunner(conn, dialect);
  1012. // Register migrations that will apply in a specific order
  1013. runner.register_migration(new Test_Auth_V1()); // 1st
  1014. runner.register_migration(new Test_Auth_V2()); // 2nd (depends on auth:1)
  1015. runner.register_migration(new Test_App_V1()); // 3rd (depends on auth)
  1016. runner.register_migration(new Test_Logging_V1()); // 4th (no deps)
  1017. runner.migrate_to_latest();
  1018. // Verify all are applied
  1019. var applied = runner.get_applied_migrations();
  1020. assert(applied.length == 4);
  1021. // Rollback to auth:1 should roll back logging:1, app:1, auth:2 in that order
  1022. runner.rollback_to("auth", 1);
  1023. // Only auth:1 should remain
  1024. applied = runner.get_applied_migrations();
  1025. assert(applied.length == 1);
  1026. assert(applied[0].migration_namespace == "auth");
  1027. assert(applied[0].serial == 1);
  1028. conn.close();
  1029. } catch (SqlError e) {
  1030. assert_not_reached();
  1031. }
  1032. }
  1033. // ============================================================================
  1034. // 7. ERROR MESSAGE TESTS
  1035. // ============================================================================
  1036. void test_error_circular_message() {
  1037. try {
  1038. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1039. var dialect = new SqliteDialect();
  1040. var runner = new MigrationRunner(conn, dialect);
  1041. runner.register_migration(new Test_Circular_A());
  1042. runner.register_migration(new Test_Circular_B());
  1043. runner.validate_dependencies();
  1044. assert_not_reached();
  1045. } catch (SqlError e) {
  1046. // Verify error message format
  1047. assert("Circular dependency detected" in e.message);
  1048. }
  1049. }
  1050. void test_error_unsatisfied_message() {
  1051. try {
  1052. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1053. var dialect = new SqliteDialect();
  1054. var runner = new MigrationRunner(conn, dialect);
  1055. runner.register_migration(new Test_Auth_V1());
  1056. runner.register_migration(new Test_Missing_Serial()); // depends on auth:999
  1057. runner.validate_dependencies();
  1058. assert_not_reached();
  1059. } catch (SqlError e) {
  1060. // Verify error message contains useful information
  1061. assert("Unsatisfied dependency" in e.message);
  1062. assert("999" in e.message); // The missing serial
  1063. assert("auth" in e.message); // The namespace
  1064. }
  1065. }
  1066. void test_error_invalid_syntax() {
  1067. try {
  1068. Dependency.parse("auth:abc:extra");
  1069. assert_not_reached();
  1070. } catch (SqlError e) {
  1071. assert("Invalid dependency syntax" in e.message);
  1072. }
  1073. }
  1074. // ============================================================================
  1075. // 8. SQL GENERATION TESTS (retained from original)
  1076. // ============================================================================
  1077. void test_create_table_sql() {
  1078. var dialect = new SqliteDialect();
  1079. var op = new CreateTableOperation() { table_name = "test" };
  1080. op.columns.add(new ColumnDefinition() {
  1081. name = "id",
  1082. column_type = ColumnType.INT_64,
  1083. is_primary_key = true,
  1084. auto_increment = true
  1085. });
  1086. op.columns.add(new ColumnDefinition() {
  1087. name = "name",
  1088. column_type = ColumnType.TEXT,
  1089. is_required = true
  1090. });
  1091. var sql = dialect.create_table_sql(op);
  1092. assert("CREATE TABLE test" in sql);
  1093. assert("id" in sql);
  1094. assert("INTEGER" in sql);
  1095. assert("PRIMARY KEY" in sql);
  1096. assert("name" in sql);
  1097. assert("TEXT" in sql);
  1098. assert("NOT NULL" in sql);
  1099. }
  1100. void test_drop_table_sql() {
  1101. var dialect = new SqliteDialect();
  1102. var op = new DropTableOperation() { table_name = "test" };
  1103. var sql = dialect.drop_table_sql(op);
  1104. assert(sql == "DROP TABLE IF EXISTS test");
  1105. }
  1106. void test_create_index_sql() {
  1107. var dialect = new SqliteDialect();
  1108. var op = new CreateIndexOperation() {
  1109. index_name = "idx_test",
  1110. table_name = "test",
  1111. is_unique = false
  1112. };
  1113. op.columns.add("name");
  1114. var sql = dialect.create_index_sql(op);
  1115. assert("CREATE INDEX idx_test ON test (name)" == sql);
  1116. }
  1117. void test_add_column_sql() {
  1118. var dialect = new SqliteDialect();
  1119. var op = new AddColumnOperation() {
  1120. table_name = "users",
  1121. column = new ColumnDefinition() {
  1122. name = "age",
  1123. column_type = ColumnType.INT_32
  1124. }
  1125. };
  1126. var sql = dialect.add_column_sql(op);
  1127. assert("ALTER TABLE users ADD COLUMN age" in sql);
  1128. assert("INTEGER" in sql);
  1129. }
  1130. void test_drop_column_sql() {
  1131. var dialect = new SqliteDialect();
  1132. var op = new DropColumnOperation() {
  1133. table_name = "users",
  1134. column_name = "age"
  1135. };
  1136. var sql = dialect.drop_column_sql(op);
  1137. assert("ALTER TABLE users DROP COLUMN age" == sql);
  1138. }
  1139. void test_rename_column_sql() {
  1140. var dialect = new SqliteDialect();
  1141. var op = new RenameColumnOperation() {
  1142. table_name = "users",
  1143. old_name = "old_name",
  1144. new_name = "new_name"
  1145. };
  1146. var sql = dialect.rename_column_sql(op);
  1147. assert("ALTER TABLE users RENAME COLUMN old_name TO new_name" == sql);
  1148. }
  1149. // ============================================================================
  1150. // 9. FOREIGN KEY TESTS (retained from original)
  1151. // ============================================================================
  1152. void test_fk_creation_with_auto_generated_name() {
  1153. var dialect = new SqliteDialect();
  1154. var builder = new MigrationBuilder(dialect);
  1155. builder.create_table("orders", t => {
  1156. t.column<int64?>("id").primary_key().auto_increment();
  1157. t.column<int64?>("user_id")
  1158. .not_null()
  1159. .references("users", "id");
  1160. });
  1161. var ops = builder.get_operations();
  1162. assert(ops.length == 1);
  1163. var table_op = ops[0] as CreateTableOperation;
  1164. assert(table_op != null);
  1165. assert(table_op.constraints.length == 1);
  1166. var constraint = table_op.constraints.get(0);
  1167. assert(constraint.constraint_type == "FOREIGN KEY");
  1168. assert(constraint.name == "fk_orders_user_id");
  1169. assert(constraint.reference_table == "users");
  1170. assert(constraint.reference_columns.get(0) == "id");
  1171. assert(constraint.columns.get(0) == "user_id");
  1172. var sql = dialect.create_table_sql(table_op);
  1173. assert("FOREIGN KEY" in sql);
  1174. assert("REFERENCES users (id)" in sql);
  1175. assert("fk_orders_user_id" in sql);
  1176. }
  1177. void test_fk_creation_with_explicit_name() {
  1178. var dialect = new SqliteDialect();
  1179. var builder = new MigrationBuilder(dialect);
  1180. builder.create_table("orders", t => {
  1181. t.column<int64?>("id").primary_key().auto_increment();
  1182. t.column<int64?>("user_id")
  1183. .not_null()
  1184. .references("users", "id")
  1185. .name("custom_fk_orders_users");
  1186. });
  1187. var ops = builder.get_operations();
  1188. var table_op = ops[0] as CreateTableOperation;
  1189. assert(table_op != null);
  1190. var constraint = table_op.constraints.get(0);
  1191. assert(constraint.name == "custom_fk_orders_users");
  1192. var sql = dialect.create_table_sql(table_op);
  1193. assert("custom_fk_orders_users" in sql);
  1194. }
  1195. void test_fk_on_delete_actions() {
  1196. var dialect = new SqliteDialect();
  1197. // Test CASCADE
  1198. var builder = new MigrationBuilder(dialect);
  1199. builder.create_table("orders", t => {
  1200. t.column<int64?>("user_id")
  1201. .references("users", "id")
  1202. .on_delete_cascade();
  1203. });
  1204. var ops = builder.get_operations();
  1205. var table_op = ops[0] as CreateTableOperation;
  1206. var constraint = table_op.constraints.get(0);
  1207. assert(constraint.on_delete_action == ReferentialAction.CASCADE);
  1208. var sql = dialect.create_table_sql(table_op);
  1209. assert("ON DELETE CASCADE" in sql);
  1210. // Test SET NULL
  1211. builder = new MigrationBuilder(dialect);
  1212. builder.create_table("orders", t => {
  1213. t.column<int64?>("user_id")
  1214. .references("users", "id")
  1215. .on_delete_set_null();
  1216. });
  1217. ops = builder.get_operations();
  1218. table_op = ops[0] as CreateTableOperation;
  1219. constraint = table_op.constraints.get(0);
  1220. assert(constraint.on_delete_action == ReferentialAction.SET_NULL);
  1221. sql = dialect.create_table_sql(table_op);
  1222. assert("ON DELETE SET NULL" in sql);
  1223. // Test RESTRICT
  1224. builder = new MigrationBuilder(dialect);
  1225. builder.create_table("orders", t => {
  1226. t.column<int64?>("user_id")
  1227. .references("users", "id")
  1228. .on_delete_restrict();
  1229. });
  1230. ops = builder.get_operations();
  1231. table_op = ops[0] as CreateTableOperation;
  1232. constraint = table_op.constraints.get(0);
  1233. assert(constraint.on_delete_action == ReferentialAction.RESTRICT);
  1234. sql = dialect.create_table_sql(table_op);
  1235. assert("ON DELETE RESTRICT" in sql);
  1236. }
  1237. void test_fk_on_update_actions() {
  1238. var dialect = new SqliteDialect();
  1239. // Test CASCADE
  1240. var builder = new MigrationBuilder(dialect);
  1241. builder.create_table("orders", t => {
  1242. t.column<int64?>("user_id")
  1243. .references("users", "id")
  1244. .on_update_cascade();
  1245. });
  1246. var ops = builder.get_operations();
  1247. var table_op = ops[0] as CreateTableOperation;
  1248. var constraint = table_op.constraints.get(0);
  1249. assert(constraint.on_update_action == ReferentialAction.CASCADE);
  1250. var sql = dialect.create_table_sql(table_op);
  1251. assert("ON UPDATE CASCADE" in sql);
  1252. // Test SET NULL
  1253. builder = new MigrationBuilder(dialect);
  1254. builder.create_table("orders", t => {
  1255. t.column<int64?>("user_id")
  1256. .references("users", "id")
  1257. .on_update_set_null();
  1258. });
  1259. ops = builder.get_operations();
  1260. table_op = ops[0] as CreateTableOperation;
  1261. constraint = table_op.constraints.get(0);
  1262. assert(constraint.on_update_action == ReferentialAction.SET_NULL);
  1263. sql = dialect.create_table_sql(table_op);
  1264. assert("ON UPDATE SET NULL" in sql);
  1265. }
  1266. void test_fk_in_alter_table_add_column() {
  1267. var dialect = new SqliteDialect();
  1268. var builder = new MigrationBuilder(dialect);
  1269. builder.alter_table("orders", t => {
  1270. t.add_column<int64?>("product_id")
  1271. .not_null()
  1272. .references("products", "id")
  1273. .on_delete_restrict();
  1274. });
  1275. var ops = builder.get_operations();
  1276. assert(ops.length == 1);
  1277. var add_col_op = ops[0] as AddColumnOperation;
  1278. assert(add_col_op != null);
  1279. assert(add_col_op.foreign_key_constraint != null);
  1280. var fk = add_col_op.foreign_key_constraint;
  1281. assert(fk.constraint_type == "FOREIGN KEY");
  1282. assert(fk.reference_table == "products");
  1283. assert(fk.reference_columns.get(0) == "id");
  1284. assert(fk.on_delete_action == ReferentialAction.RESTRICT);
  1285. var sql = dialect.add_column_sql(add_col_op);
  1286. assert("REFERENCES products (id)" in sql);
  1287. assert("ON DELETE RESTRICT" in sql);
  1288. }
  1289. // ============================================================================
  1290. // 10. INDEXED COLUMN TESTS (retained from original)
  1291. // ============================================================================
  1292. void test_indexed_with_auto_generated_name() {
  1293. var dialect = new SqliteDialect();
  1294. var builder = new MigrationBuilder(dialect);
  1295. builder.create_table("users", t => {
  1296. t.column<int64?>("id").primary_key().auto_increment();
  1297. t.column<string>("email")
  1298. .not_null()
  1299. .indexed();
  1300. });
  1301. var ops = builder.get_operations();
  1302. assert(ops.length == 2);
  1303. var table_op = ops[0] as CreateTableOperation;
  1304. assert(table_op != null);
  1305. var idx_op = ops[1] as CreateIndexOperation;
  1306. assert(idx_op != null);
  1307. assert(idx_op.index_name == "idx_users_email");
  1308. assert(idx_op.table_name == "users");
  1309. assert(idx_op.columns.length == 1);
  1310. assert(idx_op.columns.get(0) == "email");
  1311. assert(idx_op.is_unique == false);
  1312. }
  1313. void test_indexed_with_custom_name() {
  1314. var dialect = new SqliteDialect();
  1315. var builder = new MigrationBuilder(dialect);
  1316. builder.create_table("users", t => {
  1317. t.column<int64?>("id").primary_key().auto_increment();
  1318. t.column<string>("email")
  1319. .not_null()
  1320. .indexed("idx_users_email_address");
  1321. });
  1322. var ops = builder.get_operations();
  1323. assert(ops.length == 2);
  1324. var idx_op = ops[1] as CreateIndexOperation;
  1325. assert(idx_op != null);
  1326. assert(idx_op.index_name == "idx_users_email_address");
  1327. }
  1328. void test_unique_indexed_creates_unique_index() {
  1329. var dialect = new SqliteDialect();
  1330. var builder = new MigrationBuilder(dialect);
  1331. builder.create_table("users", t => {
  1332. t.column<int64?>("id").primary_key().auto_increment();
  1333. t.column<string>("email")
  1334. .not_null()
  1335. .unique()
  1336. .indexed();
  1337. });
  1338. var ops = builder.get_operations();
  1339. assert(ops.length == 2);
  1340. var idx_op = ops[1] as CreateIndexOperation;
  1341. assert(idx_op != null);
  1342. assert(idx_op.is_unique == true);
  1343. var sql = dialect.create_index_sql(idx_op);
  1344. assert("CREATE UNIQUE INDEX" in sql);
  1345. }
  1346. void test_drop_index_on() {
  1347. var dialect = new SqliteDialect();
  1348. var builder = new MigrationBuilder(dialect);
  1349. builder.alter_table("users", t => {
  1350. t.drop_index_on("email");
  1351. });
  1352. var ops = builder.get_operations();
  1353. assert(ops.length == 1);
  1354. var drop_op = ops[0] as DropIndexOperation;
  1355. assert(drop_op != null);
  1356. assert(drop_op.index_name == "idx_users_email");
  1357. assert(drop_op.table_name == "users");
  1358. }