main.go raw

   1  package main
   2  
   3  import (
   4  	"context"
   5  	"flag"
   6  	"fmt"
   7  	"time"
   8  
   9  	"next.orly.dev/pkg/lol/chk"
  10  	"next.orly.dev/pkg/lol/log"
  11  	"next.orly.dev/pkg/nostr/encoders/event"
  12  	"next.orly.dev/pkg/nostr/encoders/filter"
  13  	"next.orly.dev/pkg/nostr/encoders/kind"
  14  	"next.orly.dev/pkg/nostr/encoders/tag"
  15  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  16  	"next.orly.dev/pkg/nostr/ws"
  17  )
  18  
  19  func main() {
  20  	var err error
  21  	url := flag.String("url", "ws://127.0.0.1:3334", "relay websocket URL")
  22  	timeout := flag.Duration("timeout", 20*time.Second, "operation timeout")
  23  	testType := flag.String("type", "event", "test type: 'event' for write control, 'req' for read control, 'both' for both, 'publish-and-query' for full test")
  24  	eventKind := flag.Int("kind", 4678, "event kind to test")
  25  	numEvents := flag.Int("count", 2, "number of events to publish (for publish-and-query)")
  26  	flag.Parse()
  27  
  28  	// Connect to relay
  29  	var rl *ws.Client
  30  	if rl, err = ws.RelayConnect(context.Background(), *url); chk.E(err) {
  31  		log.E.F("connect error: %v", err)
  32  		return
  33  	}
  34  	defer rl.Close()
  35  
  36  	// Create signer
  37  	var signer *p8k.Signer
  38  	if signer, err = p8k.New(); chk.E(err) {
  39  		log.E.F("signer create error: %v", err)
  40  		return
  41  	}
  42  	if err = signer.Generate(); chk.E(err) {
  43  		log.E.F("signer generate error: %v", err)
  44  		return
  45  	}
  46  
  47  	// Perform tests based on type
  48  	switch *testType {
  49  	case "event":
  50  		testEventWrite(rl, signer, *eventKind, *timeout)
  51  	case "req":
  52  		testReqRead(rl, signer, *eventKind, *timeout)
  53  	case "both":
  54  		log.I.Ln("Testing EVENT (write control)...")
  55  		testEventWrite(rl, signer, *eventKind, *timeout)
  56  		log.I.Ln("\nTesting REQ (read control)...")
  57  		testReqRead(rl, signer, *eventKind, *timeout)
  58  	case "publish-and-query":
  59  		testPublishAndQuery(rl, signer, *eventKind, *numEvents, *timeout)
  60  	default:
  61  		log.E.F("invalid test type: %s (must be 'event', 'req', 'both', or 'publish-and-query')", *testType)
  62  	}
  63  }
  64  
  65  func testEventWrite(rl *ws.Client, signer *p8k.Signer, eventKind int, timeout time.Duration) {
  66  	ev := &event.E{
  67  		CreatedAt: time.Now().Unix(),
  68  		Kind:      uint16(eventKind),
  69  		Tags:      tag.NewS(),
  70  		Content:   []byte("policy test: expect rejection for write"),
  71  	}
  72  	if err := ev.Sign(signer); chk.E(err) {
  73  		log.E.F("sign error: %v", err)
  74  		return
  75  	}
  76  
  77  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
  78  	defer cancel()
  79  
  80  	if err := rl.Publish(ctx, ev); err != nil {
  81  		// Expected path if policy rejects: client returns error with reason (from OK false)
  82  		fmt.Println("EVENT policy reject:", err)
  83  		return
  84  	}
  85  
  86  	log.I.Ln("EVENT publish result: accepted")
  87  	fmt.Println("EVENT ACCEPT")
  88  }
  89  
  90  func testReqRead(rl *ws.Client, signer *p8k.Signer, eventKind int, timeout time.Duration) {
  91  	// First, publish a test event to the relay that we'll try to query
  92  	testEvent := &event.E{
  93  		CreatedAt: time.Now().Unix(),
  94  		Kind:      uint16(eventKind),
  95  		Tags:      tag.NewS(),
  96  		Content:   []byte("policy test: event for read control test"),
  97  	}
  98  	if err := testEvent.Sign(signer); chk.E(err) {
  99  		log.E.F("sign error: %v", err)
 100  		return
 101  	}
 102  
 103  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 104  	defer cancel()
 105  
 106  	// Try to publish the test event first (ignore errors if policy rejects)
 107  	_ = rl.Publish(ctx, testEvent)
 108  	log.I.F("published test event kind %d for read testing", eventKind)
 109  
 110  	// Now try to query for events of this kind
 111  	limit := uint(10)
 112  	f := &filter.F{
 113  		Kinds: kind.FromIntSlice([]int{eventKind}),
 114  		Limit: &limit,
 115  	}
 116  
 117  	ctx2, cancel2 := context.WithTimeout(context.Background(), timeout)
 118  	defer cancel2()
 119  
 120  	events, err := rl.QuerySync(ctx2, f)
 121  	if chk.E(err) {
 122  		log.E.F("query error: %v", err)
 123  		fmt.Println("REQ query error:", err)
 124  		return
 125  	}
 126  
 127  	// Check if we got the expected events
 128  	if len(events) == 0 {
 129  		// Could mean policy filtered it out, or it wasn't stored
 130  		fmt.Println("REQ policy reject: no events returned (filtered by read policy)")
 131  		log.I.F("REQ result: no events of kind %d returned (policy filtered or not stored)", eventKind)
 132  		return
 133  	}
 134  
 135  	// Events were returned - read access allowed
 136  	fmt.Printf("REQ ACCEPT: %d events returned\n", len(events))
 137  	log.I.F("REQ result: %d events of kind %d returned", len(events), eventKind)
 138  }
 139  
 140  func testPublishAndQuery(rl *ws.Client, signer *p8k.Signer, eventKind int, numEvents int, timeout time.Duration) {
 141  	log.I.F("Publishing %d events of kind %d...", numEvents, eventKind)
 142  
 143  	publishedIDs := make([][]byte, 0, numEvents)
 144  	acceptedCount := 0
 145  	rejectedCount := 0
 146  
 147  	// Publish multiple events
 148  	for i := 0; i < numEvents; i++ {
 149  		ev := &event.E{
 150  			CreatedAt: time.Now().Unix() + int64(i), // Slightly different timestamps
 151  			Kind:      uint16(eventKind),
 152  			Tags:      tag.NewS(),
 153  			Content:   []byte(fmt.Sprintf("policy test event %d/%d", i+1, numEvents)),
 154  		}
 155  		if err := ev.Sign(signer); chk.E(err) {
 156  			log.E.F("sign error for event %d: %v", i+1, err)
 157  			continue
 158  		}
 159  
 160  		ctx, cancel := context.WithTimeout(context.Background(), timeout)
 161  		err := rl.Publish(ctx, ev)
 162  		cancel()
 163  
 164  		if err != nil {
 165  			log.W.F("Event %d/%d rejected: %v", i+1, numEvents, err)
 166  			rejectedCount++
 167  		} else {
 168  			log.I.F("Event %d/%d published successfully (id: %x...)", i+1, numEvents, ev.ID[:8])
 169  			publishedIDs = append(publishedIDs, ev.ID)
 170  			acceptedCount++
 171  		}
 172  	}
 173  
 174  	fmt.Printf("PUBLISH: %d accepted, %d rejected out of %d total\n", acceptedCount, rejectedCount, numEvents)
 175  
 176  	if acceptedCount == 0 {
 177  		fmt.Println("No events were accepted, skipping query test")
 178  		return
 179  	}
 180  
 181  	// Wait a moment for events to be stored
 182  	time.Sleep(500 * time.Millisecond)
 183  
 184  	// Now query for events of this kind
 185  	log.I.F("Querying for events of kind %d...", eventKind)
 186  
 187  	limit := uint(100)
 188  	f := &filter.F{
 189  		Kinds: kind.FromIntSlice([]int{eventKind}),
 190  		Limit: &limit,
 191  	}
 192  
 193  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 194  	defer cancel()
 195  
 196  	events, err := rl.QuerySync(ctx, f)
 197  	if chk.E(err) {
 198  		log.E.F("query error: %v", err)
 199  		fmt.Println("QUERY ERROR:", err)
 200  		return
 201  	}
 202  
 203  	log.I.F("Query returned %d events", len(events))
 204  
 205  	// Check if we got our published events back
 206  	foundCount := 0
 207  	for _, pubID := range publishedIDs {
 208  		found := false
 209  		for _, ev := range events {
 210  			if string(ev.ID) == string(pubID) {
 211  				found = true
 212  				break
 213  			}
 214  		}
 215  		if found {
 216  			foundCount++
 217  		}
 218  	}
 219  
 220  	fmt.Printf("QUERY: found %d/%d published events (total returned: %d)\n", foundCount, len(publishedIDs), len(events))
 221  
 222  	if foundCount == len(publishedIDs) {
 223  		fmt.Println("SUCCESS: All published events were retrieved")
 224  	} else if foundCount > 0 {
 225  		fmt.Printf("PARTIAL: Only %d/%d events retrieved (some filtered by read policy?)\n", foundCount, len(publishedIDs))
 226  	} else {
 227  		fmt.Println("FAILURE: None of the published events were retrieved (read policy blocked?)")
 228  	}
 229  }
 230