tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit 128957f2d7871203597cd703f2ee7f60c1198b43
parent d55008cb87e41f46e01b5624dd86116b26fbbb32
Author: triesap <tyson@radroots.org>
Date:   Sat,  6 Jun 2026 13:37:32 -0700

tests: add discussion conformance suite

- add a relay-backed discussion conformance test
- publish listing comments and forum threads through WebSocket clients
- verify public discussion HTTP endpoints from the running relay
- assert raw, comment, forum topic, and search projection rows in SurrealDB

Diffstat:
Acrates/tangle/tests/discussion_conformance.rs | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 231 insertions(+), 0 deletions(-)

diff --git a/crates/tangle/tests/discussion_conformance.rs b/crates/tangle/tests/discussion_conformance.rs @@ -0,0 +1,231 @@ +#![forbid(unsafe_code)] + +mod support; + +use std::fs; +use support::{ + RelayHarness, assert_ok, connect_client, http_get, next_label, reopen_store, + request_event_by_id, send_auth, send_event, +}; +use tangle_protocol::{Event, EventId}; +use tangle_test_support::{ + FixtureKey, auth_event_spec, build_fixture_event, build_fixture_event_from_parts, + valid_public_listing_spec, +}; + +#[tokio::test] +async fn discussion_conformance_projects_listing_comments_and_forum_threads() { + let seller = FixtureKey::Seller.public_key(); + let harness = RelayHarness::start( + "discussion_conformance", + serde_json::json!({ + "approved_sellers": [seller.as_str()] + }), + ); + let listing = build_fixture_event(&valid_public_listing_spec()).expect("listing"); + let listing_comment = listing_comment(&listing, 1_714_124_436, "Can I pickup Saturday?"); + let thread = forum_thread(1_714_124_438, Some("Market day thread"), &["market", "csa"]); + let thread_comment = forum_thread_comment(&thread, 1_714_124_439, "I can bring greens."); + 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, &listing_comment, &thread, &thread_comment] { + assert_ok(&send_event(&mut client, event).await, true); + } + + let fetched_comment = + request_event_by_id(&mut client, "discussion-comment", &listing_comment).await; + assert_eq!(fetched_comment[0], "EVENT"); + assert_eq!(fetched_comment[1], "discussion-comment"); + assert_eq!(fetched_comment[2]["id"], listing_comment.id().as_str()); + assert_eq!(next_label(&mut client).await, "EOSE"); + let fetched_thread = request_event_by_id(&mut client, "discussion-thread", &thread).await; + assert_eq!(fetched_thread[0], "EVENT"); + assert_eq!(fetched_thread[1], "discussion-thread"); + assert_eq!(fetched_thread[2]["id"], thread.id().as_str()); + assert_eq!(next_label(&mut client).await, "EOSE"); + + let listing_comments = http_get( + harness.port, + &format!( + "/api/listings/{}/listing-a/comments?limit=5", + seller.as_str() + ), + ); + assert!(listing_comments.contains("200 OK")); + assert!(listing_comments.contains(listing_comment.id().as_str())); + assert!(listing_comments.contains("Can I pickup Saturday?")); + let forum_threads = http_get(harness.port, "/api/forum/threads?topic=market&limit=5"); + assert!(forum_threads.contains("200 OK")); + assert!(forum_threads.contains(thread.id().as_str())); + assert!(forum_threads.contains("Market day thread")); + let forum_detail = http_get( + harness.port, + &format!("/api/forum/threads/{}", thread.id().as_str()), + ); + assert!(forum_detail.contains("200 OK")); + assert!(forum_detail.contains(thread.id().as_str())); + assert!(forum_detail.contains("Market day thread")); + let forum_comments = http_get( + harness.port, + &format!( + "/api/forum/threads/{}/comments?limit=5", + thread.id().as_str() + ), + ); + assert!(forum_comments.contains("200 OK")); + assert!(forum_comments.contains(thread_comment.id().as_str())); + assert!(forum_comments.contains("I can bring greens.")); + + 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, &listing_comment, &thread, &thread_comment] { + assert!( + store + .raw_event_row(event.id()) + .await + .expect("raw row") + .is_some() + ); + } + let listing_key = format!("30402:{}:listing-a", seller.as_str()); + let listing_comment_row = store + .comment_projection_row(listing_comment.id()) + .await + .expect("listing comment row") + .expect("listing comment row exists"); + assert_eq!( + listing_comment_row["event_id"], + listing_comment.id().as_str() + ); + assert_eq!(listing_comment_row["root_target_type"], "address"); + assert_eq!(listing_comment_row["root_ref"], listing_key); + assert_eq!(listing_comment_row["root_kind"], "30402"); + assert_eq!(listing_comment_row["content"], "Can I pickup Saturday?"); + let thread_row = store + .forum_thread_row(thread.id()) + .await + .expect("thread row") + .expect("thread row exists"); + assert_eq!(thread_row["event_id"], thread.id().as_str()); + assert_eq!(thread_row["title"], "Market day thread"); + assert_eq!( + thread_row["content"], + "What is everyone bringing this weekend?" + ); + let topics = store + .forum_thread_topic_rows(thread.id()) + .await + .expect("topic rows"); + assert_eq!(topics.len(), 2); + assert_eq!(topics[0]["topic"], "csa"); + assert_eq!(topics[1]["topic"], "market"); + let thread_comment_row = store + .comment_projection_row(thread_comment.id()) + .await + .expect("thread comment row") + .expect("thread comment row exists"); + assert_eq!(thread_comment_row["event_id"], thread_comment.id().as_str()); + assert_eq!(thread_comment_row["root_target_type"], "event"); + assert_eq!(thread_comment_row["root_ref"], thread.id().as_str()); + assert_eq!(thread_comment_row["root_kind"], "11"); + assert_eq!(thread_comment_row["content"], "I can bring greens."); + assert!( + store + .search_document_row(thread.id().as_str()) + .await + .expect("thread search row") + .is_some() + ); + drop(store); + fs::remove_dir_all(root).expect("remove runtime root"); +} + +fn listing_comment(listing: &Event, created_at: u64, content: &str) -> Event { + let listing_key = format!("30402:{}:listing-a", listing.unsigned().pubkey().as_str()); + build_fixture_event_from_parts( + FixtureKey::Seller, + created_at, + 1_111, + vec![ + vec!["A".to_owned(), listing_key.clone()], + vec!["K".to_owned(), "30402".to_owned()], + vec![ + "P".to_owned(), + listing.unsigned().pubkey().as_str().to_owned(), + ], + vec!["a".to_owned(), listing_key], + vec!["k".to_owned(), "30402".to_owned()], + vec![ + "p".to_owned(), + listing.unsigned().pubkey().as_str().to_owned(), + ], + ], + content, + ) + .expect("comment event") +} + +fn forum_thread(created_at: u64, title: Option<&str>, topics: &[&str]) -> Event { + let mut tags = vec![ + vec!["e".to_owned(), "5".repeat(EventId::HEX_LENGTH)], + vec![ + "p".to_owned(), + FixtureKey::Buyer.public_key().as_str().to_owned(), + ], + ]; + if let Some(title) = title { + tags.push(vec!["title".to_owned(), title.to_owned()]); + } + tags.extend( + topics + .iter() + .map(|topic| vec!["t".to_owned(), (*topic).to_owned()]), + ); + build_fixture_event_from_parts( + FixtureKey::Seller, + created_at, + 11, + tags, + "What is everyone bringing this weekend?", + ) + .expect("forum thread") +} + +fn forum_thread_comment(thread: &Event, created_at: u64, content: &str) -> Event { + build_fixture_event_from_parts( + FixtureKey::Seller, + created_at, + 1_111, + vec![ + vec![ + "E".to_owned(), + thread.id().as_str().to_owned(), + "wss://relay.radroots.test".to_owned(), + thread.unsigned().pubkey().as_str().to_owned(), + ], + vec!["K".to_owned(), "11".to_owned()], + vec![ + "P".to_owned(), + thread.unsigned().pubkey().as_str().to_owned(), + ], + vec![ + "e".to_owned(), + thread.id().as_str().to_owned(), + "wss://relay.radroots.test".to_owned(), + thread.unsigned().pubkey().as_str().to_owned(), + ], + vec!["k".to_owned(), "11".to_owned()], + vec![ + "p".to_owned(), + thread.unsigned().pubkey().as_str().to_owned(), + ], + ], + content, + ) + .expect("forum comment event") +}