From 0a087899ec304dfddc3d83742e191ab153543175 Mon Sep 17 00:00:00 2001 From: Daniel Widgren Date: Sun, 10 May 2026 21:08:59 +0200 Subject: [PATCH] test: end-to-end migrator suite + bump kura to v2.0.2 Adds kura_sqlite_migrator_SUITE: full migrate -> verify table -> verify schema_migrations row -> rollback path against an in-memory SQLite DB. Closes the SQLite-side gap where unit tests covered DDL emission but no test exercised the kura_migrator runtime end-to-end. This suite caught two real kura bugs that v2.0.0 / v2.0.1 users would have hit on first migrate: - ensure_schema_migrations emitted Postgres-only DDL (TIMESTAMPTZ, DEFAULT now()). Fixed in kura #116 -> v2.0.1. - Dialect dispatch used erlang:function_exported/3 without code:ensure_loaded/1, so SQLite's column_type/format_default callbacks were silently invisible and the migrator fell back to the PG defaults (BIGSERIAL, etc). Fixed in kura #117 -> v2.0.2. Bumps the kura pin to v2.0.2 so this suite picks up both fixes. --- rebar.config | 2 +- rebar.lock | 2 +- test/kura_sqlite_e2e_test_repo.erl | 6 ++ test/kura_sqlite_migrator_SUITE.erl | 85 +++++++++++++++++++++ test/m20260510000000_e2e_create_widgets.erl | 19 +++++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 test/kura_sqlite_e2e_test_repo.erl create mode 100644 test/kura_sqlite_migrator_SUITE.erl create mode 100644 test/m20260510000000_e2e_create_widgets.erl diff --git a/rebar.config b/rebar.config index 186c3a2..5fd562d 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,7 @@ {erl_opts, [debug_info, warnings_as_errors]}. {deps, [ - {kura, {git, "https://github.com/Taure/kura.git", {tag, "v2.0.0"}}}, + {kura, {git, "https://github.com/Taure/kura.git", {tag, "v2.0.2"}}}, {esqlite, "~> 0.9"} ]}. diff --git a/rebar.lock b/rebar.lock index 58c31dc..8b94f6d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -2,7 +2,7 @@ [{<<"esqlite">>,{pkg,<<"esqlite">>,<<"0.9.0">>},0}, {<<"kura">>, {git,"https://github.com/Taure/kura.git", - {ref,"78f71e2e954e11c2f1cf49112dc8123c8dd8cf95"}}, + {ref,"de3fd745fc588ff9384e513b7254ddf28dcf11da"}}, 0}, {<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.4.1">>},1}]}. [ diff --git a/test/kura_sqlite_e2e_test_repo.erl b/test/kura_sqlite_e2e_test_repo.erl new file mode 100644 index 0000000..7f54ddc --- /dev/null +++ b/test/kura_sqlite_e2e_test_repo.erl @@ -0,0 +1,6 @@ +-module(kura_sqlite_e2e_test_repo). +-behaviour(kura_repo). + +-export([otp_app/0]). + +otp_app() -> kura_sqlite_e2e_test_app. diff --git a/test/kura_sqlite_migrator_SUITE.erl b/test/kura_sqlite_migrator_SUITE.erl new file mode 100644 index 0000000..28ffaeb --- /dev/null +++ b/test/kura_sqlite_migrator_SUITE.erl @@ -0,0 +1,85 @@ +-module(kura_sqlite_migrator_SUITE). +-moduledoc """ +End-to-end migration through `kura_migrator:migrate/1` against an +in-memory SQLite database. Pins the SQLite migration runtime path +that asobi-CT covers for Postgres but no app exercises for SQLite. +""". + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). + +-export([all/0, init_per_suite/1, end_per_suite/1]). +-export([ + migrate_creates_table/1, + migrate_records_version_in_schema_migrations/1, + rollback_drops_table/1 +]). + +-define(REPO, kura_sqlite_e2e_test_repo). +-define(APP, kura_sqlite_e2e_test_app). +-define(POOL, kura_sqlite_e2e_pool). +-define(VERSION, 20260510000000). + +all() -> + [ + migrate_creates_table, + migrate_records_version_in_schema_migrations, + rollback_drops_table + ]. + +init_per_suite(Config) -> + %% Make sure backend modules are loaded before kura_app:resolve_backends + %% looks them up. + {module, _} = code:ensure_loaded(kura_backend_sqlite), + application:set_env(kura, repos, #{ + ?REPO => #{ + backend => kura_backend_sqlite, + database => <<":memory:">>, + pool_size => 1 + } + }), + application:set_env(kura, ensure_database, false), + {ok, _} = application:ensure_all_started(kura), + %% Synthetic app so kura_migrator:discover_migrations/1 finds the test + %% migration via application:get_key(?APP, modules). + AppSpec = + {application, ?APP, [ + {description, "test app for kura_sqlite_migrator_SUITE"}, + {vsn, "0.0.1"}, + {modules, [?REPO, m20260510000000_e2e_create_widgets]}, + {registered, []}, + {applications, [kernel, stdlib]} + ]}, + ok = application:load(AppSpec), + Config. + +end_per_suite(_Config) -> + application:unload(?APP), + kura_pool_sqlite:stop_pool(?REPO), + application:stop(kura), + application:unset_env(kura, repos), + application:unset_env(kura, ensure_database), + ok. + +migrate_creates_table(_Config) -> + {ok, [?VERSION]} = kura_migrator:migrate(?REPO), + %% Verify the widgets table is queryable. + Result = kura_db:query(?REPO, ~"SELECT name FROM widgets WHERE 1=0", []), + ?assertMatch(#{rows := []}, Result). + +migrate_records_version_in_schema_migrations(_Config) -> + %% migrate_creates_table already applied, so this run should be a no-op. + {ok, []} = kura_migrator:migrate(?REPO), + Result = kura_db:query( + ?REPO, ~"SELECT version FROM schema_migrations ORDER BY version", [] + ), + ?assertMatch(#{rows := [#{version := ?VERSION}]}, Result). + +rollback_drops_table(_Config) -> + {ok, [?VERSION]} = kura_migrator:rollback(?REPO), + Result = kura_db:query( + ?REPO, + ~"SELECT name FROM sqlite_master WHERE type='table' AND name='widgets'", + [] + ), + ?assertMatch(#{rows := []}, Result). diff --git a/test/m20260510000000_e2e_create_widgets.erl b/test/m20260510000000_e2e_create_widgets.erl new file mode 100644 index 0000000..731e5d4 --- /dev/null +++ b/test/m20260510000000_e2e_create_widgets.erl @@ -0,0 +1,19 @@ +-module(m20260510000000_e2e_create_widgets). +-behaviour(kura_migration). + +-include_lib("kura/include/kura.hrl"). + +-export([up/0, down/0]). + +up() -> + [ + {create_table, ~"widgets", [ + #kura_column{name = id, type = id, primary_key = true, nullable = false}, + #kura_column{name = name, type = string, nullable = false}, + #kura_column{name = active, type = boolean, default = true}, + #kura_column{name = data, type = jsonb} + ]} + ]. + +down() -> + [{drop_table, ~"widgets"}].