package tools import ( "testing" "time" "github.com/google/uuid" thoughttypes "git.warky.dev/wdevs/amcs/internal/types" ) func TestBuildDuplicateAuditGroupsProjectsAndThoughtsSafely(t *testing.T) { now := time.Date(2026, 4, 12, 20, 5, 0, 0, time.UTC) projects := []thoughttypes.ProjectSummary{ {Project: thoughttypes.Project{ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"), NumericID: 1, Name: "AMCS", CreatedAt: now}, ThoughtCount: 3}, {Project: thoughttypes.Project{ID: uuid.MustParse("22222222-2222-2222-2222-222222222222"), NumericID: 2, Name: "amcs ", CreatedAt: now.Add(time.Hour)}, ThoughtCount: 1}, {Project: thoughttypes.Project{ID: uuid.MustParse("33333333-3333-3333-3333-333333333333"), NumericID: 3, Name: "other", CreatedAt: now}, ThoughtCount: 1}, } thoughts := []thoughttypes.Thought{ {ID: 10, GUID: uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), Content: "Same text", Metadata: thoughttypes.ThoughtMetadata{Type: "Note", Topics: []string{"Go", "go"}, People: []string{"Alice"}}, CreatedAt: now}, {ID: 11, GUID: uuid.MustParse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), Content: " same text ", Metadata: thoughttypes.ThoughtMetadata{Type: "note", Topics: []string{"go"}, People: []string{"alice"}}, CreatedAt: now.Add(time.Hour)}, {ID: 12, GUID: uuid.MustParse("cccccccc-cccc-cccc-cccc-cccccccccccc"), Content: "unique", Metadata: thoughttypes.ThoughtMetadata{Type: "Task", Topics: []string{"Tasks"}}, CreatedAt: now}, } report := buildDuplicateAuditReport(projects, thoughts, DuplicateAuditInput{}) if len(report.ProjectCandidates) != 1 { t.Fatalf("project candidates = %d, want 1: %#v", len(report.ProjectCandidates), report.ProjectCandidates) } projectCandidate := report.ProjectCandidates[0] if projectCandidate.MatchType != "normalized_name" || projectCandidate.NormalizedValue != "amcs" || len(projectCandidate.Projects) != 2 { t.Fatalf("project candidate mismatch: %#v", projectCandidate) } if projectCandidate.RecommendedCanonicalID != projects[0].ID { t.Fatalf("recommended project = %s, want %s", projectCandidate.RecommendedCanonicalID, projects[0].ID) } if len(report.ThoughtCandidates) != 1 { t.Fatalf("thought candidates = %d, want 1: %#v", len(report.ThoughtCandidates), report.ThoughtCandidates) } thoughtCandidate := report.ThoughtCandidates[0] if thoughtCandidate.MatchType != "normalized_content" || thoughtCandidate.NormalizedValue != "same text" || len(thoughtCandidate.Thoughts) != 2 { t.Fatalf("thought candidate mismatch: %#v", thoughtCandidate) } if thoughtCandidate.RecommendedCanonicalID != thoughts[0].GUID { t.Fatalf("recommended thought = %s, want %s", thoughtCandidate.RecommendedCanonicalID, thoughts[0].GUID) } if len(report.MetadataCandidates) != 3 { t.Fatalf("metadata candidates = %d, want 3: %#v", len(report.MetadataCandidates), report.MetadataCandidates) } if report.Summary.ProjectCandidateGroups != 1 || report.Summary.ThoughtCandidateGroups != 1 || report.Summary.MetadataCandidateGroups != 3 { t.Fatalf("summary mismatch: %#v", report.Summary) } if report.Summary.DryRun != true { t.Fatalf("dry run = false, want true") } } func TestBuildDuplicateAuditRespectsLimit(t *testing.T) { projects := []thoughttypes.ProjectSummary{ {Project: thoughttypes.Project{ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"), Name: "Alpha"}}, {Project: thoughttypes.Project{ID: uuid.MustParse("22222222-2222-2222-2222-222222222222"), Name: "alpha"}}, {Project: thoughttypes.Project{ID: uuid.MustParse("33333333-3333-3333-3333-333333333333"), Name: "Beta"}}, {Project: thoughttypes.Project{ID: uuid.MustParse("44444444-4444-4444-4444-444444444444"), Name: "beta"}}, } report := buildDuplicateAuditReport(projects, nil, DuplicateAuditInput{Limit: 1}) if len(report.ProjectCandidates) != 1 { t.Fatalf("project candidates = %d, want 1", len(report.ProjectCandidates)) } if report.Summary.Truncated != true { t.Fatalf("truncated = false, want true") } }