feat(registry): add workspace-scoped artifact metadata
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
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::<i64, _>("version"),
|
||||
row.get::<String, _>("name"),
|
||||
row.get::<String, _>("checksum"),
|
||||
row.get::<time::OffsetDateTime, _>("applied_at"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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::<i64, _>("version"),
|
||||
row.get::<String, _>("name"),
|
||||
row.get::<String, _>("checksum"),
|
||||
row.get::<time::OffsetDateTime, _>("applied_at"),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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));
|
||||
}
|
||||
@@ -1,5 +1,73 @@
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn failed_artifact_metadata_migration_rolls_back_schema_and_ledger() {
|
||||
let _event_trigger_guard = EVENT_TRIGGER_TEST_LOCK.lock().await;
|
||||
let database_url =
|
||||
crank_test_support::postgres_schema_url("test_artifact_metadata_rollback").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
MigrationAuthority::apply(&pool).await.unwrap();
|
||||
remove_v12_schema(&pool).await;
|
||||
let schema: String = sqlx::query_scalar("select current_schema()")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let failure_trigger = format!(
|
||||
"create function reject_story21b_v12() returns event_trigger language plpgsql as $$
|
||||
begin
|
||||
if current_schema() = '{schema}' and current_query() like '%artifact_sources%' then
|
||||
raise exception 'injected v12 ddl failure';
|
||||
end if;
|
||||
end $$;
|
||||
create event trigger reject_story21b_v12 on ddl_command_start
|
||||
execute function reject_story21b_v12();"
|
||||
);
|
||||
sqlx::raw_sql(sqlx::AssertSqlSafe(failure_trigger))
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let error = MigrationAuthority::apply(&pool).await.unwrap_err();
|
||||
sqlx::raw_sql(
|
||||
"drop event trigger reject_story21b_v12;
|
||||
drop function reject_story21b_v12();",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(error.code(), "apply_failed");
|
||||
assert_eq!(error.version(), Some(12));
|
||||
for relation in [
|
||||
"artifact_blobs",
|
||||
"artifact_blobs_pkey",
|
||||
"artifact_blobs_artifact_ref_key",
|
||||
"artifact_sources",
|
||||
"artifact_sources_pkey",
|
||||
"artifact_sources_workspace_created_idx",
|
||||
] {
|
||||
let present: bool = sqlx::query_scalar(
|
||||
"select to_regclass(format('%I.%I', current_schema(), $1)) is not null",
|
||||
)
|
||||
.bind(relation)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!present, "{relation} must roll back with failed V12 DDL");
|
||||
}
|
||||
let ledger_v12: i64 =
|
||||
sqlx::query_scalar("select count(*) from __crank_migrations where version = 12")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(ledger_v12, 0);
|
||||
assert_eq!(
|
||||
MigrationAuthority::preflight(&pool).await.unwrap(),
|
||||
MigrationPreflight::MigrationRequired {
|
||||
current: 11,
|
||||
target: 12,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn failed_consolidation_rolls_back_all_changes() {
|
||||
let _event_trigger_guard = EVENT_TRIGGER_TEST_LOCK.lock().await;
|
||||
|
||||
Reference in New Issue
Block a user