package module import ( "context" "reflect" "sort" "testing" "god-eye/internal/eventbus" ) // fakeModule is a minimal Module for tests. type fakeModule struct { name string phase Phase consumes []eventbus.EventType produces []eventbus.EventType defaultEnabled bool runCalled bool } func (f *fakeModule) Name() string { return f.name } func (f *fakeModule) Phase() Phase { return f.phase } func (f *fakeModule) Consumes() []eventbus.EventType { return f.consumes } func (f *fakeModule) Produces() []eventbus.EventType { return f.produces } func (f *fakeModule) DefaultEnabled() bool { return f.defaultEnabled } func (f *fakeModule) Run(mctx Context) error { f.runCalled = true; return nil } // fakeConfig implements ConfigView for tests. type fakeConfig struct { profile string enabled map[string]bool } func (c *fakeConfig) Profile() string { return c.profile } func (c *fakeConfig) Bool(k string, fb bool) bool { return fb } func (c *fakeConfig) Int(k string, fb int) int { return fb } func (c *fakeConfig) String(k, fb string) string { return fb } func (c *fakeConfig) Strings(k string) []string { return nil } func (c *fakeConfig) ModuleEnabled(name string) bool { return c.enabled[name] } func TestRegister_AndGet(t *testing.T) { r := NewRegistry() m := &fakeModule{name: "test.one", phase: PhaseDiscovery, defaultEnabled: true} r.Register(m) got, ok := r.Get("test.one") if !ok { t.Fatal("Get returned !ok for registered module") } if got != m { t.Error("Get returned a different instance") } if _, ok := r.Get("not.present"); ok { t.Error("Get returned ok for missing module") } } func TestRegister_DuplicatePanic(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "dup", phase: PhaseDiscovery}) defer func() { if recover() == nil { t.Error("expected panic on duplicate registration") } }() r.Register(&fakeModule{name: "dup", phase: PhaseDiscovery}) } func TestRegister_NilPanic(t *testing.T) { r := NewRegistry() defer func() { if recover() == nil { t.Error("expected panic on nil module") } }() r.Register(nil) } func TestRegister_EmptyNamePanic(t *testing.T) { r := NewRegistry() defer func() { if recover() == nil { t.Error("expected panic on empty name") } }() r.Register(&fakeModule{name: "", phase: PhaseDiscovery}) } func TestNames_InsertionOrder(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "zebra", phase: PhaseDiscovery}) r.Register(&fakeModule{name: "alpha", phase: PhaseDiscovery}) r.Register(&fakeModule{name: "middle", phase: PhaseDiscovery}) want := []string{"zebra", "alpha", "middle"} got := r.Names() if !reflect.DeepEqual(got, want) { t.Errorf("Names order = %v, want %v", got, want) } } func TestAll_ReturnsRegistered(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "a", phase: PhaseDiscovery}) r.Register(&fakeModule{name: "b", phase: PhaseAnalysis}) r.Register(&fakeModule{name: "c", phase: PhaseReporting}) if got := len(r.All()); got != 3 { t.Errorf("All length = %d, want 3", got) } } func TestByPhase_SortedByName(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "sources.zzz", phase: PhaseDiscovery}) r.Register(&fakeModule{name: "sources.aaa", phase: PhaseDiscovery}) r.Register(&fakeModule{name: "security.cors", phase: PhaseAnalysis}) r.Register(&fakeModule{name: "sources.mmm", phase: PhaseDiscovery}) got := r.ByPhase(PhaseDiscovery) names := make([]string, len(got)) for i, m := range got { names[i] = m.Name() } want := []string{"sources.aaa", "sources.mmm", "sources.zzz"} if !reflect.DeepEqual(names, want) { t.Errorf("ByPhase(discovery) = %v, want %v (sorted)", names, want) } if got := r.ByPhase(PhaseAnalysis); len(got) != 1 || got[0].Name() != "security.cors" { t.Errorf("ByPhase(analysis) unexpected: %v", got) } if got := r.ByPhase(PhaseReporting); len(got) != 0 { t.Errorf("ByPhase(reporting) should be empty, got %d", len(got)) } } func TestSelect_DefaultEnabled(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "on-by-default", phase: PhaseDiscovery, defaultEnabled: true}) r.Register(&fakeModule{name: "off-by-default", phase: PhaseDiscovery, defaultEnabled: false}) // nil config: module default governs got := r.Select(nil) names := moduleNames(got) sort.Strings(names) if !reflect.DeepEqual(names, []string{"on-by-default"}) { t.Errorf("Select(nil) = %v, want [on-by-default]", names) } } func TestSelect_ConfigEnablesOff(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "optin", phase: PhaseAnalysis, defaultEnabled: false}) r.Register(&fakeModule{name: "default-on", phase: PhaseAnalysis, defaultEnabled: true}) cfg := &fakeConfig{enabled: map[string]bool{"optin": true}} got := r.Select(cfg) names := moduleNames(got) sort.Strings(names) want := []string{"default-on", "optin"} if !reflect.DeepEqual(names, want) { t.Errorf("Select = %v, want %v", names, want) } } func TestProducersOf_AndConsumersOf(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{ name: "producer-a", phase: PhaseDiscovery, produces: []eventbus.EventType{eventbus.EventSubdomainDiscovered}, }) r.Register(&fakeModule{ name: "producer-b", phase: PhaseDiscovery, produces: []eventbus.EventType{eventbus.EventSubdomainDiscovered, eventbus.EventDNSResolved}, }) r.Register(&fakeModule{ name: "consumer", phase: PhaseEnrichment, consumes: []eventbus.EventType{eventbus.EventDNSResolved}, }) producers := r.ProducersOf(eventbus.EventSubdomainDiscovered) names := moduleNames(producers) sort.Strings(names) want := []string{"producer-a", "producer-b"} if !reflect.DeepEqual(names, want) { t.Errorf("ProducersOf = %v, want %v", names, want) } consumers := r.ConsumersOf(eventbus.EventDNSResolved) if len(consumers) != 1 || consumers[0].Name() != "consumer" { t.Errorf("ConsumersOf unexpected: %v", consumers) } } func TestReset(t *testing.T) { r := NewRegistry() r.Register(&fakeModule{name: "m1", phase: PhaseDiscovery, defaultEnabled: true}) r.Register(&fakeModule{name: "m2", phase: PhaseDiscovery, defaultEnabled: true}) if len(r.All()) != 2 { t.Fatal("pre-reset: expected 2 modules") } r.Reset() if len(r.All()) != 0 { t.Errorf("post-reset: expected 0 modules, got %d", len(r.All())) } // Re-register after reset works r.Register(&fakeModule{name: "m1", phase: PhaseDiscovery, defaultEnabled: true}) if len(r.All()) != 1 { t.Errorf("post-reset re-register: expected 1, got %d", len(r.All())) } } func TestDefault_Singleton(t *testing.T) { a := Default() b := Default() if a != b { t.Error("Default() returned different instances") } } func TestRunContextCarriesFields(t *testing.T) { // Sanity: Context struct is populated correctly — this is effectively a // struct-init contract test to catch accidental field removals. ctx := context.Background() bus := eventbus.New(16) defer bus.Close(context.Background()) mctx := Context{ Ctx: ctx, Bus: bus, Target: "example.com", Profile: "bugbounty", } if mctx.Target != "example.com" { t.Errorf("Target lost: %q", mctx.Target) } if mctx.Profile != "bugbounty" { t.Errorf("Profile lost: %q", mctx.Profile) } if mctx.Bus != bus { t.Error("Bus not retained") } } func moduleNames(ms []Module) []string { out := make([]string, len(ms)) for i, m := range ms { out[i] = m.Name() } return out }