package task import ( "errors" "testing" ) type fakeLookup struct { tasks map[string]*Task } func newFakeLookup() *fakeLookup { return &fakeLookup{tasks: make(map[string]*Task)} } func (f *fakeLookup) GetTask(id string) (*Task, error) { t, ok := f.tasks[id] if !ok { return nil, errors.New("not found") } return t, nil } func (f *fakeLookup) ListDependents(taskID string) ([]*Task, error) { var out []*Task for _, t := range f.tasks { for _, d := range t.DependsOn { if d == taskID { out = append(out, t) break } } } return out, nil } func TestCurrentAttempt_NoFixAttempt_ReturnsAnchorItself(t *testing.T) { lookup := newFakeLookup() lookup.tasks["a"] = &Task{ID: "a"} got, err := CurrentAttempt(lookup, "a") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "a" { t.Errorf("got %q, want a", got.ID) } } func TestCurrentAttempt_OneFixAttempt_ResolvesToIt(t *testing.T) { lookup := newFakeLookup() lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} lookup.tasks["a-fix1"] = &Task{ID: "a-fix1", ParentTaskID: "parent-1", DependsOn: []string{"a"}} got, err := CurrentAttempt(lookup, "a") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "a-fix1" { t.Errorf("got %q, want a-fix1", got.ID) } } func TestCurrentAttempt_ChainOfFixAttempts_ResolvesToTip(t *testing.T) { lookup := newFakeLookup() lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} lookup.tasks["a-fix1"] = &Task{ID: "a-fix1", ParentTaskID: "parent-1", DependsOn: []string{"a"}} lookup.tasks["a-fix2"] = &Task{ID: "a-fix2", ParentTaskID: "parent-1", DependsOn: []string{"a-fix1"}} got, err := CurrentAttempt(lookup, "a") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "a-fix2" { t.Errorf("got %q, want a-fix2 (tip of the chain)", got.ID) } // Resolving from an intermediate link in the chain also lands on the tip. got, err = CurrentAttempt(lookup, "a-fix1") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "a-fix2" { t.Errorf("resolving from a-fix1: got %q, want a-fix2", got.ID) } } // TestCurrentAttempt_DependentWithDifferentParent_NotTreatedAsFixAttempt // proves a task that depends on "a" but has a DIFFERENT ParentTaskID (e.g. // an unrelated Evaluator task depending on a Builder for scheduling reasons) // must not be mistaken for a's fix attempt. func TestCurrentAttempt_DependentWithDifferentParent_NotTreatedAsFixAttempt(t *testing.T) { lookup := newFakeLookup() lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} lookup.tasks["unrelated"] = &Task{ID: "unrelated", ParentTaskID: "", DependsOn: []string{"a"}} got, err := CurrentAttempt(lookup, "a") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "a" { t.Errorf("got %q, want a (unrelated dependent must not be treated as a fix attempt)", got.ID) } } // TestCurrentAttempt_DependentWithMultipleDependsOn_NotTreatedAsFixAttempt // proves a fix attempt has exactly one DependsOn entry (the task it // supersedes); a task depending on "a" among several dependencies (e.g. an // Arbitration task depending on all 4 Evaluators) must not be mistaken for // a's fix attempt. func TestCurrentAttempt_DependentWithMultipleDependsOn_NotTreatedAsFixAttempt(t *testing.T) { lookup := newFakeLookup() lookup.tasks["a"] = &Task{ID: "a", ParentTaskID: "parent-1"} lookup.tasks["multi-dep"] = &Task{ID: "multi-dep", ParentTaskID: "parent-1", DependsOn: []string{"a", "b"}} got, err := CurrentAttempt(lookup, "a") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "a" { t.Errorf("got %q, want a", got.ID) } } // TestCurrentAttempt_DependentWithDifferentRole_NotTreatedAsFixAttempt proves // a top-level evaluator task depending on a top-level builder task (both with // ParentTaskID=="") is not mistaken for the builder's fix attempt, because its // role differs from the builder's. This is the story-orchestrator scenario: // builder (role="builder") has evaluator dependents (role="evaluator_quality" // etc.) and later a fix-attempt (role="builder") -- only the fix-attempt must // be resolved as "next", not the evaluators. func TestCurrentAttempt_DependentWithDifferentRole_NotTreatedAsFixAttempt(t *testing.T) { lookup := newFakeLookup() lookup.tasks["builder"] = &Task{ID: "builder", ParentTaskID: "", Agent: AgentConfig{Role: "builder"}} lookup.tasks["evaluator"] = &Task{ID: "evaluator", ParentTaskID: "", Agent: AgentConfig{Role: "evaluator_quality"}, DependsOn: []string{"builder"}} got, err := CurrentAttempt(lookup, "builder") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "builder" { t.Errorf("got %q, want builder (evaluator with different role must not be treated as a fix attempt)", got.ID) } } // TestCurrentAttempt_FixAttemptWithSameRole_ResolvedCorrectly proves that a // fix-attempt task with the same role as the anchor IS resolved as "next", // even when evaluators with different roles also depend on the anchor. func TestCurrentAttempt_FixAttemptWithSameRole_ResolvedCorrectly(t *testing.T) { lookup := newFakeLookup() lookup.tasks["builder"] = &Task{ID: "builder", ParentTaskID: "", Agent: AgentConfig{Role: "builder"}} lookup.tasks["evaluator"] = &Task{ID: "evaluator", ParentTaskID: "", Agent: AgentConfig{Role: "evaluator_quality"}, DependsOn: []string{"builder"}} lookup.tasks["fix1"] = &Task{ID: "fix1", ParentTaskID: "", Agent: AgentConfig{Role: "builder"}, DependsOn: []string{"builder"}} got, err := CurrentAttempt(lookup, "builder") if err != nil { t.Fatalf("CurrentAttempt: %v", err) } if got.ID != "fix1" { t.Errorf("got %q, want fix1 (same-role fix attempt must be resolved as current)", got.ID) } } func TestCurrentAttempt_UnknownAnchor_ReturnsError(t *testing.T) { lookup := newFakeLookup() if _, err := CurrentAttempt(lookup, "does-not-exist"); err == nil { t.Error("expected an error for an unknown anchor ID") } }