package sources import ( "strings" "testing" "time" "gitea.maximumdirect.net/ejr/feedkit/config" "gitea.maximumdirect.net/ejr/feedkit/event" ) type testInput struct { name string } func (s testInput) Name() string { return s.name } type testKindSource struct { testInput kind event.Kind } func (s testKindSource) Kind() event.Kind { return s.kind } type testKindsSource struct { testInput kinds []event.Kind } func (s testKindsSource) Kinds() []event.Kind { return s.kinds } func TestValidateExpectedKindsLegacyKindFallback(t *testing.T) { cfg := config.SourceConfig{Kind: "observation"} in := testKindSource{ testInput: testInput{name: "test"}, kind: event.Kind("observation"), } if err := ValidateExpectedKinds(cfg, in); err != nil { t.Fatalf("ValidateExpectedKinds() unexpected error: %v", err) } } func TestValidateExpectedKindsSubsetAllowed(t *testing.T) { cfg := config.SourceConfig{Kinds: []string{"observation"}} in := testKindsSource{ testInput: testInput{name: "test"}, kinds: []event.Kind{"observation", "forecast"}, } if err := ValidateExpectedKinds(cfg, in); err != nil { t.Fatalf("ValidateExpectedKinds() unexpected error: %v", err) } } func TestValidateExpectedKindsMismatchFails(t *testing.T) { cfg := config.SourceConfig{Kinds: []string{"alert"}} in := testKindsSource{ testInput: testInput{name: "test"}, kinds: []event.Kind{"observation", "forecast"}, } err := ValidateExpectedKinds(cfg, in) if err == nil { t.Fatalf("ValidateExpectedKinds() expected mismatch error, got nil") } if !strings.Contains(err.Error(), "configured expected kind") { t.Fatalf("ValidateExpectedKinds() error %q does not include expected message", err) } } func TestValidateExpectedKindsNoMetadataSkipsCheck(t *testing.T) { cfg := config.SourceConfig{Kinds: []string{"alert"}} in := testInput{name: "test"} if err := ValidateExpectedKinds(cfg, in); err != nil { t.Fatalf("ValidateExpectedKinds() unexpected error: %v", err) } } func TestDefaultEventIDUsesUpstreamID(t *testing.T) { emittedAt := time.Date(2026, 3, 28, 15, 4, 5, 123, time.UTC) got := DefaultEventID(" upstream-id ", "source", nil, emittedAt) if got != "upstream-id" { t.Fatalf("DefaultEventID() = %q, want upstream-id", got) } } func TestDefaultEventIDPrefersEffectiveAt(t *testing.T) { effectiveAt := time.Date(2026, 3, 28, 16, 4, 5, 987654321, time.FixedZone("x", -6*3600)) emittedAt := time.Date(2026, 3, 28, 15, 4, 5, 123, time.UTC) got := DefaultEventID("", "source", &effectiveAt, emittedAt) want := "source:" + effectiveAt.UTC().Format(time.RFC3339Nano) if got != want { t.Fatalf("DefaultEventID() = %q, want %q", got, want) } } func TestDefaultEventIDFallsBackToEmittedAt(t *testing.T) { emittedAt := time.Date(2026, 3, 28, 15, 4, 5, 123456789, time.FixedZone("y", 3*3600)) got := DefaultEventID("", "source", nil, emittedAt) want := "source:" + emittedAt.UTC().Format(time.RFC3339Nano) if got != want { t.Fatalf("DefaultEventID() = %q, want %q", got, want) } } func TestSingleEventBuildsValidatedSlice(t *testing.T) { effectiveAt := time.Date(2026, 3, 28, 16, 0, 0, 0, time.UTC) emittedAt := time.Date(2026, 3, 28, 15, 0, 0, 0, time.FixedZone("z", -5*3600)) got, err := SingleEvent( event.Kind("observation"), "source-a", "raw.example.v1", "evt-1", emittedAt, &effectiveAt, map[string]any{"ok": true}, ) if err != nil { t.Fatalf("SingleEvent() unexpected error: %v", err) } if len(got) != 1 { t.Fatalf("SingleEvent() len = %d, want 1", len(got)) } if got[0].EmittedAt != emittedAt.UTC() { t.Fatalf("SingleEvent() emittedAt = %s, want %s", got[0].EmittedAt, emittedAt.UTC()) } }