fix(migrations): adopt published ledgerless baseline
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
use super::*;
|
||||
|
||||
async fn ledgerless_v1(pool: &sqlx::PgPool) {
|
||||
MigrationAuthority::apply(pool).await.unwrap();
|
||||
remove_v4_schema(pool).await;
|
||||
remove_v3_schema(pool).await;
|
||||
sqlx::raw_sql(
|
||||
"drop table __crank_migrations, __crank_migration_legacy_audit;
|
||||
drop table __crank_mcp_migrations;
|
||||
drop index mcp_transport_sessions_expires_at_idx;
|
||||
drop table __crank_ext_migrations;
|
||||
create table __crank_ext_migrations (
|
||||
extension_name text not null,
|
||||
version integer not null,
|
||||
applied_at timestamptz not null default now(),
|
||||
primary key (extension_name, version)
|
||||
);
|
||||
drop table __crank_core_migrations;",
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ledgerless_baseline_with_optional_index_drift_is_rejected_without_writes() {
|
||||
let database_url = crank_test_support::postgres_schema_url("test_ledgerless_index").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
ledgerless_v1(&pool).await;
|
||||
sqlx::raw_sql(
|
||||
"drop index mcp_transport_sessions_workspace_agent_idx;
|
||||
create index mcp_transport_sessions_workspace_agent_idx
|
||||
on mcp_transport_sessions(id);",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let error = MigrationAuthority::apply(&pool).await.unwrap_err();
|
||||
assert_eq!(error.code(), "partial_sequence");
|
||||
assert_eq!(error.stage(), "preflight.legacy_fingerprint");
|
||||
let core_exists: bool = sqlx::query_scalar(
|
||||
"select to_regclass(format('%I.%I', current_schema(), '__crank_core_migrations')) is not null",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!core_exists, "rejected adoption must remain read-only");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ledgerless_baseline_with_disabled_integrity_triggers_is_rejected_without_writes() {
|
||||
let database_url = crank_test_support::postgres_schema_url("test_ledgerless_triggers").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
ledgerless_v1(&pool).await;
|
||||
sqlx::query("alter table memberships disable trigger all")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let error = MigrationAuthority::apply(&pool).await.unwrap_err();
|
||||
assert_eq!(error.code(), "partial_sequence");
|
||||
assert_eq!(error.stage(), "preflight.legacy_fingerprint");
|
||||
let core_exists: bool = sqlx::query_scalar(
|
||||
"select to_regclass(format('%I.%I', current_schema(), '__crank_core_migrations')) is not null",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!core_exists, "rejected adoption must remain read-only");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn published_ledgerless_baseline_upgrades_without_data_loss() {
|
||||
let database_url = crank_test_support::postgres_schema_url("test_ledgerless_baseline").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
ledgerless_v1(&pool).await;
|
||||
sqlx::query(
|
||||
"insert into operations
|
||||
(id, workspace_id, name, display_name, protocol, status, created_at, updated_at)
|
||||
values ('op_ledgerless', 'ws_default', 'ledgerless', 'Ledgerless', 'rest', 'draft', now(), now())",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
MigrationAuthority::preflight(&pool).await.unwrap(),
|
||||
MigrationPreflight::MigrationRequired {
|
||||
current: 0,
|
||||
target: 13,
|
||||
}
|
||||
);
|
||||
MigrationAuthority::apply(&pool).await.unwrap();
|
||||
|
||||
let operation_count: i64 =
|
||||
sqlx::query_scalar("select count(*) from operations where id = 'op_ledgerless'")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(operation_count, 1);
|
||||
assert_eq!(
|
||||
MigrationAuthority::preflight(&pool).await.unwrap(),
|
||||
MigrationPreflight::Current { version: 13 }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn published_in_place_mcp_upgrade_layout_is_accepted() {
|
||||
let database_url = crank_test_support::postgres_schema_url("test_ledgerless_mcp_upgrade").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
ledgerless_v1(&pool).await;
|
||||
sqlx::raw_sql(
|
||||
"drop table mcp_transport_sessions;
|
||||
create table mcp_transport_sessions (
|
||||
id text primary key,
|
||||
protocol_version text not null,
|
||||
initialized boolean not null default false,
|
||||
workspace_slug text not null,
|
||||
agent_slug text not null,
|
||||
created_at timestamptz not null,
|
||||
updated_at timestamptz not null,
|
||||
expires_at timestamptz null
|
||||
);
|
||||
create index mcp_transport_sessions_workspace_agent_idx
|
||||
on mcp_transport_sessions(workspace_slug, agent_slug, updated_at desc);
|
||||
alter table mcp_transport_sessions
|
||||
add column supports_elicitation boolean not null default false;",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
MigrationAuthority::preflight(&pool).await.unwrap(),
|
||||
MigrationPreflight::MigrationRequired {
|
||||
current: 0,
|
||||
target: 13,
|
||||
}
|
||||
);
|
||||
MigrationAuthority::apply(&pool).await.unwrap();
|
||||
assert_eq!(
|
||||
MigrationAuthority::preflight(&pool).await.unwrap(),
|
||||
MigrationPreflight::Current { version: 13 }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ledgerless_baseline_with_future_drift_is_rejected_without_writes() {
|
||||
let database_url = crank_test_support::postgres_schema_url("test_ledgerless_drift").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
ledgerless_v1(&pool).await;
|
||||
sqlx::query("alter table invocation_logs add column trace_id text")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let error = MigrationAuthority::apply(&pool).await.unwrap_err();
|
||||
assert_eq!(error.code(), "partial_sequence");
|
||||
assert_eq!(error.stage(), "preflight.legacy_fingerprint");
|
||||
let core_exists: bool = sqlx::query_scalar(
|
||||
"select to_regclass(format('%I.%I', current_schema(), '__crank_core_migrations')) is not null",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!core_exists, "rejected adoption must remain read-only");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ledgerless_baseline_with_constraint_drift_is_rejected_without_writes() {
|
||||
let database_url = crank_test_support::postgres_schema_url("test_ledgerless_constraint").await;
|
||||
let pool = sqlx::PgPool::connect(&database_url).await.unwrap();
|
||||
ledgerless_v1(&pool).await;
|
||||
sqlx::query("alter table workspaces alter column status drop not null")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let error = MigrationAuthority::apply(&pool).await.unwrap_err();
|
||||
assert_eq!(error.code(), "partial_sequence");
|
||||
assert_eq!(error.stage(), "preflight.legacy_fingerprint");
|
||||
let core_exists: bool = sqlx::query_scalar(
|
||||
"select to_regclass(format('%I.%I', current_schema(), '__crank_core_migrations')) is not null",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!core_exists, "rejected adoption must remain read-only");
|
||||
}
|
||||
Reference in New Issue
Block a user