返回 DeepSeek-Reasonix
integration_matrix_test.go
根目录 / internal / boot / integration_matrix_test.go
1 package boot
2
3 import (
4 "context"
5 "testing"
6
7 "reasonix/internal/extension"
8 "reasonix/internal/extension/sidecar"
9 "reasonix/internal/extensioncontract"
10 )
11
12 func TestIntegrationNoOpDoesNotBuildNewController(t *testing.T) {
13 isolateConfigHome(t)
14 oldRes, err := BuildRuntime(context.Background(), Options{})
15 if err != nil {
16 t.Fatal(err)
17 }
18 t.Cleanup(func() {
19 if oldRes.Controller != nil {
20 oldRes.Controller.Close()
21 }
22 })
23 res, err := RebuildFrom(context.Background(), oldRes, Options{})
24 if err != nil {
25 t.Fatal(err)
26 }
27 // True subgraph no-op reuses the controller pointer.
28 if !res.ReusedController {
29 t.Fatal("expected ReusedController on no-op rebuild")
30 }
31 if res.Controller != oldRes.Controller {
32 t.Fatal("no-op must keep the same controller pointer")
33 }
34 if res.Snapshot == nil || oldRes.Snapshot == nil {
35 t.Fatal("snapshots required")
36 }
37 if res.Snapshot.CacheHash() != oldRes.Snapshot.CacheHash() {
38 t.Fatalf("cache hash churned: %s -> %s", oldRes.Snapshot.CacheHash(), res.Snapshot.CacheHash())
39 }
40 }
41
42 func TestIntegrationDrainTimeoutFiresCancel(t *testing.T) {
43 g := extension.NewPublishGate()
44 g.Publish(1)
45 g.Publish(2)
46 fired := make(chan struct{}, 1)
47 g.RegisterDrainCancel(1, func() {
48 select {
49 case fired <- struct{}{}:
50 default:
51 }
52 })
53 g.ForceExpireDrain(1)
54 select {
55 case <-fired:
56 default:
57 t.Fatal("drain cancel not fired")
58 }
59 }
60
61 func TestIntegrationProviderDrainReloadOrder(t *testing.T) {
62 from, err := extension.BuildDependencyGraph([]extension.ComponentDescriptor{
63 {ID: "plugin/p", Provides: []extensioncontract.Capability{{
64 Key: extensioncontract.CapabilityKey{Namespace: "plugin/p", Kind: "provider", ID: "main"},
65 Version: "1.0.0", SchemaHash: "sha256:a",
66 }}},
67 })
68 if err != nil {
69 t.Fatal(err)
70 }
71 to, err := extension.BuildDependencyGraph([]extension.ComponentDescriptor{
72 {ID: "plugin/p", Provides: []extensioncontract.Capability{{
73 Key: extensioncontract.CapabilityKey{Namespace: "plugin/p", Kind: "provider", ID: "main"},
74 Version: "1.0.1", SchemaHash: "sha256:b",
75 }}},
76 })
77 if err != nil {
78 t.Fatal(err)
79 }
80 plan := extension.DiffRuntimePlan(from, to, 1, 2)
81 if plan.Kind != extension.SubgraphProviderOnly {
82 t.Fatalf("kind = %v", plan.Kind)
83 }
84 // Drain order must list the reloaded component (consumers drain after providers in reverse topo).
85 if len(plan.DrainOrder) == 0 && len(plan.Reloaded) == 0 {
86 t.Fatal("expected reloaded/drain order for provider change")
87 }
88 _ = sidecar.PluginComponentID("p")
89 }
90
91 func TestIntegrationSnapshotLiveRefreshPreservesCache(t *testing.T) {
92 // Product semantic for provider/MCP subgraph: live contributions refresh
93 // interceptor/provider catalog view; CacheHash (system+tools) stays.
94 snap := &extension.RuntimeSnapshot{}
95 // Build via empty WithGeneration path — use real build snapshot.
96 isolateConfigHome(t)
97 res, err := BuildRuntime(context.Background(), Options{})
98 if err != nil {
99 t.Fatal(err)
100 }
101 t.Cleanup(func() {
102 if res.Controller != nil {
103 res.Controller.Close()
104 }
105 })
106 if res.Snapshot == nil {
107 t.Fatal("nil snapshot")
108 }
109 baseHash := res.Snapshot.CacheHash()
110 next := res.Snapshot.WithLiveContributions(res.Snapshot.Generation()+1, nil)
111 if next.CacheHash() != baseHash {
112 t.Fatal("empty live refresh churned cache")
113 }
114 _ = snap
115 }
116
117 func TestIntegrationGoalAuthPreservedOnNoOp(t *testing.T) {
118 isolateConfigHome(t)
119 oldRes, err := BuildRuntime(context.Background(), Options{})
120 if err != nil {
121 t.Fatal(err)
122 }
123 t.Cleanup(func() {
124 if oldRes.Controller != nil {
125 oldRes.Controller.Close()
126 }
127 })
128 // Seed goal + approval mode on the live controller.
129 oldRes.Controller.SetToolApprovalMode("yolo")
130 oldRes.Controller.SetGoal("ship it")
131 auth := oldRes.Controller.SessionAuthorizations()
132 res, err := RebuildFrom(context.Background(), oldRes, Options{})
133 if err != nil {
134 t.Fatal(err)
135 }
136 if res.Controller.ToolApprovalMode() != "yolo" && res.Controller.ToolApprovalMode() != oldRes.Controller.ToolApprovalMode() {
137 // Reused controller keeps in-memory mode.
138 if !res.ReusedController {
139 t.Fatalf("approval mode = %q", res.Controller.ToolApprovalMode())
140 }
141 }
142 _ = auth
143 if res.ReusedController {
144 if res.Controller.Goal() != oldRes.Controller.Goal() {
145 t.Fatalf("goal drifted on reuse")
146 }
147 }
148 }
149
149 lines GO