Files
buzz/crates/buzz-test-client
Matt Toohey 592ba29812 feat(relay): honour search_mode on WebSocket NIP-50 REQ
The HTTP bridge has read a per-filter `search_mode` extension since prefix
typeahead landed, but the WebSocket REQ path hard-coded
`SearchMode::FullText`, so a REQ carrying `search_mode: "prefix"` was
silently answered with whole-lexeme semantics: `pro` could never match
`project`. Parse the extension in `ClientMessage::parse` the way `before_id`
already is, carrying it as `search_modes: Vec<buzz_search::SearchMode>`
aligned with `filters` by index, thread it through `connection.rs` into
`handle_req` and `handle_search_req`, and use it in place of the constant.
`prefix` selects prefix mode; absence or any other value is full text, and a
missing entry falls back to full text so the existing test callers are
unchanged in behaviour. The bridge's private `extract_search_mode` moves to
`protocol.rs` as the single shared definition, so the two transports cannot
drift on the vocabulary; its two bridge unit tests pass unchanged.

`page` is deliberately not honoured on this path, and a comment at the scan
loop says why: the WebSocket handler pages internally as a scan cursor over
raw FTS candidates while `limit` counts accepted rows after post-filtering,
so a client offset would count a different thing than the limit it pairs
with and cannot compose with the scan budget pinned by
`search_scan_capacity_covers_advertised_nip11_max_limit`. Offset paging
stays a bridge-only contract. The mobile doc comment on `searchUsers` still
calls `search_mode` bridge-only; that remains the right client guidance
until relays with this change are deployed, so it is left for the client
change that starts sending search over WebSocket.

Tests: `parse_req_extracts_prefix_search_mode_per_filter` asserts
`[FullText, Prefix]` for a two-filter REQ, and
`ws_search_prefix_mode_matches_partial_token` in `e2e_nostr_interop.rs`
sends the same REQ twice over a live WebSocket, once with the extension and
once without, asserting `pro` matches `project` only in prefix mode. The
e2e test was verified to fail by mutation: with the handler pinned back to
full text it fails at the prefix assertion with an empty result set.

The two pre-existing P-256 PKCS#8 test fixtures in the bridge's NIP-FI
tests are rewritten as `concat!` of per-line literals so the BEGIN line can
carry the `sadscan:disable kingfisher.privkey.2` marker on the same line;
the pre-commit secrets scan blocks any commit touching `bridge.rs` on those
lines, honours only same-line markers, and the original `\`-continuation
literal could not take one. Both values are byte-identical to before
(checked by decoding both encodings), and the 82 NIP-FI tests that consume
them pass.

Verified with the lld linker workaround for sqlx proc-macros on this
machine: `cargo test -p buzz-relay --lib` for `protocol::tests`,
`handlers::req::tests`, the bridge `search_mode` tests, and the `nip_fi`
tests (71 and 82 passed; Postgres-only tests ignored); `cargo test -p
buzz-test-client --test e2e_nostr_interop -- --ignored` for the new test
plus the five existing NIP-50 search tests against a locally launched relay
built from this code (6 passed); `cargo fmt --all --check`; and `cargo
clippy -p buzz-relay -p buzz-test-client --all-targets -- -D warnings`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-10-02 14:29:14 +10:00
..