test(db): satisfy the 0032 roster fence in ignored Postgres tests

Migration 0032's guard_channel_roster_snapshot trigger fences INSERTed
kind 39002 rosters to the emitted four-field p tag shape matching
canonical active membership. Two #[ignore = "requires Postgres"] tests
still built pre-fence two-field rosters and were rejected with 23514 at
fixture setup — invisible to CI, which never runs ignored tests:

- large_roster_reconciliation_candidates_respect_snapshot_count_and_signer:
  INSERTed complete_tags now enumerate the creator-owner plus synthetic
  members with roles; the other tenant gets its own member-only roster.
  The deliberately stale pre-fence head keeps its two-field shape but is
  applied via UPDATE, which the INSERT fence does not police.
- locked_member_snapshot_blocks_post_capture_membership_mutation: the
  replacement roster event now carries the four-field owner p tag.

Test-only; no production code changes.

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Wakko <wakko@buzz.block.builderlab.xyz>
This commit is contained in:
Logan Johnson
2026-08-25 13:11:42 -04:00
parent 700011f7c0
commit 2cece20a11

View File

@@ -2430,13 +2430,30 @@ mod tests {
.await
.expect("insert large roster");
// The stale head predates migration 0032's INSERT fence, so its shape
// is deliberately the old two-field p tag; it is applied with UPDATE,
// which the fence does not police.
let stale_tags: Vec<serde_json::Value> =
std::iter::once(serde_json::json!(["d", channel.id.to_string()]))
.chain((0..1_000).map(|n| serde_json::json!(["p", format!("{n:064x}")])))
.collect();
// Rosters that go through INSERT must satisfy the 0032 fence: emitted
// four-field p tags that exactly match active canonical membership —
// here the creator (owner, from create_test_channel) plus the
// 1..=1500 synthetic members.
let creator_hex = hex::encode(&creator);
let complete_tags: Vec<serde_json::Value> =
std::iter::once(serde_json::json!(["d", channel.id.to_string()]))
.chain((0..1_501).map(|n| serde_json::json!(["p", format!("{n:064x}")])))
.chain(std::iter::once(serde_json::json!([
"p",
creator_hex,
"",
"owner"
])))
.chain(
(1..=extra_members)
.map(|n| serde_json::json!(["p", format!("{n:064x}"), "", "member"])),
)
.collect();
// Insert canonical-looking history first, then corrupt the newest row
@@ -2505,6 +2522,15 @@ mod tests {
.execute(&pool)
.await
.expect("insert complete other-tenant roster");
// This tenant's canonical membership is exactly the synthetic
// 0..=1500 members — the creator never joined the cloned channel.
let other_complete_tags: Vec<serde_json::Value> =
std::iter::once(serde_json::json!(["d", channel.id.to_string()]))
.chain(
(0..=extra_members)
.map(|n| serde_json::json!(["p", format!("{n:064x}"), "", "member"])),
)
.collect();
sqlx::query(
r#"
INSERT INTO events
@@ -2515,7 +2541,7 @@ mod tests {
.bind(other_community_id)
.bind(random_pubkey())
.bind(&relay_pubkey)
.bind(serde_json::Value::Array(complete_tags.clone()))
.bind(serde_json::Value::Array(other_complete_tags))
.bind(vec![0u8; 64])
.bind(channel.id)
.bind(channel.id.to_string())
@@ -3097,10 +3123,12 @@ mod tests {
.await
.expect("capture locked roster");
assert_eq!(snapshot.members.len(), 1);
// Migration 0032's roster fence admits only the emitted four-field p
// tag shape that exactly matches active canonical membership.
let event = nostr::EventBuilder::new(nostr::Kind::Custom(39002), "")
.tags(vec![
nostr::Tag::parse(["d", &channel.id.to_string()]).expect("d tag"),
nostr::Tag::parse(["p", &hex::encode(&owner)]).expect("p tag"),
nostr::Tag::parse(["p", &hex::encode(&owner), "", "owner"]).expect("p tag"),
])
.sign_with_keys(&relay_keys)
.expect("sign roster");