From 98c6fa4bfa897ff0b8f9c95ea698d4d065b5e9f3 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 18 Nov 2025 16:50:02 +0000 Subject: docs: switch focus onto grasp implementation --- .../2025-11-05-grasp01-smoke-test-design.md | 503 +++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 docs/archive/2025-11-05-grasp01-smoke-test-design.md (limited to 'docs/archive/2025-11-05-grasp01-smoke-test-design.md') diff --git a/docs/archive/2025-11-05-grasp01-smoke-test-design.md b/docs/archive/2025-11-05-grasp01-smoke-test-design.md new file mode 100644 index 0000000..ffff411 --- /dev/null +++ b/docs/archive/2025-11-05-grasp01-smoke-test-design.md @@ -0,0 +1,503 @@ +# GRASP-01 Event Relationship Smoke Tests Design + +**Version:** 1.0 +**Date:** 2025-11-05 +**Status:** Ready for Implementation + +## Overview + +This document specifies a focused suite of **smoke tests** for GRASP-01 event reference validation (lines 7-9). These tests validate the basic acceptance/rejection behavior based on event tagging relationships, separate from the comprehensive test suite. + +**Key Principle:** Events are accepted if they tag OR are tagged by accepted repositories. + +--- + +## File Location + +**Proposed Path:** `grasp-audit/src/specs/grasp01/event-acceptance-policy.rs` + +**Rationale:** +- Separate from comprehensive suite +- Clear naming indicates purpose (smoke tests for event acceptance policy) +- Lives in `grasp01/` subdirectory for organization +- Can be run independently or as part of full suite + +--- + +## Test Scenarios + +### Scenario Group 1: Accept Events Tagging Accepted Repositories + +Events that reference an already-accepted repo should be accepted. + +#### Test 1.1: `test_accept_issue_via_a_tag` +**Tags Issue → Repo via `a` tag** + +```rust +Setup: +1. Create and send repo announcement (kind 30617) +2. Create issue (kind 1621) with: + - ["a", "30617:{pubkey}:{repo-id}"] +3. Send issue + +Expected: Issue SHOULD be stored (query returns it) +``` + +--- + +#### Test 1.2: `test_accept_comment_via_A_tag` +**Tags Comment → Repo via `A` tag (NIP-22 root)** + +```rust +Setup: +1. Create and send repo announcement +2. Create comment (kind 1111) with: + - ["A", "30617:{pubkey}:{repo-id}"] // Root + - ["K", "30617"] + - ["P", "{repo-pubkey}"] +3. Send comment + +Expected: Comment SHOULD be stored +``` + +--- + +#### Test 1.3: `test_accept_kind1_via_q_tag` +**Tags Kind 1 → Repo via `q` tag (quote)** + +```rust +Setup: +1. Create and send repo announcement +2. Create kind 1 text note with: + - ["q", "30617:{pubkey}:{repo-id}"] + - content: "Check out this repo!" +3. Send kind 1 + +Expected: Kind 1 SHOULD be stored +``` + +--- + +### Scenario Group 2: Accept Events Tagging Accepted Events + +Events that reference other accepted events should be accepted (transitive acceptance). + +#### Test 2.1: `test_accept_issue_quoting_issue_via_q` +**Issue referencing unaccepted repo but quoting accepted issue** + +```rust +Setup: +1. Create and send repo A announcement +2. Create and send issue A (for repo A) +3. Create repo B announcement (DO NOT send - not accepted) +4. Create issue B (for repo B) with: + - ["a", "30617:{pubkey}:{repo-b-id}"] // References unaccepted repo B + - ["q", "{issue-a-id}"] // Quote accepted issue A +5. Send issue B + +Expected: Issue B SHOULD be stored (related via quote to accepted issue A, + even though its own repo reference is not accepted) +``` + +--- + +#### Test 2.2: `test_accept_comment_via_E_tag` +**Comment on issue via `E` tag (NIP-22)** + +```rust +Setup: +1. Create and send repo announcement +2. Create and send issue (kind 1621) +3. Create comment (kind 1111) with: + - ["E", "{issue-id}"] // Root + - ["K", "1621"] + - ["P", "{issue-author}"] + - ["e", "{issue-id}"] // Parent (same as root for top-level) + - ["k", "1621"] + - ["p", "{issue-author}"] +4. Send comment + +Expected: Comment SHOULD be stored (related to accepted issue) +``` + +--- + +#### Test 2.3: `test_accept_kind1_via_e_tag` +**Kind 1 referencing another kind 1 via `e` tag** + +```rust +Setup: +1. Create and send repo announcement +2. Create kind 1 note A with ["q", "30617:{pubkey}:{repo-id}"] +3. Send kind 1 A +4. Create kind 1 note B with: + - ["e", "{kind1-a-id}", "", "reply"] + - content: "Great point!" +5. Send kind 1 B + +Expected: Kind 1 B SHOULD be stored (related via e tag to accepted kind 1 A) +``` + +--- + +### Scenario Group 3: Accept Events Tagged by Accepted Events + +Events that are referenced BY accepted events should be accepted (forward references). + +#### Test 3.1: `test_accept_kind1_referenced_in_issue` +**Kind 1 referenced in issue via `q` tag** + +```rust +Setup: +1. Create kind 1 note (NOT sent yet) +2. Create and send repo announcement +3. Create issue with: + - ["a", "30617:{pubkey}:{repo-id}"] + - ["q", "{kind1-id}"] // Reference the not-yet-sent kind 1 +4. Send issue +5. Send kind 1 note + +Expected: Kind 1 SHOULD be stored (referenced by accepted issue) +``` + +--- + +#### Test 3.2: `test_accept_comment_referenced_in_comment` +**Comment referenced in another comment via `q` tag** + +```rust +Setup: +1. Create and send repo announcement +2. Create and send issue +3. Create comment A (NOT sent yet) +4. Create comment B with: + - ["E", "{issue-id}"] // Root + - ["e", "{issue-id}"] // Parent + - ["q", "{comment-a-id}"] // Quote comment A +5. Send comment B +6. Send comment A + +Expected: Comment A SHOULD be stored (referenced by accepted comment B) +``` + +--- + +#### Test 3.3: `test_accept_kind1_referenced_in_kind1` +**Kind 1 referenced in accepted kind 1 via `e` tag** + +```rust +Setup: +1. Create and send repo announcement +2. Create kind 1 A (NOT sent yet) +3. Create kind 1 B with: + - ["q", "30617:{pubkey}:{repo-id}"] + - ["e", "{kind1-a-id}", "", "mention"] +4. Send kind 1 B +5. Send kind 1 A + +Expected: Kind 1 A SHOULD be stored (referenced by accepted kind 1 B) +``` + +--- + +### Scenario Group 4: Reject Unrelated Events + +Events with no relationship to accepted repositories should be rejected. + +#### Test 4.1: `test_reject_orphan_issue` +**Issue from unrelated repository** + +```rust +Setup: +1. Create issue (kind 1621) with: + - ["a", "30617:{other-pubkey}:{other-repo-id}"] // Different repo +2. Send issue + +Expected: Issue SHOULD NOT be stored (no accepted repo) +``` + +--- + +#### Test 4.2: `test_reject_orphan_kind1` +**Kind 1 from unrelated context** + +```rust +Setup: +1. Create kind 1 note with generic content (no tags) +2. Send kind 1 + +Expected: Kind 1 SHOULD NOT be stored (no relationship to any repo) +``` + +--- + +#### Test 4.3: `test_reject_comment_quoting_other_repo` +**Comment quoting announcement from different repository** + +```rust +Setup: +1. Create repo A announcement (sent) +2. Create repo B announcement (NOT sent - different owner) +3. Create comment with: + - ["A", "30617:{other-pubkey}:{repo-b-id}"] // Root + - ["q", "30617:{other-pubkey}:{repo-b-id}"] // Quote unaccepted repo +4. Send comment + +Expected: Comment SHOULD NOT be stored (references unaccepted repo) +``` + +--- + +## Helper Functions + +Keep helpers minimal and focused on smoke test needs. + +**Implementation Note:** Reference [`nostr-sdk`](https://docs.rs/nostr-sdk) (rust-nostr) for event generation patterns. The SDK provides robust helpers for creating events with proper signatures and tags. Use these patterns rather than building everything from scratch. + +### `create_test_repo(client, repo_id) -> Event` +Creates a basic repo announcement with required tags. + +```rust +async fn create_test_repo(client: &AuditClient, repo_id: &str) -> Result { + client.create_repo_announcement(repo_id).await +} +``` + +--- + +### `create_issue_for_repo(client, repo_event, subject) -> Event` +Creates issue referencing repo via `a` tag. + +```rust +async fn create_issue_for_repo( + client: &AuditClient, + repo_event: &Event, + subject: &str, +) -> Result { + let repo_id = extract_d_tag(repo_event)?; + let a_tag = Tag::parse(&["a", &format!("30617:{}:{}", repo_event.pubkey, repo_id)])?; + + client.event_builder() + .kind(Kind::Custom(1621)) + .content(format!("Issue: {}", subject)) + .tag(a_tag) + .build() + .await +} +``` + +--- + +### `create_comment_for_event(client, root_event, content) -> Event` +Creates NIP-22 comment for an event. + +```rust +async fn create_comment_for_event( + client: &AuditClient, + root_event: &Event, + content: &str, +) -> Result { + client.event_builder() + .kind(Kind::Custom(1111)) + .content(content) + .tag(Tag::parse(&["E", &root_event.id.to_string()])?) + .tag(Tag::parse(&["K", &root_event.kind.to_string()])?) + .tag(Tag::parse(&["P", &root_event.pubkey.to_string()])?) + .tag(Tag::parse(&["e", &root_event.id.to_string()])?) + .tag(Tag::parse(&["k", &root_event.kind.to_string()])?) + .tag(Tag::parse(&["p", &root_event.pubkey.to_string()])?) + .build() + .await +} +``` + +--- + +### `send_and_verify_accepted(client, event) -> Result<()>` +Sends event and verifies it was stored. + +```rust +async fn send_and_verify_accepted(client: &AuditClient, event: Event) -> Result<()> { + let event_id = client.send_event(event.clone()).await?; + + // Small delay for propagation + tokio::time::sleep(Duration::from_millis(100)).await; + + let filter = Filter::new() + .id(event_id) + .limit(1); + + let results = client.query(filter).await?; + + if results.is_empty() { + return Err("Event was not stored".into()); + } + + Ok(()) +} +``` + +--- + +### `send_and_verify_rejected(client, event) -> Result<()>` +Sends event and verifies it was NOT stored. + +```rust +async fn send_and_verify_rejected(client: &AuditClient, event: Event) -> Result<()> { + let event_id = event.id; + + // Attempt to send + let _ = client.send_event(event).await; + + // Small delay for propagation + tokio::time::sleep(Duration::from_millis(100)).await; + + let filter = Filter::new() + .id(event_id) + .limit(1); + + let results = client.query(filter).await?; + + if !results.is_empty() { + return Err("Event was stored but should have been rejected".into()); + } + + Ok(()) +} +``` + +--- + +### `extract_d_tag(event) -> Result` +Extracts `d` tag value from event. + +```rust +fn extract_d_tag(event: &Event) -> Result { + event.tags + .iter() + .find(|t| t.kind() == TagKind::d()) + .and_then(|t| t.content()) + .ok_or("Missing d tag")? + .to_string() +} +``` + +--- + +## Module Structure + +```rust +//! GRASP-01 Event Relationship Smoke Tests +//! +//! Focused smoke tests validating basic event acceptance/rejection +//! based on tagging relationships with accepted repositories. + +use crate::{AuditClient, AuditResult, TestResult}; +use nostr_sdk::prelude::*; +use std::time::Duration; + +pub struct EventAcceptancePolicyTests; + +impl EventAcceptancePolicyTests { + pub async fn run_all(client: &AuditClient) -> AuditResult { + let mut results = AuditResult::new("GRASP-01 Event Acceptance Policy Tests"); + + // Group 1: Events tagging repos + results.add(Self::test_accept_issue_via_a_tag(client).await); + results.add(Self::test_accept_comment_via_A_tag(client).await); + results.add(Self::test_accept_kind1_via_q_tag(client).await); + + // Group 2: Events tagging accepted events + results.add(Self::test_accept_issue_quoting_issue_via_q(client).await); + results.add(Self::test_accept_comment_via_E_tag(client).await); + results.add(Self::test_accept_kind1_via_e_tag(client).await); + + // Group 3: Events tagged by accepted events + results.add(Self::test_accept_kind1_referenced_in_issue(client).await); + results.add(Self::test_accept_comment_referenced_in_comment(client).await); + results.add(Self::test_accept_kind1_referenced_in_kind1(client).await); + + // Group 4: Reject unrelated events + results.add(Self::test_reject_orphan_issue(client).await); + results.add(Self::test_reject_orphan_kind1(client).await); + results.add(Self::test_reject_comment_quoting_other_repo(client).await); + + results + } + + // Test implementations follow... +} + +// Helper functions follow... +``` + +--- + +## Integration with Test Suite + +Add to `grasp-audit/src/specs/grasp01/mod.rs`: + +```rust +pub mod event_acceptance_policy; + +pub use event_acceptance_policy::EventAcceptancePolicyTests; +``` + +Add to main test runner if desired, or run independently: + +```rust +// In grasp01_nostr_relay.rs or separate test file +#[tokio::test] +#[ignore] +async fn test_event_acceptance_policy_suite() { + let client = AuditClient::new_for_relay(&relay_url()).await.unwrap(); + let results = EventAcceptancePolicyTests::run_all(&client).await; + + // Assert all tests passed + assert!(results.all_passed(), "Some tests failed:\n{}", results); +} +``` + +--- + +## Implementation Notes + +1. **Simplicity First:** Keep test logic straightforward - setup, send, verify +2. **Independent Tests:** Each test should be runnable standalone +3. **Clear Failures:** Use descriptive error messages for debugging +4. **Minimal Helpers:** Only create helpers that reduce significant duplication +5. **Fast Execution:** Smoke tests should run quickly (use minimal delays) + +--- + +## Expected Outcomes + +When implemented, this suite should: + +- ✅ Run in under 5 seconds total +- ✅ Clearly show which relationship types work/fail +- ✅ Provide quick validation during development +- ✅ Act as regression tests for basic GRASP-01 compliance +- ✅ Be easy to understand and modify + +--- + +## Next Steps + +1. Create `grasp-audit/src/specs/grasp01/event-acceptance-policy.rs` +2. Implement helper functions (referencing nostr-sdk patterns) +3. Implement each test function following the specifications above +4. Add module declaration to `grasp01/mod.rs` +5. Run tests: `cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test` +6. Verify all tests pass or show expected "Not implemented yet" status + +--- + +## Success Criteria + +- [ ] All 12 tests compile without errors +- [ ] Tests run independently and as a suite +- [ ] Accept tests verify events ARE stored +- [ ] Reject tests verify events are NOT stored +- [ ] Helper functions eliminate code duplication +- [ ] Test output clearly indicates pass/fail/not-implemented \ No newline at end of file -- cgit v1.2.3