package federation import ( "context" "sync" "testing" "fiatjaf.com/nostr" ) func TestParseConfig(t *testing.T) { t.Run("empty spec disables federation", func(t *testing.T) { cfg, warnings := ParseConfig("") if cfg.Enabled() { t.Fatal("expected federation disabled for empty spec") } if len(warnings) != 0 { t.Fatalf("unexpected warnings: %v", warnings) } }) t.Run("bare url defaults to mirror all channels", func(t *testing.T) { cfg, _ := ParseConfig("wss://a.example") if len(cfg.Peers) != 1 { t.Fatalf("expected 1 peer, got %d", len(cfg.Peers)) } p := cfg.Peers[0] if p.Mode != ModeMirror { t.Errorf("expected mirror mode, got %s", p.Mode) } if !p.AllChans { t.Error("expected AllChans=true for bare url") } if !p.wants("anything") { t.Error("bare url should federate every channel") } }) t.Run("explicit mode and channels", func(t *testing.T) { cfg, _ := ParseConfig("wss://a.example|ingest:general,dev") p := cfg.Peers[0] if p.Mode != ModeIngest { t.Errorf("expected ingest, got %s", p.Mode) } if p.AllChans { t.Error("expected scoped channels, got AllChans") } if !p.wants("general") || !p.wants("dev") { t.Error("should federate listed channels") } if p.wants("secret") { t.Error("should NOT federate unlisted channel") } }) t.Run("multiple peers separated by semicolons", func(t *testing.T) { cfg, _ := ParseConfig("wss://a.example|mirror:general; wss://b.example|ingest:*") if len(cfg.Peers) != 2 { t.Fatalf("expected 2 peers, got %d", len(cfg.Peers)) } if cfg.Peers[0].Mode != ModeMirror || cfg.Peers[1].Mode != ModeIngest { t.Error("modes parsed incorrectly") } if !cfg.Peers[1].AllChans { t.Error("ingest:* should mean all channels") } }) t.Run("channel names tolerate a leading #", func(t *testing.T) { cfg, _ := ParseConfig("wss://a.example|mirror:#general") if !cfg.Peers[0].wants("general") { t.Error("should strip leading # from channel names") } }) t.Run("invalid url is skipped with a warning", func(t *testing.T) { cfg, warnings := ParseConfig("http://not-websocket.example") if cfg.Enabled() { t.Error("non-ws url should be skipped") } if len(warnings) == 0 { t.Error("expected a warning for invalid url") } }) t.Run("explicit but empty channel list is skipped", func(t *testing.T) { cfg, warnings := ParseConfig("wss://a.example|mirror:,") if cfg.Enabled() { t.Error("empty channel list should skip the peer") } if len(warnings) == 0 { t.Error("expected a warning for empty channel list") } }) t.Run("unknown mode falls back to mirror with a warning", func(t *testing.T) { cfg, warnings := ParseConfig("wss://a.example|bogus:general") if cfg.Peers[0].Mode != ModeMirror { t.Error("unknown mode should fall back to mirror") } if len(warnings) == 0 { t.Error("expected a warning for unknown mode") } }) } func TestFederatesChannel(t *testing.T) { cfg, _ := ParseConfig("wss://a.example|mirror:general;wss://b.example|ingest:dev") if !cfg.FederatesChannel("general") { t.Error("general federates via peer a") } if !cfg.FederatesChannel("dev") { t.Error("dev federates via peer b") } if cfg.FederatesChannel("private") { t.Error("private federates with nobody") } } // TestReceiveLoopPrevention proves the loop guard: an event injected twice is // only stored (and thus only forwardable) once, because the injector reports the // second sight as not-newly-stored. This mirrors AddEvent's ErrDupEvent path. func TestReceiveLoopPrevention(t *testing.T) { seen := map[nostr.ID]bool{} var mu sync.Mutex injectCount := 0 storedCount := 0 inject := func(_ context.Context, evt nostr.Event) bool { mu.Lock() defer mu.Unlock() injectCount++ if seen[evt.ID] { return false // duplicate: not newly stored (the loop breaker) } seen[evt.ID] = true storedCount++ return true } cfg, _ := ParseConfig("wss://peer.example|mirror:general") f := NewFederator(cfg, inject) peer := cfg.Peers[0] sk := nostr.Generate() evt := nostr.Event{ Kind: nostr.KindSimpleGroupChatMessage, CreatedAt: nostr.Now(), Content: "hello federation", Tags: nostr.Tags{{"h", "general"}}, } if err := evt.Sign(sk); err != nil { t.Fatalf("sign: %v", err) } // Same event received twice (e.g. A→B then echoed B→A→B). f.receive(peer, evt) f.receive(peer, evt) if injectCount != 2 { t.Errorf("expected 2 inject attempts, got %d", injectCount) } if storedCount != 1 { t.Errorf("loop guard failed: event stored %d times, want 1", storedCount) } } // TestReceiveDropsBadSignature ensures a peer can't inject forged events. func TestReceiveDropsBadSignature(t *testing.T) { injected := false inject := func(_ context.Context, _ nostr.Event) bool { injected = true return true } cfg, _ := ParseConfig("wss://peer.example|mirror:general") f := NewFederator(cfg, inject) // Unsigned (invalid) event tagged for a federated channel. evt := nostr.Event{ Kind: nostr.KindSimpleGroupChatMessage, CreatedAt: nostr.Now(), Content: "forged", Tags: nostr.Tags{{"h", "general"}}, } f.receive(cfg.Peers[0], evt) if injected { t.Error("event with an invalid signature must not be injected") } } // TestReceiveIgnoresUnfederatedChannel ensures scope is enforced on ingress. func TestReceiveIgnoresUnfederatedChannel(t *testing.T) { injected := false inject := func(_ context.Context, _ nostr.Event) bool { injected = true return true } cfg, _ := ParseConfig("wss://peer.example|mirror:general") f := NewFederator(cfg, inject) sk := nostr.Generate() evt := nostr.Event{ Kind: nostr.KindSimpleGroupChatMessage, CreatedAt: nostr.Now(), Content: "off-topic", Tags: nostr.Tags{{"h", "other-channel"}}, } if err := evt.Sign(sk); err != nil { t.Fatalf("sign: %v", err) } f.receive(cfg.Peers[0], evt) if injected { t.Error("chat for an unfederated channel must not be injected") } }