package domain import "testing" // TestTransitionMatrixExhaustive enumerates every (from, to) pair and pins // the §4.4 machine exactly: the allowed set below is the complete truth. func TestTransitionMatrixExhaustive(t *testing.T) { allowed := map[TaskStatus]map[TaskStatus]bool{ StatusImported: {StatusAtomizing: true, StatusArchived: true}, StatusAtomizing: {StatusAtomized: true, StatusImported: true, StatusArchived: true}, StatusAtomized: {StatusPublished: true, StatusAtomizing: true, StatusArchived: true}, StatusPublished: {StatusClaimRequested: true, StatusAssigned: true, StatusArchived: true}, StatusClaimRequested: {StatusAssigned: true, StatusPublished: true, StatusArchived: true}, StatusAssigned: {StatusInProgress: true, StatusInReview: true, StatusPublished: true, StatusArchived: true}, StatusInProgress: {StatusInReview: true, StatusPublished: true, StatusArchived: true}, StatusInReview: {StatusApproved: true, StatusChangesRequested: true, StatusAssigned: true, StatusArchived: true}, StatusChangesRequested: {StatusInProgress: true, StatusArchived: true}, StatusApproved: {StatusArchived: true}, StatusArchived: {}, } for _, from := range AllStatuses { for _, to := range AllStatuses { want := allowed[from][to] if got := CanTransition(from, to); got != want { t.Errorf("CanTransition(%s, %s) = %v, want %v", from, to, got, want) } err := Transition(from, to) if want && err != nil { t.Errorf("Transition(%s, %s) unexpected error %v", from, to, err) } if !want && err == nil { t.Errorf("Transition(%s, %s) should error", from, to) } } } } func TestTransitionRejectsUnknownStatuses(t *testing.T) { if CanTransition("bogus", StatusArchived) { t.Error("unknown from-status must be rejected") } if CanTransition(StatusImported, "bogus") { t.Error("unknown to-status must be rejected") } if CanTransition(StatusImported, StatusImported) { t.Error("self-transition must be rejected") } } func TestStatusValid(t *testing.T) { for _, s := range AllStatuses { if !s.Valid() { t.Errorf("%s should be valid", s) } } if TaskStatus("nope").Valid() { t.Error("nope should be invalid") } } func TestComputeBounty(t *testing.T) { tests := []struct { coeff, budget, want float64 }{ {0.25, 1000, 250}, {0.2, 1000, 200}, {1.0 / 3.0, 1000, 333.33}, {0.333, 100, 33.3}, {0, 1000, 0}, {1, 0, 0}, {0.01, 99.99, 1.0}, // 0.9999 rounds to 1.00 {0.005, 100, 0.5}, {2.0, 500, 1000}, // extension coefficients may exceed 1 (§5.1) } for _, tt := range tests { if got := ComputeBounty(tt.coeff, tt.budget); got != tt.want { t.Errorf("ComputeBounty(%v, %v) = %v, want %v", tt.coeff, tt.budget, got, tt.want) } } } func TestTaskHelpers(t *testing.T) { task := &Task{ Assignee: &Assignee{Kind: "human", UserID: "dev1"}, ClaimRequests: []ClaimRequest{{DeveloperID: "dev2"}}, } if !task.IsAssignedTo("dev1") || task.IsAssignedTo("dev2") { t.Error("IsAssignedTo wrong") } if !task.HasClaimFrom("dev2") || task.HasClaimFrom("dev1") { t.Error("HasClaimFrom wrong") } aiTask := &Task{Assignee: &Assignee{Kind: "ai", JobID: "wp_1"}} if aiTask.IsAssignedTo("dev1") { t.Error("ai assignee must not match human checks") } }