package roadmapsync import ( "errors" "testing" ) func TestIdentityNormalizeValid(t *testing.T) { in := Identity{ RoadmapMilestonePath: " agent-roadmap/phase/p/milestones/m.md ", Provider: " plane ", Tenant: " general ", Project: " NOMAD ", WorkItemID: " NOMAD-13 ", } got, err := in.Normalize() if err != nil { t.Fatalf("unexpected error: %v", err) } if got.Shape != ShapeMilestone { t.Errorf("shape = %q, want default milestone", got.Shape) } if got.RoadmapMilestonePath != "agent-roadmap/phase/p/milestones/m.md" { t.Errorf("path = %q", got.RoadmapMilestonePath) } if got.Provider != "plane" || got.WorkItemID != "NOMAD-13" { t.Errorf("provider/work item not trimmed: %+v", got) } } func TestIdentityNormalizeRejectsMissingFields(t *testing.T) { cases := map[string]Identity{ "missing provider": { RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md", WorkItemID: "NOMAD-13", }, "missing work item id": { RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md", Provider: "plane", }, "missing milestone path": { Provider: "plane", WorkItemID: "NOMAD-13", }, "task without parent": { Shape: ShapeTask, RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md", Provider: "plane", WorkItemID: "NOMAD-13", }, "unknown shape": { Shape: Shape("epic"), RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md", Provider: "plane", WorkItemID: "NOMAD-13", }, } for name, in := range cases { t.Run(name, func(t *testing.T) { if _, err := in.Normalize(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("err = %v, want ErrInvalidIdentity", err) } }) } } func TestIdentityMatches(t *testing.T) { base := Identity{ RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md", Provider: "plane", WorkItemID: "NOMAD-13", } same := base same.Project = "NOMAD" // non-key field differs, still a match if !base.Matches(same) { t.Errorf("expected match ignoring non-key fields") } differentWorkItem := base differentWorkItem.WorkItemID = "NOMAD-99" if base.Matches(differentWorkItem) { t.Errorf("different work item id must not match") } invalid := Identity{Provider: "plane"} if base.Matches(invalid) { t.Errorf("invalid identity must not match") } } func TestIdentityMatchesStrict(t *testing.T) { base := Identity{ RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md", Provider: "plane", Tenant: "general", Project: "NOMAD", WorkItemID: "NOMAD-13", } if !base.MatchesStrict(base) { t.Errorf("identical identity must strict-match") } differentTenant := base differentTenant.Tenant = "other-workspace" if base.MatchesStrict(differentTenant) { t.Errorf("different tenant must not strict-match") } differentProject := base differentProject.Project = "OTHER" if base.MatchesStrict(differentProject) { t.Errorf("different project must not strict-match") } // Matches (loose) ignores tenant/project; MatchesStrict must not. if !base.Matches(differentTenant) { t.Errorf("sanity: loose Matches should still ignore tenant") } } func TestRevisionNormalizeDefaultsDevelop(t *testing.T) { got := Revision{}.Normalize() if got.Branch != DefaultBranch { t.Errorf("branch = %q, want develop", got.Branch) } if !(Revision{Branch: "develop", Revision: "abc"}).OnDevelop() { t.Errorf("expected OnDevelop true") } if (Revision{Branch: "feature/x"}).OnDevelop() { t.Errorf("feature branch must not be OnDevelop") } }