mirror of
https://github.com/block/buzz.git
synced 2026-10-30 14:01:37 +00:00
113 lines
4.5 KiB
Rust
113 lines
4.5 KiB
Rust
//! Upgrade-cost and mixed-writer contract for the additive revision migration.
|
|
use sqlx::{postgres::PgPoolOptions, Acquire, Executor};
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires Postgres"]
|
|
async fn revision_binding_skips_history_validation_but_enforces_new_writes() {
|
|
let url = std::env::var("BUZZ_TEST_DATABASE_URL")
|
|
.or_else(|_| std::env::var("DATABASE_URL"))
|
|
.expect("isolated test database URL");
|
|
let pool = PgPoolOptions::new()
|
|
.max_connections(1)
|
|
.connect(&url)
|
|
.await
|
|
.expect("connect");
|
|
let mut tx = pool.begin().await.expect("transaction");
|
|
let schema = format!("revision_{}", uuid::Uuid::new_v4().simple());
|
|
tx.execute(sqlx::AssertSqlSafe(format!(
|
|
"CREATE SCHEMA {schema}; SET LOCAL search_path = {schema}"
|
|
)))
|
|
.await
|
|
.expect("isolated transactional schema");
|
|
// Populated legacy tables with the semantic columns named by the trigger.
|
|
tx.execute(
|
|
"CREATE TABLE workflows (id int, community_id int, owner_pubkey text,
|
|
channel_id int, name text, definition text, definition_hash bytea, enabled bool);
|
|
CREATE TABLE workflow_runs (id int);
|
|
INSERT INTO workflows (id, name, enabled) VALUES (1, 'legacy', true);
|
|
INSERT INTO workflow_runs VALUES (1);
|
|
CREATE TABLE original_storage AS
|
|
SELECT oid, relfilenode FROM pg_class
|
|
WHERE oid IN ('workflows'::regclass, 'workflow_runs'::regclass);",
|
|
)
|
|
.await
|
|
.expect("legacy tables");
|
|
tx.execute(include_str!(
|
|
"../../../migrations/0045_workflow_revision_binding.sql"
|
|
))
|
|
.await
|
|
.expect("revision migration");
|
|
let (checks, validated): (i64, bool) = sqlx::query_as(
|
|
"SELECT count(*), bool_or(convalidated) FROM pg_constraint
|
|
WHERE conrelid IN ('workflows'::regclass, 'workflow_runs'::regclass)
|
|
AND contype = 'c'",
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
.expect("constraint state");
|
|
assert_eq!(checks, 2);
|
|
assert!(
|
|
!validated,
|
|
"upgrade must not scan historical rows to validate checks"
|
|
);
|
|
let unchanged: bool = sqlx::query_scalar(
|
|
"SELECT bool_and(c.relfilenode = o.relfilenode)
|
|
FROM original_storage o JOIN pg_class c USING (oid)",
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
.expect("physical storage");
|
|
assert!(unchanged, "additive metadata must not rewrite heaps");
|
|
let legacy_null: bool = sqlx::query_scalar(
|
|
"SELECT (SELECT definition_event_id IS NULL FROM workflows WHERE id=1)
|
|
AND (SELECT definition_event_id IS NULL FROM workflow_runs WHERE id=1)",
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
.expect("legacy provenance");
|
|
assert!(legacy_null);
|
|
|
|
// Catch check violations inside savepoints so both INSERT and UPDATE can be
|
|
// tested without aborting the outer migration/fixture transaction.
|
|
for statement in [
|
|
"INSERT INTO workflows (id, definition_event_id) VALUES (2, decode('ab','hex'))",
|
|
"INSERT INTO workflow_runs (id, definition_event_id) VALUES (2, decode('ab','hex'))",
|
|
"UPDATE workflows SET definition_event_id = decode('ab','hex') WHERE id=1",
|
|
"UPDATE workflow_runs SET definition_event_id = decode('ab','hex') WHERE id=1",
|
|
] {
|
|
let mut savepoint = tx.begin().await.expect("savepoint");
|
|
let error = savepoint
|
|
.execute(statement)
|
|
.await
|
|
.expect_err("bad length denied");
|
|
assert_eq!(
|
|
error.as_database_error().and_then(|e| e.code()).as_deref(),
|
|
Some("23514")
|
|
);
|
|
savepoint.rollback().await.expect("rollback rejected write");
|
|
}
|
|
tx.execute(
|
|
"UPDATE workflows SET definition_event_id = decode(repeat('ab',32),'hex') WHERE id=1;
|
|
UPDATE workflow_runs SET definition_event_id = decode(repeat('ab',32),'hex') WHERE id=1;
|
|
UPDATE workflows SET enabled=false WHERE id=1;",
|
|
)
|
|
.await
|
|
.expect("bind and operational update");
|
|
let bound: bool =
|
|
sqlx::query_scalar("SELECT definition_event_id IS NOT NULL FROM workflows WHERE id=1")
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
.expect("operational update preserves binding");
|
|
assert!(bound);
|
|
tx.execute("UPDATE workflows SET name=name WHERE id=1")
|
|
.await
|
|
.expect("old writer equal-value semantic update");
|
|
let cleared: bool =
|
|
sqlx::query_scalar("SELECT definition_event_id IS NULL FROM workflows WHERE id=1")
|
|
.fetch_one(&mut *tx)
|
|
.await
|
|
.expect("old writer invalidation");
|
|
assert!(cleared);
|
|
tx.rollback().await.expect("cleanup");
|
|
}
|