191 lines
6.9 KiB
Rust
191 lines
6.9 KiB
Rust
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");
|
|
}
|