From fa94d6627804764c98fb0170538ba85d94ee2d6a Mon Sep 17 00:00:00 2001 From: Duncan Date: Thu, 8 Oct 2026 21:03:27 -0400 Subject: [PATCH] test(relay): cover exact #buzz-channel matching against tag decoys Containment-only matching let a newer event carrying ["x","buzz-channel",] take a limit-1 project or repository lookup. Also reattach the e-tag planner comment and describe the positional check in the tag field docs. Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- crates/buzz-db/src/store/event.rs | 16 ++++--- .../src/api/artifact_postgres_tests.rs | 47 +++++++++++-------- .../tests/postgres_fts_integration.rs | 2 +- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/crates/buzz-db/src/store/event.rs b/crates/buzz-db/src/store/event.rs index ebc3fd02d8..a6eb2bbe9b 100644 --- a/crates/buzz-db/src/store/event.rs +++ b/crates/buzz-db/src/store/event.rs @@ -89,11 +89,13 @@ pub struct EventQuery { /// column is NULL (not NIP-33), so this lets identity lookups match before /// SQL `LIMIT`. Rows of other kinds are left to the caller's post-filter. pub d_tag_values: Option>, - /// Restrict results to events with an exact custom tag pair. - /// Uses JSONB containment against `tags` before SQL `LIMIT`. + /// Restrict results to events with an exact custom tag pair: a tag whose + /// first element is the name and second the value, checked positionally + /// (JSONB containment is only the index prefilter) before SQL `LIMIT`. pub custom_tag: Option<(String, String)>, - /// Restrict results to events with a `t` tag of this value. Uses JSONB - /// containment against `tags` before SQL `LIMIT`, so a per-subject read + /// Restrict results to events with a `t` tag of this value, checked + /// positionally (JSONB containment is only the index prefilter) before + /// SQL `LIMIT`, so a per-subject read /// (e.g. a kind:44300 team's current version) is not starved by newer /// events with other `t` values. pub t_tag: Option, @@ -860,9 +862,6 @@ async fn fetch_with_e_tag_deadline( Ok(rows) } -/// e-tag pushdown as one array-bound containment test instead of an N-way -/// `OR` chain, so planner cost does not scale with the number of referenced -/// ids (the thread aux hop sends one id per reply). /// Match a tag whose first element is `name` and second is `value` (NIP-01). /// /// JSONB containment alone ignores element position and multiplicity: @@ -887,6 +886,9 @@ fn push_exact_tag_filter( .push(")"); } +/// e-tag pushdown as one array-bound containment test instead of an N-way +/// `OR` chain, so planner cost does not scale with the number of referenced +/// ids (the thread aux hop sends one id per reply). fn push_e_tag_filter(qb: &mut QueryBuilder, col_prefix: &str, e_tags: &[String]) { let containments: Vec = e_tags .iter() diff --git a/crates/buzz-relay/src/api/artifact_postgres_tests.rs b/crates/buzz-relay/src/api/artifact_postgres_tests.rs index e60010cc8a..52fefb5eca 100644 --- a/crates/buzz-relay/src/api/artifact_postgres_tests.rs +++ b/crates/buzz-relay/src/api/artifact_postgres_tests.rs @@ -144,25 +144,34 @@ impl Fixture { #[ignore = "requires Postgres"] async fn project_filter_survives_artifact_query_hook() { let f = Fixture::new().await; - let project = EventBuilder::new(Kind::Custom(30621), "") - .tags([ - Tag::parse(["d", "review-project"]).unwrap(), - Tag::parse(["buzz-channel", &f.home.to_string()]).unwrap(), - ]) - .sign_with_keys(&f.owner) - .unwrap(); - f.state - .db - .insert_event(f.community, &project, None) - .await - .unwrap(); - let filter = json!([{"kinds":[30621],"#buzz-channel":[f.home]}]); - let (status, body) = f.request("/query", filter.clone()).await; - assert!(status.is_success(), "{status}: {body}"); - assert_eq!(body.as_array().unwrap().len(), 1); - assert_eq!(body[0]["id"], project.id.to_hex()); - let (status, body) = f.request("/count", filter).await; - assert!(status.is_success(), "{status}: {body}"); + let home = f.home.to_string(); + // 30621 projects and 30617 repositories bind a channel via `buzz-channel`. + for kind in [30621u16, 30617] { + let event = |tag: &[&str], at: u64| { + EventBuilder::new(Kind::Custom(kind), "") + .tags([ + Tag::parse(["d", &Uuid::new_v4().to_string()]).unwrap(), + Tag::parse(tag.iter().copied()).unwrap(), + ]) + .custom_created_at(nostr::Timestamp::from(at)) + .sign_with_keys(&f.owner) + .unwrap() + }; + let now = nostr::Timestamp::now().as_secs(); + let bound = event(&["buzz-channel", &home], now - 10); + // Newer, unbound: `buzz-channel` and the wanted id sit in an unrelated tag. + let decoy = event(&["x", "buzz-channel", &home], now); + for e in [&bound, &decoy] { + f.state.db.insert_event(f.community, e, None).await.unwrap(); + } + let filter = json!([{"kinds":[kind],"#buzz-channel":[f.home],"limit":1}]); + let (status, body) = f.request("/query", filter.clone()).await; + assert!(status.is_success(), "{kind} {status}: {body}"); + assert_eq!(body.as_array().unwrap().len(), 1, "{kind}: {body}"); + assert_eq!(body[0]["id"], bound.id.to_hex(), "{kind}: {body}"); + let (status, body) = f.request("/count", filter).await; + assert!(status.is_success(), "{kind} {status}: {body}"); + } } #[tokio::test] diff --git a/crates/buzz-search/tests/postgres_fts_integration.rs b/crates/buzz-search/tests/postgres_fts_integration.rs index 329844a3e9..580f53bc56 100644 --- a/crates/buzz-search/tests/postgres_fts_integration.rs +++ b/crates/buzz-search/tests/postgres_fts_integration.rs @@ -1323,7 +1323,7 @@ async fn author_only_kinds_are_unsearchable() { assert!( !kinds.contains(&(kind as i32)), "AUTHOR_ONLY kind:{kind} MUST NOT be searchable — \ - schema skip-set is missing this kind. AUTHOR_ONLY_KINDS={AUTHOR_ONLY_KINDS:?}, \ + schema exclusion or query fence is missing this kind. AUTHOR_ONLY_KINDS={AUTHOR_ONLY_KINDS:?}, \ hits={kinds:?}", ); }