diff --git a/crates/buzz-relay/src/handlers/section_workspace.rs b/crates/buzz-relay/src/handlers/section_workspace.rs index 2a260c8c40..b7df7c2a28 100644 --- a/crates/buzz-relay/src/handlers/section_workspace.rs +++ b/crates/buzz-relay/src/handlers/section_workspace.rs @@ -457,6 +457,16 @@ mod tests { .unwrap_err(); assert!(error.to_string().contains("immutable")); + state + .db + .soft_delete_event(tenant.community(), event.id.as_bytes()) + .await + .unwrap(); + assert!(handle_import(&tenant, &state, &event) + .await + .unwrap() + .message + .starts_with("duplicate:")); let mut conflicting = import.clone(); conflicting.action_id = uuid::Uuid::new_v4(); assert!( diff --git a/crates/buzz-search/tests/fts_integration.rs b/crates/buzz-search/tests/fts_integration.rs index 39c2508f2d..648e336016 100644 --- a/crates/buzz-search/tests/fts_integration.rs +++ b/crates/buzz-search/tests/fts_integration.rs @@ -28,6 +28,8 @@ const MIGRATION_0007_SQL: &str = include_str!("../../../migrations/0007_nip_rs_r const MIGRATION_0008_SQL: &str = include_str!("../../../migrations/0008_fresh_install_search_allowlist.sql"); const MIGRATION_0014_SQL: &str = include_str!("../../../migrations/0014_push_lease_fts.sql"); +const MIGRATION_0033_SQL: &str = + include_str!("../../../migrations/0033_section_workspace_projection_fts.sql"); async fn setup() -> (PgPool, String) { let url = std::env::var("BUZZ_TEST_DATABASE_URL").unwrap_or_else(|_| TEST_DB_URL.to_string()); @@ -81,6 +83,9 @@ async fn setup() -> (PgPool, String) { pool.execute(MIGRATION_0014_SQL) .await .expect("apply 0014 migration"); + pool.execute(MIGRATION_0033_SQL) + .await + .expect("apply 0033 migration"); (pool, schema) } @@ -1425,6 +1430,20 @@ async fn author_only_kinds_are_storage_level_unsearchable() { async fn p_gated_persistent_kinds_have_storage_null_tsvector() { let (pool, schema) = setup().await; + let expression: String = sqlx::query_scalar( + "SELECT pg_get_expr(d.adbin, d.adrelid) \ + FROM pg_attrdef d JOIN pg_attribute a \ + ON a.attrelid = d.adrelid AND a.attnum = d.adnum \ + WHERE d.adrelid = 'events'::regclass AND a.attname = 'search_tsv'", + ) + .fetch_one(&pool) + .await + .expect("read migration 0033 generated expression"); + assert!( + expression.contains("9050") && expression.contains("30623"), + "{expression}" + ); + let c = mk_community(&pool, "p-gated-tripwire.example").await; let token = "pgated_tripwire_marker_qwerty"; diff --git a/crates/buzz-test-client/tests/e2e_section_workspace.rs b/crates/buzz-test-client/tests/e2e_section_workspace.rs index 7b5c24d668..8371d9a551 100644 --- a/crates/buzz-test-client/tests/e2e_section_workspace.rs +++ b/crates/buzz-test-client/tests/e2e_section_workspace.rs @@ -2,10 +2,12 @@ use std::time::Duration; +use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; use buzz_test_client::BuzzTestClient; use nostr::{Alphabet, Event, EventBuilder, Filter, Keys, Kind, SingleLetterTag, Tag}; use reqwest::Client; use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; const IMPORT_KIND: u16 = 9050; const PROJECTION_KIND: u16 = 30623; @@ -37,11 +39,31 @@ async fn ws_query(client: &mut BuzzTestClient, name: &str, filter: Filter) -> Ve .unwrap() } +fn nip98(keys: &Keys, url: &str, body: &str) -> String { + let payload = hex::encode(Sha256::digest(body.as_bytes())); + let event = EventBuilder::new(Kind::Custom(27_235), "") + .tags([ + Tag::parse(["u", url]).unwrap(), + Tag::parse(["method", "POST"]).unwrap(), + Tag::parse(["payload", &payload]).unwrap(), + Tag::parse(["nonce", &uuid::Uuid::new_v4().to_string()]).unwrap(), + ]) + .sign_with_keys(keys) + .unwrap(); + format!( + "Nostr {}", + BASE64.encode(serde_json::to_string(&event).unwrap()) + ) +} + async fn http_query(http: &Client, reader: &Keys, filter: Filter) -> Vec { + let url = format!("{}/query", http_url()); + let body = serde_json::to_string(&vec![filter]).unwrap(); let response = http - .post(format!("{}/query", http_url())) - .header("X-Pubkey", reader.public_key().to_hex()) - .json(&vec![filter]) + .post(&url) + .header("Authorization", nip98(reader, &url, &body)) + .header("Content-Type", "application/json") + .body(body) .send() .await .unwrap(); @@ -54,10 +76,13 @@ async fn http_query(http: &Client, reader: &Keys, filter: Filter) -> Vec } async fn http_count(http: &Client, reader: &Keys, filter: Filter) -> u64 { + let url = format!("{}/count", http_url()); + let body = serde_json::to_string(&vec![filter]).unwrap(); let response = http - .post(format!("{}/count", http_url())) - .header("X-Pubkey", reader.public_key().to_hex()) - .json(&vec![filter]) + .post(&url) + .header("Authorization", nip98(reader, &url, &body)) + .header("Content-Type", "application/json") + .body(body) .send() .await .unwrap();