返回 DeepSeek-Reasonix
order_test.go
根目录 / internal / plancontract / order_test.go
1 package plancontract
2
3 import (
4 "slices"
5 "testing"
6 )
7
8 func orderedTitles(p Plan) []string {
9 steps := p.Ordered()
10 out := make([]string, 0, len(steps))
11 for _, s := range steps {
12 out = append(out, s.Title)
13 }
14 return out
15 }
16
17 func TestOrderedGroupsSubStepsUnderTheirPhase(t *testing.T) {
18 p := Plan{Objective: "o", Steps: []Step{
19 {ID: "p1", Title: "phase one"},
20 {ID: "p2", Title: "phase two"},
21 {ID: "b", ParentID: "p2", Title: "two-a"},
22 {ID: "a", ParentID: "p1", Title: "one-a"},
23 }}.Normalize()
24
25 got := orderedTitles(p)
26 want := []string{"phase one", "one-a", "phase two", "two-a"}
27 if !slices.Equal(got, want) {
28 t.Fatalf("ordered = %v, want %v", got, want)
29 }
30 }
31
32 func TestOrderedRespectsSiblingDependencies(t *testing.T) {
33 p := Plan{Objective: "o", Steps: []Step{
34 {ID: "p", Title: "phase"},
35 {ID: "late", ParentID: "p", Title: "late", DependsOn: []string{"early"}},
36 {ID: "early", ParentID: "p", Title: "early"},
37 }}.Normalize()
38
39 got := orderedTitles(p)
40 want := []string{"phase", "early", "late"}
41 if !slices.Equal(got, want) {
42 t.Fatalf("ordered = %v, want %v", got, want)
43 }
44 }
45
46 func TestOrderedIgnoresDependenciesAcrossPhases(t *testing.T) {
47 // A sub-step of phase two depending on a sub-step of phase one cannot
48 // reorder anything: the phases already order them.
49 p := Plan{Objective: "o", Steps: []Step{
50 {ID: "p1", Title: "phase one"},
51 {ID: "a", ParentID: "p1", Title: "one-a", DependsOn: []string{"b"}},
52 {ID: "p2", Title: "phase two"},
53 {ID: "b", ParentID: "p2", Title: "two-a"},
54 }}.Normalize()
55
56 got := orderedTitles(p)
57 want := []string{"phase one", "one-a", "phase two", "two-a"}
58 if !slices.Equal(got, want) {
59 t.Fatalf("ordered = %v, want %v", got, want)
60 }
61 }
62
63 func TestOrderedKeepsEveryStepThroughADependencyCycle(t *testing.T) {
64 p := Plan{Objective: "o", Steps: []Step{
65 {ID: "a", Title: "a", DependsOn: []string{"b"}},
66 {ID: "b", Title: "b", DependsOn: []string{"a"}},
67 }}.Normalize()
68
69 got := orderedTitles(p)
70 want := []string{"a", "b"}
71 if !slices.Equal(got, want) {
72 t.Fatalf("ordered = %v, want declared order %v", got, want)
73 }
74 }
75
76 func TestOrderedKeepsEveryStepOnUnnormalizedInput(t *testing.T) {
77 // Ordered must be total: a caller that skips Normalize gets the same steps,
78 // grouped the same way, never a silently shorter list.
79 raw := Plan{Objective: "o", Steps: []Step{
80 {ID: "p", Title: "phase"},
81 {ID: "child", ParentID: "p", Title: "child"},
82 {ID: "grandchild", ParentID: "child", Title: "grandchild"},
83 {ID: "orphan", ParentID: "ghost", Title: "orphan"},
84 }}
85
86 got := orderedTitles(raw)
87 if len(got) != len(raw.Steps) {
88 t.Fatalf("ordered dropped steps: %v", got)
89 }
90 if !slices.Equal(got, orderedTitles(raw.Normalize())) {
91 t.Fatalf("ordered disagrees with the normalized plan: %v vs %v", got, orderedTitles(raw.Normalize()))
92 }
93 }
94
94 lines GO