package bidoutcomes import ( "context" "errors" "testing" "github.com/google/uuid" ) // TestRecordRejectsBadResult proves the service validates the result enum BEFORE any database work: a value // outside the allowed set (win, loss, no_bid, pending) is ErrInvalidResult, an empty result defaults to // pending and is accepted (it reaches the nil database guard, ErrNoDatabase, not the enum guard), and a // known good value is accepted the same way. Validation runs with a nil service db, so this needs no // Postgres. func TestRecordRejectsBadResult(t *testing.T) { svc := NewService(nil, nil) // nil db: validation runs first, then the ErrNoDatabase guard if _, err := svc.RecordBidOutcome(context.Background(), RecordInput{Result: "victory"}); !errors.Is(err, ErrInvalidResult) { t.Fatalf("a bad result enum must be ErrInvalidResult, got %v", err) } // Each allowed result (and the empty default) passes validation and falls through to ErrNoDatabase. for _, r := range []string{"", ResultPending, ResultWin, ResultLoss, ResultNoBid} { _, err := svc.RecordBidOutcome(context.Background(), RecordInput{Result: r}) if !errors.Is(err, ErrNoDatabase) { t.Fatalf("a valid result %q must pass validation and reach the db guard (ErrNoDatabase), got %v", r, err) } } } // TestRecordRejectsBadLinkID proves a non uuid link is rejected as ErrInvalidID before any database work, // so a malformed opportunity, requirement, or slate id is a clean 422 rather than a raw bind error. func TestRecordRejectsBadLinkID(t *testing.T) { svc := NewService(nil, nil) for name, in := range map[string]RecordInput{ "opportunity": {Result: ResultWin, OpportunityID: "not_a_uuid"}, "requirement": {Result: ResultWin, RequirementID: "not_a_uuid"}, "slate": {Result: ResultWin, SlateID: "not_a_uuid"}, "row": {Result: ResultWin, ID: "not_a_uuid"}, } { if _, err := svc.RecordBidOutcome(context.Background(), in); !errors.Is(err, ErrInvalidID) { t.Fatalf("a bad %s link must be ErrInvalidID, got %v", name, err) } } // A valid uuid link passes validation and reaches the db guard. if _, err := svc.RecordBidOutcome(context.Background(), RecordInput{Result: ResultWin, OpportunityID: uuid.NewString()}); !errors.Is(err, ErrNoDatabase) { t.Fatalf("a valid uuid link must pass validation, got %v", err) } } // TestUpdateRejectsBadResult proves the update path validates the result enum the same way. func TestUpdateRejectsBadResult(t *testing.T) { svc := NewService(nil, nil) if _, err := svc.UpdateBidOutcome(context.Background(), uuid.New(), UpdateInput{Result: "maybe"}); !errors.Is(err, ErrInvalidResult) { t.Fatalf("a bad result enum must be ErrInvalidResult, got %v", err) } if _, err := svc.UpdateBidOutcome(context.Background(), uuid.New(), UpdateInput{Result: ResultLoss}); !errors.Is(err, ErrNoDatabase) { t.Fatalf("a valid result must pass validation and reach the db guard, got %v", err) } } // TestIsValidResult pins the closed set of allowed result values. func TestIsValidResult(t *testing.T) { for _, r := range []string{ResultPending, ResultWin, ResultLoss, ResultNoBid} { if !IsValidResult(r) { t.Errorf("%q must be a valid result", r) } } for _, r := range []string{"", "Win", "WIN", "withdrawn", "pending "} { if IsValidResult(r) { t.Errorf("%q must NOT be a valid result", r) } } }