package adapters import ( "context" "errors" "testing" oppintel "github.com/missionworkforce/missionmatch/internal/modules/opportunity_intelligence" ) // stubSource yields a fixed set of records, then returns its err (used to inject a throttle). type stubSource struct { name string recs []oppintel.OpportunityRecord err error } func (s *stubSource) Name() string { return s.name } func (s *stubSource) Fetch(_ context.Context, yield func(oppintel.OpportunityRecord) error) error { for _, r := range s.recs { if e := yield(r); e != nil { return e } } return s.err } func caRec(id string) oppintel.OpportunityRecord { return oppintel.OpportunityRecord{SourceRecordID: id, Kind: oppintel.KindContractAward} } func collect(t *testing.T, src oppintel.OpportunitySource) ([]string, error) { t.Helper() var got []string err := src.Fetch(context.Background(), func(r oppintel.OpportunityRecord) error { got = append(got, r.SourceRecordID) return nil }) return got, err } func TestFallbackUsesPrimaryWhenHealthy(t *testing.T) { primary := &stubSource{name: "SAM Contract Awards", recs: []oppintel.OpportunityRecord{caRec("a")}} fb := &stubSource{name: "USASpending", recs: []oppintel.OpportunityRecord{caRec("b")}} got, err := collect(t, NewFallbackAwardsSource(primary, fb)) if err != nil { t.Fatalf("err = %v", err) } if len(got) != 1 || got[0] != "a" { t.Fatalf("a healthy primary should be used alone, got %v", got) } } func TestFallbackSwitchesWhenPrimaryFailsBeforeYield(t *testing.T) { primary := &stubSource{name: "SAM Contract Awards", err: oppintel.ErrThrottled} fb := &stubSource{name: "USASpending", recs: []oppintel.OpportunityRecord{caRec("b")}} got, err := collect(t, NewFallbackAwardsSource(primary, fb)) if err != nil { t.Fatalf("a clean fallback should succeed, got %v", err) } if len(got) != 1 || got[0] != "b" { t.Fatalf("expected the fallback record, got %v", got) } } func TestFallbackDoesNotSwitchMidPull(t *testing.T) { primary := &stubSource{name: "SAM Contract Awards", recs: []oppintel.OpportunityRecord{caRec("a")}, err: oppintel.ErrThrottled} fb := &stubSource{name: "USASpending", recs: []oppintel.OpportunityRecord{caRec("b")}} got, err := collect(t, NewFallbackAwardsSource(primary, fb)) if !errors.Is(err, oppintel.ErrThrottled) { t.Fatalf("a throttle after the primary yielded should surface, got %v", err) } if len(got) != 1 || got[0] != "a" { t.Fatalf("once the primary yields, the run must not switch feeds, got %v", got) } } func TestFallbackName(t *testing.T) { src := NewFallbackAwardsSource(&stubSource{name: "SAM Contract Awards"}, &stubSource{name: "USASpending"}) if src.Name() != "SAM Contract Awards" { t.Fatalf("name should report the primary, got %q", src.Name()) } }