commit 93a61fe2ed14f7a63192a6515bf54daa82196e3e
parent dfdb8aed65969037fda129f9fc702a1681ca0bcb
Author: triesap <tyson@radroots.org>
Date: Sat, 6 Jun 2026 13:25:39 -0700
tests: add nip42 conformance suite
- add relay-backed NIP-42 authentication conformance test
- assert challenge frames are issued on connection
- reject AUTH events sent through EVENT and unauthenticated writes
- verify authenticated writes persist while AUTH events stay transient
Diffstat:
2 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/crates/tangle/tests/nip42_conformance.rs b/crates/tangle/tests/nip42_conformance.rs
@@ -0,0 +1,71 @@
+#![forbid(unsafe_code)]
+
+mod support;
+
+use std::fs;
+use support::{
+ RelayHarness, assert_ok, connect_client_with_challenge, reopen_store, send_auth, send_event,
+};
+use tangle_test_support::{
+ FixtureKey, auth_event_spec, build_fixture_event, valid_public_listing_spec,
+};
+
+#[tokio::test]
+async fn nip42_conformance_challenges_authenticates_and_gates_writes() {
+ let seller = FixtureKey::Seller.public_key();
+ let harness = RelayHarness::start(
+ "nip42_conformance",
+ serde_json::json!({
+ "require_write_auth": true,
+ "approved_sellers": [seller.as_str()]
+ }),
+ );
+ let listing = build_fixture_event(&valid_public_listing_spec()).expect("listing");
+ let auth = build_fixture_event(&auth_event_spec()).expect("auth");
+
+ let (mut client, challenge) = connect_client_with_challenge(harness.port).await;
+ assert_eq!(challenge[0], "AUTH");
+ assert_eq!(challenge[1], "challenge-001");
+
+ let auth_as_event = send_event(&mut client, &auth).await;
+ assert_ok(&auth_as_event, false);
+ assert!(
+ auth_as_event[3]
+ .as_str()
+ .expect("auth rejection")
+ .contains("auth events must use AUTH")
+ );
+ let unauthenticated_write = send_event(&mut client, &listing).await;
+ assert_ok(&unauthenticated_write, false);
+ assert!(
+ unauthenticated_write[3]
+ .as_str()
+ .expect("write rejection")
+ .contains("write authentication required")
+ );
+
+ assert_ok(&send_auth(&mut client, &auth).await, true);
+ assert_ok(&send_event(&mut client, &listing).await, true);
+
+ let store_config = harness.store_config();
+ let root = harness.root.clone();
+ drop(client);
+ harness.stop();
+ let store = reopen_store(&store_config).await;
+ assert!(
+ store
+ .raw_event_row(listing.id())
+ .await
+ .expect("raw row")
+ .is_some()
+ );
+ assert!(
+ store
+ .raw_event_row(auth.id())
+ .await
+ .expect("auth raw row")
+ .is_none()
+ );
+ drop(store);
+ fs::remove_dir_all(root).expect("remove runtime root");
+}
diff --git a/crates/tangle/tests/support/mod.rs b/crates/tangle/tests/support/mod.rs
@@ -66,11 +66,17 @@ impl RelayHarness {
}
pub async fn connect_client(port: u16) -> RelayClient {
+ let (client, challenge) = connect_client_with_challenge(port).await;
+ assert_eq!(challenge[0], "AUTH");
+ client
+}
+
+pub async fn connect_client_with_challenge(port: u16) -> (RelayClient, Value) {
let (mut client, _) = tokio_tungstenite::connect_async(format!("ws://127.0.0.1:{port}/ws"))
.await
.expect("client connect");
- assert_eq!(next_label(&mut client).await, "AUTH");
- client
+ let challenge = next_json(&mut client).await;
+ (client, challenge)
}
pub async fn send_event(client: &mut RelayClient, event: &Event) -> Value {