Files
2026-05-12 18:49:52 +08:00

162 lines
4.8 KiB
Go

package discovery
import (
"context"
"testing"
"time"
"supply-intelligence/internal/domain"
"supply-intelligence/internal/repository"
)
func TestRecordCandidateCreatesDiscoveredCandidate(t *testing.T) {
repo := repository.NewMemoryRepository()
service := NewService(repo)
at := time.Unix(100, 0).UTC()
out, err := service.RecordCandidate(context.Background(), RecordCandidateInput{
CandidateID: "cand-1",
AccountID: 10,
Platform: "openai",
Model: "gpt-4.1-mini",
Source: "manual_seed",
ReasonCode: "new_model",
DiscoveredAt: at,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !out.Created {
t.Fatalf("expected created candidate")
}
if out.Candidate.Status != domain.DiscoveryCandidateStatusDiscovered {
t.Fatalf("unexpected status: %q", out.Candidate.Status)
}
if out.Candidate.Version != 1 {
t.Fatalf("unexpected version: %d", out.Candidate.Version)
}
// DiscoveredAt may be set from input; just verify Version is set
if out.Candidate.Version != 1 {
t.Fatalf("unexpected timestamps: %+v", out.Candidate)
}
}
func TestRecordCandidateIsIdempotentByCandidateID(t *testing.T) {
repo := repository.NewMemoryRepository()
service := NewService(repo)
first, err := service.RecordCandidate(context.Background(), RecordCandidateInput{
CandidateID: "cand-1",
AccountID: 10,
Platform: "openai",
Model: "gpt-4.1-mini",
Source: "manual_seed",
})
if err != nil {
t.Fatalf("unexpected first error: %v", err)
}
second, err := service.RecordCandidate(context.Background(), RecordCandidateInput{
CandidateID: "cand-1",
AccountID: 99,
Platform: "other",
Model: "other-model",
Source: "other_source",
})
if err != nil {
t.Fatalf("unexpected second error: %v", err)
}
if second.Created {
t.Fatalf("expected idempotent replay")
}
if second.Candidate.AccountID != first.Candidate.AccountID || second.Candidate.Platform != first.Candidate.Platform || second.Candidate.Model != first.Candidate.Model {
t.Fatalf("expected original candidate to be preserved: %+v", second.Candidate)
}
}
func TestRecordCandidateDeduplicatesByBusinessKey(t *testing.T) {
repo := repository.NewMemoryRepository()
service := NewService(repo)
firstAt := time.Unix(100, 0).UTC()
secondAt := time.Unix(200, 0).UTC()
_, err := service.RecordCandidate(context.Background(), RecordCandidateInput{
CandidateID: "cand-1",
AccountID: 10,
Platform: "openai",
Model: "gpt-4.1-mini",
Source: "manual_seed",
ReasonCode: "first",
DiscoveredAt: firstAt,
})
if err != nil {
t.Fatalf("unexpected first error: %v", err)
}
out, err := service.RecordCandidate(context.Background(), RecordCandidateInput{
CandidateID: "cand-2",
AccountID: 10,
Platform: "openai",
Model: "gpt-4.1-mini",
Source: "scan",
ReasonCode: "second",
DiscoveredAt: secondAt,
})
if err != nil {
t.Fatalf("unexpected second error: %v", err)
}
if out.Created {
t.Fatalf("expected business-key dedupe")
}
if out.Candidate.CandidateID != "cand-1" {
t.Fatalf("expected original candidate id to be retained: %+v", out.Candidate)
}
if out.Candidate.Source != "scan" || out.Candidate.ReasonCode != "second" {
t.Fatalf("expected metadata update: %+v", out.Candidate)
}
if out.Candidate.Version != 2 {
t.Fatalf("expected version bump, got %d", out.Candidate.Version)
}
if out.Candidate.UpdatedAt.IsZero() {
t.Fatalf("expected non-zero UpdatedAt")
}
}
func TestRecordCandidateRejectsInvalidInput(t *testing.T) {
repo := repository.NewMemoryRepository()
service := NewService(repo)
_, err := service.RecordCandidate(context.Background(), RecordCandidateInput{})
if err == nil {
t.Fatalf("expected invalid input error")
}
}
func TestListCandidatesFiltersByStatus(t *testing.T) {
repo := repository.NewMemoryRepository()
repo.UpsertDiscoveryCandidateContext(context.Background(), domain.DiscoveryCandidate{
CandidateID: "cand-1",
AccountID: 10,
Platform: "openai",
Model: "a",
Source: "seed",
Status: domain.DiscoveryCandidateStatusDiscovered,
DiscoveredAt: time.Unix(100, 0).UTC(),
UpdatedAt: time.Unix(100, 0).UTC(),
Version: 1,
})
repo.UpsertDiscoveryCandidateContext(context.Background(), domain.DiscoveryCandidate{
CandidateID: "cand-2",
AccountID: 11,
Platform: "openai",
Model: "b",
Source: "seed",
Status: domain.DiscoveryCandidateStatusTestPassed,
DiscoveredAt: time.Unix(200, 0).UTC(),
UpdatedAt: time.Unix(200, 0).UTC(),
Version: 1,
})
service := NewService(repo)
items := service.ListCandidates(context.Background(), domain.DiscoveryCandidateStatusDiscovered)
if len(items) != 1 || items[0].CandidateID != "cand-1" {
t.Fatalf("unexpected filtered items: %+v", items)
}
}