返回 DeepSeek-Reasonix
runtimeplan_test.go
根目录 / internal / extension / runtimeplan_test.go
1 package extension
2
3 import (
4 "encoding/json"
5 "strings"
6 "testing"
7
8 "reasonix/internal/extensioncontract"
9 )
10
11 func TestRuntimePlanNoOp(t *testing.T) {
12 g, err := BuildDependencyGraph([]ComponentDescriptor{
13 {ID: "host", Provides: []extensioncontract.Capability{cap("reasonix", "provider", "p", "1.0.0", "sha256:p")}},
14 })
15 if err != nil {
16 t.Fatal(err)
17 }
18 plan := DiffRuntimePlan(g, g, 1, 2)
19 if !plan.IsNoOp() || plan.PrefixChanged || plan.ProviderChanged {
20 t.Fatalf("plan = %+v", plan)
21 }
22 if plan.MayChangePrefix() {
23 t.Fatal("no-op plan must not predict a prefix change")
24 }
25 if len(plan.Unchanged) != 1 || plan.Unchanged[0] != "host" {
26 t.Fatalf("unchanged = %v", plan.Unchanged)
27 }
28 }
29
30 func TestRuntimePlanRestartUnchangedSidecarsAffectsOnlySidecars(t *testing.T) {
31 plan := &RuntimePlan{RestartUnchangedSidecars: true}
32 if !plan.IsNoOp() {
33 t.Fatal("sidecar process replacement must remain a semantic graph no-op")
34 }
35 if !plan.AffectsSidecars() {
36 t.Fatal("sidecar process replacement must report its lifecycle work")
37 }
38 if plan.MayChangePrefix() || plan.AffectsInterceptors() || plan.AffectsUI() || plan.AffectsProviders() {
39 t.Fatalf("sidecar process replacement affected unrelated subgraphs: %+v", plan)
40 }
41 }
42
43 func TestRuntimePlanProviderOnlyChange(t *testing.T) {
44 from, err := BuildDependencyGraph([]ComponentDescriptor{
45 {ID: "host", Provides: []extensioncontract.Capability{cap("reasonix", "provider", "p", "1.0.0", "sha256:a")}},
46 {ID: "consumer", Requires: []extensioncontract.Requirement{req("reasonix", "provider", "p", ">=1.0.0", false)}},
47 })
48 if err != nil {
49 t.Fatal(err)
50 }
51 to, err := BuildDependencyGraph([]ComponentDescriptor{
52 {ID: "host", Provides: []extensioncontract.Capability{cap("reasonix", "provider", "p", "1.1.0", "sha256:b")}},
53 {ID: "consumer", Requires: []extensioncontract.Requirement{req("reasonix", "provider", "p", ">=1.0.0", false)}},
54 })
55 if err != nil {
56 t.Fatal(err)
57 }
58 plan := DiffRuntimePlan(from, to, 1, 2)
59 if plan.IsNoOp() {
60 t.Fatal("expected reload")
61 }
62 if plan.PrefixChanged {
63 t.Fatal("graph diff must not report an observed prefix change")
64 }
65 if !plan.ProviderChanged {
66 t.Fatal("provider-only change must set ProviderChanged")
67 }
68 if !plan.MayChangePrefix() {
69 t.Fatal("provider-only change should conservatively rebuild/cache-check the snapshot")
70 }
71 // Host identity changed; consumer epoch changed → both reloaded.
72 reloaded := map[ComponentID]bool{}
73 for _, id := range plan.Reloaded {
74 reloaded[id] = true
75 }
76 if !reloaded["host"] || !reloaded["consumer"] {
77 t.Fatalf("reloaded = %v", plan.Reloaded)
78 }
79 }
80
81 func TestRuntimePlanRemovedProviderDetected(t *testing.T) {
82 from, err := BuildDependencyGraph([]ComponentDescriptor{
83 {ID: "plugin/p", Provides: []extensioncontract.Capability{cap("plugin/p", "provider", "x", "1.0.0", "sha256:a")}},
84 })
85 if err != nil {
86 t.Fatal(err)
87 }
88 to, err := BuildDependencyGraph(nil)
89 if err != nil {
90 t.Fatal(err)
91 }
92 plan := DiffRuntimePlan(from, to, 1, 2)
93 if plan.Kind != SubgraphProviderOnly {
94 t.Fatalf("kind = %v, want provider-only", plan.Kind)
95 }
96 if !plan.ProviderChanged {
97 t.Fatal("removed provider must set ProviderChanged")
98 }
99 if plan.PrefixChanged {
100 t.Fatal("graph diff must not invent an observed prefix change")
101 }
102 }
103
104 func TestRuntimePlanMCPSchemaChangeRequiresFullRebuild(t *testing.T) {
105 from, err := BuildDependencyGraph([]ComponentDescriptor{
106 {ID: "plugin/m", Provides: []extensioncontract.Capability{cap("plugin/m", "mcp", "server", "1.0.0", "sha256:a")}},
107 })
108 if err != nil {
109 t.Fatal(err)
110 }
111 to, err := BuildDependencyGraph([]ComponentDescriptor{
112 {ID: "plugin/m", Provides: []extensioncontract.Capability{cap("plugin/m", "mcp", "server", "1.0.0", "sha256:b")}},
113 })
114 if err != nil {
115 t.Fatal(err)
116 }
117 plan := DiffRuntimePlan(from, to, 1, 2)
118 if plan.Kind != SubgraphFull {
119 t.Fatalf("kind = %v, want full rebuild for MCP schema change", plan.Kind)
120 }
121 if plan.ProviderChanged {
122 t.Fatal("MCP-only change must not set ProviderChanged")
123 }
124 }
125
126 func TestRuntimePlanMCPBackendChangeKeepsNarrowPlan(t *testing.T) {
127 from, err := BuildDependencyGraph([]ComponentDescriptor{
128 {
129 ID: "plugin/m",
130 Source: ContributionSource{Scope: ScopePlugin, PluginID: "m", Version: "1.0.0"},
131 Provides: []extensioncontract.Capability{cap("plugin/m", "mcp", "server", "1.0.0", "sha256:stable")},
132 },
133 })
134 if err != nil {
135 t.Fatal(err)
136 }
137 to, err := BuildDependencyGraph([]ComponentDescriptor{
138 {
139 ID: "plugin/m",
140 Source: ContributionSource{Scope: ScopePlugin, PluginID: "m", Version: "1.0.1"},
141 Provides: []extensioncontract.Capability{cap("plugin/m", "mcp", "server", "1.0.0", "sha256:stable")},
142 },
143 })
144 if err != nil {
145 t.Fatal(err)
146 }
147 plan := DiffRuntimePlan(from, to, 1, 2)
148 if plan.Kind != SubgraphMCPOnly {
149 t.Fatalf("kind = %v, want MCP-only", plan.Kind)
150 }
151 if plan.PrefixChanged || plan.ProviderChanged {
152 t.Fatalf("observed flags must remain false before snapshot comparison: %+v", plan)
153 }
154 }
155
156 func TestRuntimePlanViewUsesObservedDiagnosticFields(t *testing.T) {
157 raw, err := json.Marshal(PlanView(&RuntimePlan{PrefixChanged: true, ProviderChanged: true}))
158 if err != nil {
159 t.Fatal(err)
160 }
161 text := string(raw)
162 for _, field := range []string{`"prefixChanged":true`, `"providerChanged":true`} {
163 if !strings.Contains(text, field) {
164 t.Fatalf("plan JSON %s missing %s", text, field)
165 }
166 }
167 if strings.Contains(text, "cacheChanged") {
168 t.Fatalf("plan JSON retains ambiguous cacheChanged field: %s", text)
169 }
170 }
171
172 func TestRuntimePlanAddedRemoved(t *testing.T) {
173 from, _ := BuildDependencyGraph([]ComponentDescriptor{{ID: "a"}})
174 to, _ := BuildDependencyGraph([]ComponentDescriptor{{ID: "b"}})
175 plan := DiffRuntimePlan(from, to, 1, 2)
176 if len(plan.Added) != 1 || plan.Added[0] != "b" {
177 t.Fatalf("added = %v", plan.Added)
178 }
179 if len(plan.Removed) != 1 || plan.Removed[0] != "a" {
180 t.Fatalf("removed = %v", plan.Removed)
181 }
182 }
183
183 lines GO