use super::*; #[tokio::test] async fn healthy_v11_upgrades_to_v12_without_rewriting_prior_ledger() { let database_url = crank_test_support::postgres_schema_url("test_v11_to_v12_artifacts").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); let prior = sqlx::query( "select version, name, checksum, applied_at from __crank_migrations where version <= 11 order by version", ) .fetch_all(&pool) .await .unwrap() .into_iter() .map(|row| { ( row.get::("version"), row.get::("name"), row.get::("checksum"), row.get::("applied_at"), ) }) .collect::>(); remove_v12_schema(&pool).await; assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::MigrationRequired { current: 11, target: 12, } ); MigrationAuthority::apply(&pool).await.unwrap(); let after = sqlx::query( "select version, name, checksum, applied_at from __crank_migrations where version <= 11 order by version", ) .fetch_all(&pool) .await .unwrap() .into_iter() .map(|row| { ( row.get::("version"), row.get::("name"), row.get::("checksum"), row.get::("applied_at"), ) }) .collect::>(); assert_eq!(after, prior); let v12_applied_at: time::OffsetDateTime = sqlx::query_scalar("select applied_at from __crank_migrations where version = 12") .fetch_one(&pool) .await .unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); let replayed_at: time::OffsetDateTime = sqlx::query_scalar("select applied_at from __crank_migrations where version = 12") .fetch_one(&pool) .await .unwrap(); assert_eq!(replayed_at, v12_applied_at); } #[tokio::test] async fn v12_exact_guard_rejects_column_constraint_index_and_relation_drift() { let database_url = crank_test_support::postgres_schema_url("test_v12_artifact_drift").await; let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); MigrationAuthority::apply(&pool).await.unwrap(); for (drift, restore) in [ ( "alter table artifact_blobs add column unexpected text null;", "alter table artifact_blobs drop column unexpected;", ), ( "create index unrelated_idx on artifact_sources(source_id) include (mime_type);", "drop index unrelated_idx;", ), ( "alter table artifact_blobs alter column storage_lifecycle set default 'unavailable';", "alter table artifact_blobs alter column storage_lifecycle set default 'available';", ), ( "alter table artifact_sources alter column mime_type drop not null;", "alter table artifact_sources alter column mime_type set not null;", ), ( "alter table artifact_blobs drop constraint artifact_blobs_size_check; alter table artifact_blobs add constraint artifact_blobs_size_check check (size_bytes between 1 and 262145);", "alter table artifact_blobs drop constraint artifact_blobs_size_check; alter table artifact_blobs add constraint artifact_blobs_size_check check (size_bytes between 1 and 262144);", ), ( "drop index artifact_sources_workspace_created_idx; create index artifact_sources_workspace_created_idx on artifact_sources(workspace_id, source_id, created_at);", "drop index artifact_sources_workspace_created_idx; create index artifact_sources_workspace_created_idx on artifact_sources(workspace_id, created_at, source_id);", ), ( "alter table artifact_sources alter column created_at type timestamptz(3);", "alter table artifact_sources alter column created_at type timestamptz;", ), ( "alter table artifact_sources enable row level security;", "alter table artifact_sources disable row level security;", ), ( "create policy unexpected_policy on artifact_sources using (true);", "drop policy unexpected_policy on artifact_sources;", ), ( "create function unrelated_trigger_fn() returns trigger language plpgsql as $$ begin return new; end $$; create trigger unexpected_trigger before insert on artifact_sources for each row execute function unrelated_trigger_fn();", "drop trigger unexpected_trigger on artifact_sources; drop function unrelated_trigger_fn();", ), ( "alter table artifact_sources set unlogged;", "alter table artifact_sources set logged;", ), ( "alter table artifact_sources drop constraint artifact_sources_id_check; alter table artifact_sources add constraint artifact_sources_id_check check (source_id ~ '^src_[a-zA-Z0-9_-]{1,128}$');", "alter table artifact_sources drop constraint artifact_sources_id_check; alter table artifact_sources add constraint artifact_sources_id_check check (source_id ~ '^src_[A-Za-z0-9_-]{1,128}$');", ), ( "alter table artifact_blobs drop constraint artifact_blobs_claim_shape_check; alter table artifact_blobs add constraint artifact_blobs_claim_shape_check check ( claim_token is null and (claim_expires_at is null or claim_token is not null) and claim_expires_at is not null );", "alter table artifact_blobs drop constraint artifact_blobs_claim_shape_check; alter table artifact_blobs add constraint artifact_blobs_claim_shape_check check ( (claim_token is null and claim_expires_at is null) or (claim_token is not null and claim_expires_at is not null) );", ), ] { sqlx::raw_sql(drift).execute(&pool).await.unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence", "drift: {drift}"); assert_eq!(error.version(), Some(12), "drift: {drift}"); sqlx::raw_sql(restore).execute(&pool).await.unwrap(); assert_eq!( MigrationAuthority::preflight(&pool).await.unwrap(), MigrationPreflight::Current { version: 12 }, "restore: {restore}" ); } sqlx::query("alter table artifact_sources rename to artifact_sources_table") .execute(&pool) .await .unwrap(); sqlx::query("create view artifact_sources as select * from artifact_sources_table") .execute(&pool) .await .unwrap(); let error = MigrationAuthority::preflight(&pool).await.unwrap_err(); assert_eq!(error.code(), "partial_sequence"); assert_eq!(error.version(), Some(12)); }