commit 24ad6759170d85c9752c9e866cc9548d51fa8a79
parent 128957f2d7871203597cd703f2ee7f60c1198b43
Author: triesap <tyson@radroots.org>
Date: Sat, 6 Jun 2026 13:40:49 -0700
tests: add moderation conformance suite
- add a relay-backed moderation conformance test
- publish label and report events through authenticated Nostr clients
- verify admin-only moderation query endpoints from the running relay
- assert raw, label, and report projection rows directly in SurrealDB
Diffstat:
2 files changed, 186 insertions(+), 0 deletions(-)
diff --git a/crates/tangle/tests/moderation_conformance.rs b/crates/tangle/tests/moderation_conformance.rs
@@ -0,0 +1,168 @@
+#![forbid(unsafe_code)]
+
+mod support;
+
+use std::fs;
+use support::{
+ RelayHarness, assert_ok, connect_client, http_get, http_get_admin, next_label, reopen_store,
+ request_event_by_id, send_auth, send_event,
+};
+use tangle_protocol::Event;
+use tangle_test_support::{
+ FixtureKey, auth_event_spec, build_fixture_event, build_fixture_event_from_parts,
+ valid_public_listing_spec,
+};
+
+#[tokio::test]
+async fn moderation_conformance_projects_labels_reports_and_admin_queries() {
+ let seller = FixtureKey::Seller.public_key();
+ let admin = FixtureKey::Relay.public_key();
+ let harness = RelayHarness::start(
+ "moderation_conformance",
+ serde_json::json!({
+ "admin_pubkeys": [admin.as_str()],
+ "approved_sellers": [seller.as_str()]
+ }),
+ );
+ let listing = build_fixture_event(&valid_public_listing_spec()).expect("listing");
+ let label = listing_label(&listing, 1_714_124_440, "reviewed");
+ let report = listing_report(&listing, 1_714_124_441, "spam");
+ let auth = build_fixture_event(&auth_event_spec()).expect("auth");
+
+ let mut client = connect_client(harness.port).await;
+ assert_ok(&send_auth(&mut client, &auth).await, true);
+ for event in [&listing, &label, &report] {
+ assert_ok(&send_event(&mut client, event).await, true);
+ }
+ let fetched_label = request_event_by_id(&mut client, "moderation-label", &label).await;
+ assert_eq!(fetched_label[0], "EVENT");
+ assert_eq!(fetched_label[1], "moderation-label");
+ assert_eq!(fetched_label[2]["id"], label.id().as_str());
+ assert_eq!(next_label(&mut client).await, "EOSE");
+ let fetched_report = request_event_by_id(&mut client, "moderation-report", &report).await;
+ assert_eq!(fetched_report[0], "EVENT");
+ assert_eq!(fetched_report[1], "moderation-report");
+ assert_eq!(fetched_report[2]["id"], report.id().as_str());
+ assert_eq!(next_label(&mut client).await, "EOSE");
+
+ let unauthenticated_labels = http_get(
+ harness.port,
+ &format!(
+ "/api/admin/moderation/labels?target_type=event&target_ref={}&namespace=com.radroots.moderation&label=reviewed&limit=5",
+ listing.id().as_str()
+ ),
+ );
+ assert!(unauthenticated_labels.contains("401 Unauthorized"));
+ let labels = http_get_admin(
+ harness.port,
+ &format!(
+ "/api/admin/moderation/labels?target_type=event&target_ref={}&namespace=com.radroots.moderation&label=reviewed&limit=5",
+ listing.id().as_str()
+ ),
+ admin.as_str(),
+ );
+ assert!(labels.contains("200 OK"));
+ assert!(labels.contains(label.id().as_str()));
+ assert!(labels.contains("\"label\":\"reviewed\""));
+ let reports = http_get_admin(
+ harness.port,
+ &format!(
+ "/api/admin/moderation/reports?target_type=event&target_ref={}&report_type=spam&limit=5",
+ listing.id().as_str()
+ ),
+ admin.as_str(),
+ );
+ assert!(reports.contains("200 OK"));
+ assert!(reports.contains(report.id().as_str()));
+ assert!(reports.contains("\"report_type\":\"spam\""));
+
+ let store_config = harness.store_config();
+ let root = harness.root.clone();
+ drop(client);
+ harness.stop();
+ let store = reopen_store(&store_config).await;
+ for event in [&listing, &label, &report] {
+ assert!(
+ store
+ .raw_event_row(event.id())
+ .await
+ .expect("raw row")
+ .is_some()
+ );
+ }
+ let label_rows = store
+ .label_projection_rows(label.id())
+ .await
+ .expect("label rows");
+ assert_eq!(label_rows.len(), 2);
+ let event_label = label_rows
+ .iter()
+ .find(|row| row["target_type"] == "event")
+ .expect("event label");
+ assert_eq!(event_label["event_id"], label.id().as_str());
+ assert_eq!(event_label["target_ref"], listing.id().as_str());
+ assert_eq!(event_label["namespace"], "com.radroots.moderation");
+ assert_eq!(event_label["label"], "reviewed");
+ let address_label = label_rows
+ .iter()
+ .find(|row| row["target_type"] == "address")
+ .expect("address label");
+ assert_eq!(
+ address_label["target_ref"],
+ format!("30402:{}:listing-a", seller.as_str())
+ );
+ let report_rows = store
+ .report_projection_rows(report.id())
+ .await
+ .expect("report rows");
+ let event_report = report_rows
+ .iter()
+ .find(|row| {
+ row["target_type"] == "event"
+ && row["target_ref"] == listing.id().as_str()
+ && row["report_type"] == "spam"
+ })
+ .expect("event report");
+ assert_eq!(event_report["reported_pubkeys"][0], seller.as_str());
+ drop(store);
+ fs::remove_dir_all(root).expect("remove runtime root");
+}
+
+fn listing_label(listing: &Event, created_at: u64, label: &str) -> Event {
+ let listing_key = format!("30402:{}:listing-a", listing.unsigned().pubkey().as_str());
+ let namespace = "com.radroots.moderation";
+ build_fixture_event_from_parts(
+ FixtureKey::Seller,
+ created_at,
+ 1_985,
+ vec![
+ vec!["L".to_owned(), namespace.to_owned()],
+ vec!["l".to_owned(), label.to_owned(), namespace.to_owned()],
+ vec!["e".to_owned(), listing.id().as_str().to_owned()],
+ vec!["a".to_owned(), listing_key],
+ ],
+ "moderator label",
+ )
+ .expect("label event")
+}
+
+fn listing_report(listing: &Event, created_at: u64, report_type: &str) -> Event {
+ build_fixture_event_from_parts(
+ FixtureKey::Seller,
+ created_at,
+ 1_984,
+ vec![
+ vec![
+ "p".to_owned(),
+ listing.unsigned().pubkey().as_str().to_owned(),
+ ],
+ vec![
+ "e".to_owned(),
+ listing.id().as_str().to_owned(),
+ report_type.to_owned(),
+ ],
+ ],
+ "moderator report",
+ )
+ .expect("report event")
+}
diff --git a/crates/tangle/tests/support/mod.rs b/crates/tangle/tests/support/mod.rs
@@ -173,6 +173,24 @@ pub fn http_get(port: u16, path: &str) -> String {
try_http_get(port, path).expect("http get")
}
+pub fn http_get_admin(port: u16, path: &str, admin_pubkey: &str) -> String {
+ let mut stream = TcpStream::connect(("127.0.0.1", port)).expect("http connect");
+ stream
+ .set_read_timeout(Some(Duration::from_secs(2)))
+ .expect("read timeout");
+ stream
+ .set_write_timeout(Some(Duration::from_secs(2)))
+ .expect("write timeout");
+ write!(
+ stream,
+ "GET {path} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nAccept: application/json\r\nx-tangle-admin-pubkey: {admin_pubkey}\r\nConnection: close\r\n\r\n"
+ )
+ .expect("http get");
+ let mut response = String::new();
+ stream.read_to_string(&mut response).expect("http read");
+ response
+}
+
pub async fn reopen_store(config: &SurrealConnectionConfig) -> SurrealStore {
let started = Instant::now();
loop {