package main import ( "context" "flag" "fmt" "time" "lol.mleku.dev/chk" "lol.mleku.dev/log" "git.mleku.dev/mleku/nostr/encoders/event" "git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/interfaces/signer/p8k" "git.mleku.dev/mleku/nostr/ws" ) func main() { var err error url := flag.String("url", "ws://127.0.0.1:3334", "relay websocket URL") timeout := flag.Duration("timeout", 20*time.Second, "operation timeout") testType := flag.String("type", "event", "test type: 'event' for write control, 'req' for read control, 'both' for both, 'publish-and-query' for full test") eventKind := flag.Int("kind", 4678, "event kind to test") numEvents := flag.Int("count", 2, "number of events to publish (for publish-and-query)") flag.Parse() // Connect to relay var rl *ws.Client if rl, err = ws.RelayConnect(context.Background(), *url); chk.E(err) { log.E.F("connect error: %v", err) return } defer rl.Close() // Create signer var signer *p8k.Signer if signer, err = p8k.New(); chk.E(err) { log.E.F("signer create error: %v", err) return } if err = signer.Generate(); chk.E(err) { log.E.F("signer generate error: %v", err) return } // Perform tests based on type switch *testType { case "event": testEventWrite(rl, signer, *eventKind, *timeout) case "req": testReqRead(rl, signer, *eventKind, *timeout) case "both": log.I.Ln("Testing EVENT (write control)...") testEventWrite(rl, signer, *eventKind, *timeout) log.I.Ln("\nTesting REQ (read control)...") testReqRead(rl, signer, *eventKind, *timeout) case "publish-and-query": testPublishAndQuery(rl, signer, *eventKind, *numEvents, *timeout) default: log.E.F("invalid test type: %s (must be 'event', 'req', 'both', or 'publish-and-query')", *testType) } } func testEventWrite(rl *ws.Client, signer *p8k.Signer, eventKind int, timeout time.Duration) { ev := &event.E{ CreatedAt: time.Now().Unix(), Kind: uint16(eventKind), Tags: tag.NewS(), Content: []byte("policy test: expect rejection for write"), } if err := ev.Sign(signer); chk.E(err) { log.E.F("sign error: %v", err) return } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() if err := rl.Publish(ctx, ev); err != nil { // Expected path if policy rejects: client returns error with reason (from OK false) fmt.Println("EVENT policy reject:", err) return } log.I.Ln("EVENT publish result: accepted") fmt.Println("EVENT ACCEPT") } func testReqRead(rl *ws.Client, signer *p8k.Signer, eventKind int, timeout time.Duration) { // First, publish a test event to the relay that we'll try to query testEvent := &event.E{ CreatedAt: time.Now().Unix(), Kind: uint16(eventKind), Tags: tag.NewS(), Content: []byte("policy test: event for read control test"), } if err := testEvent.Sign(signer); chk.E(err) { log.E.F("sign error: %v", err) return } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // Try to publish the test event first (ignore errors if policy rejects) _ = rl.Publish(ctx, testEvent) log.I.F("published test event kind %d for read testing", eventKind) // Now try to query for events of this kind limit := uint(10) f := &filter.F{ Kinds: kind.FromIntSlice([]int{eventKind}), Limit: &limit, } ctx2, cancel2 := context.WithTimeout(context.Background(), timeout) defer cancel2() events, err := rl.QuerySync(ctx2, f) if chk.E(err) { log.E.F("query error: %v", err) fmt.Println("REQ query error:", err) return } // Check if we got the expected events if len(events) == 0 { // Could mean policy filtered it out, or it wasn't stored fmt.Println("REQ policy reject: no events returned (filtered by read policy)") log.I.F("REQ result: no events of kind %d returned (policy filtered or not stored)", eventKind) return } // Events were returned - read access allowed fmt.Printf("REQ ACCEPT: %d events returned\n", len(events)) log.I.F("REQ result: %d events of kind %d returned", len(events), eventKind) } func testPublishAndQuery(rl *ws.Client, signer *p8k.Signer, eventKind int, numEvents int, timeout time.Duration) { log.I.F("Publishing %d events of kind %d...", numEvents, eventKind) publishedIDs := make([][]byte, 0, numEvents) acceptedCount := 0 rejectedCount := 0 // Publish multiple events for i := 0; i < numEvents; i++ { ev := &event.E{ CreatedAt: time.Now().Unix() + int64(i), // Slightly different timestamps Kind: uint16(eventKind), Tags: tag.NewS(), Content: []byte(fmt.Sprintf("policy test event %d/%d", i+1, numEvents)), } if err := ev.Sign(signer); chk.E(err) { log.E.F("sign error for event %d: %v", i+1, err) continue } ctx, cancel := context.WithTimeout(context.Background(), timeout) err := rl.Publish(ctx, ev) cancel() if err != nil { log.W.F("Event %d/%d rejected: %v", i+1, numEvents, err) rejectedCount++ } else { log.I.F("Event %d/%d published successfully (id: %x...)", i+1, numEvents, ev.ID[:8]) publishedIDs = append(publishedIDs, ev.ID) acceptedCount++ } } fmt.Printf("PUBLISH: %d accepted, %d rejected out of %d total\n", acceptedCount, rejectedCount, numEvents) if acceptedCount == 0 { fmt.Println("No events were accepted, skipping query test") return } // Wait a moment for events to be stored time.Sleep(500 * time.Millisecond) // Now query for events of this kind log.I.F("Querying for events of kind %d...", eventKind) limit := uint(100) f := &filter.F{ Kinds: kind.FromIntSlice([]int{eventKind}), Limit: &limit, } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() events, err := rl.QuerySync(ctx, f) if chk.E(err) { log.E.F("query error: %v", err) fmt.Println("QUERY ERROR:", err) return } log.I.F("Query returned %d events", len(events)) // Check if we got our published events back foundCount := 0 for _, pubID := range publishedIDs { found := false for _, ev := range events { if string(ev.ID) == string(pubID) { found = true break } } if found { foundCount++ } } fmt.Printf("QUERY: found %d/%d published events (total returned: %d)\n", foundCount, len(publishedIDs), len(events)) if foundCount == len(publishedIDs) { fmt.Println("SUCCESS: All published events were retrieved") } else if foundCount > 0 { fmt.Printf("PARTIAL: Only %d/%d events retrieved (some filtered by read policy?)\n", foundCount, len(publishedIDs)) } else { fmt.Println("FAILURE: None of the published events were retrieved (read policy blocked?)") } }