package normalize import ( "context" "errors" "strings" "testing" "time" "gitea.maximumdirect.net/ejr/feedkit/event" ) func TestProcessorFirstMatchWins(t *testing.T) { var firstCalls, secondCalls int p := NewProcessor([]Normalizer{ Func{ MatchFn: func(event.Event) bool { return true }, NormalizeFn: func(_ context.Context, in event.Event) (*event.Event, error) { firstCalls++ out := in out.Schema = "normalized.first.v1" return &out, nil }, }, Func{ MatchFn: func(event.Event) bool { return true }, NormalizeFn: func(_ context.Context, in event.Event) (*event.Event, error) { secondCalls++ out := in out.Schema = "normalized.second.v1" return &out, nil }, }, }, false) out, err := p.Process(context.Background(), testEvent()) if err != nil { t.Fatalf("Process error: %v", err) } if out == nil { t.Fatalf("expected output event, got nil") } if out.Schema != "normalized.first.v1" { t.Fatalf("unexpected schema: %q", out.Schema) } if firstCalls != 1 { t.Fatalf("expected first normalizer called once, got %d", firstCalls) } if secondCalls != 0 { t.Fatalf("expected second normalizer skipped, got %d calls", secondCalls) } } func TestProcessorNoMatchPassThroughAndRequireMatch(t *testing.T) { in := testEvent() in.Schema = "raw.schema.v1" passThrough := NewProcessor([]Normalizer{ Func{ MatchFn: func(event.Event) bool { return false }, NormalizeFn: func(_ context.Context, in event.Event) (*event.Event, error) { out := in out.Schema = "should.not.run" return &out, nil }, }, }, false) out, err := passThrough.Process(context.Background(), in) if err != nil { t.Fatalf("pass-through Process error: %v", err) } if out == nil { t.Fatalf("expected pass-through output event, got nil") } if out.Schema != "raw.schema.v1" { t.Fatalf("expected unchanged schema, got %q", out.Schema) } required := NewProcessor(nil, true) _, err = required.Process(context.Background(), in) if err == nil { t.Fatalf("expected require-match error") } if !strings.Contains(err.Error(), "no normalizer matched") { t.Fatalf("unexpected error: %v", err) } } func TestProcessorDropAndErrorPropagation(t *testing.T) { t.Run("drop", func(t *testing.T) { p := NewProcessor([]Normalizer{ Func{ MatchFn: func(event.Event) bool { return true }, NormalizeFn: func(context.Context, event.Event) (*event.Event, error) { return nil, nil }, }, }, false) out, err := p.Process(context.Background(), testEvent()) if err != nil { t.Fatalf("Process error: %v", err) } if out != nil { t.Fatalf("expected nil output for dropped event, got %#v", out) } }) t.Run("error", func(t *testing.T) { p := NewProcessor([]Normalizer{ Func{ MatchFn: func(event.Event) bool { return true }, NormalizeFn: func(context.Context, event.Event) (*event.Event, error) { return nil, errors.New("map failed") }, }, }, false) _, err := p.Process(context.Background(), testEvent()) if err == nil { t.Fatalf("expected error") } if !strings.Contains(err.Error(), "normalizer failed") { t.Fatalf("unexpected error: %v", err) } }) } func testEvent() event.Event { return event.Event{ ID: "evt-normalize-1", Kind: event.Kind("observation"), Source: "source-1", EmittedAt: time.Now().UTC(), Payload: map[string]any{"x": 1}, } }