| 1 | package boot |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "testing" |
| 6 | |
| 7 | "reasonix/internal/extension/protocol" |
| 8 | "reasonix/internal/extension/providerext" |
| 9 | "reasonix/internal/extension/sidecar" |
| 10 | "reasonix/internal/provider" |
| 11 | ) |
| 12 | |
| 13 | type baseResolver struct{} |
| 14 | |
| 15 | func (baseResolver) Catalog() []provider.Descriptor { return nil } |
| 16 | func (baseResolver) Resolve(provider.Selection) (provider.Provider, error) { |
| 17 | return nil, fmt.Errorf("no model") |
| 18 | } |
| 19 | |
| 20 | // namedRouter is a distinct StreamRouter identity for before/after assertions. |
| 21 | type namedRouter struct{ name string } |
| 22 | |
| 23 | func (n *namedRouter) RouteStreamChunk(protocol.StreamChunkParams) {} |
| 24 | func (n *namedRouter) RouteStreamEnd(protocol.StreamEndParams) {} |
| 25 | |
| 26 | func TestMergeDoesNotInstallStreamRouterCommitDoes(t *testing.T) { |
| 27 | old := &namedRouter{name: "old-generation"} |
| 28 | client := sidecar.NewProbeClient("demo", []protocol.ProviderDescriptor{{ |
| 29 | Ref: "plugin/demo/fake/x", |
| 30 | Model: "x", |
| 31 | }}, old) |
| 32 | if client.StreamRouter() != old { |
| 33 | t.Fatal("precondition: client must start with old router") |
| 34 | } |
| 35 | |
| 36 | mgr := &sidecar.Manager{} |
| 37 | if err := mgr.Adopt("demo", client); err != nil { |
| 38 | t.Fatal(err) |
| 39 | } |
| 40 | |
| 41 | // Stage merge: construct resolver only — must not touch client router. |
| 42 | merged, err := mergeSidecarProviders(baseResolver{}, mgr, nil) |
| 43 | if err != nil { |
| 44 | t.Fatalf("mergeSidecarProviders: %v", err) |
| 45 | } |
| 46 | if merged == nil { |
| 47 | t.Fatal("expected merged resolver when client declares providers") |
| 48 | } |
| 49 | if got := client.StreamRouter(); got != old { |
| 50 | t.Fatalf("after merge router = %T, want old generation router", got) |
| 51 | } |
| 52 | |
| 53 | // Commit install: only then switch away from the old router. |
| 54 | installSidecarStreamRouters(mgr, merged) |
| 55 | got := client.StreamRouter() |
| 56 | if got == old { |
| 57 | t.Fatal("after commit install, router must not still be the old generation") |
| 58 | } |
| 59 | newRouter, ok := any(merged).(sidecar.StreamRouter) |
| 60 | if !ok { |
| 61 | t.Fatal("merged resolver must implement sidecar.StreamRouter") |
| 62 | } |
| 63 | if got != newRouter { |
| 64 | t.Fatalf("after commit router = %T, want merged providerext resolver", got) |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | func TestFailedStageLeavesOldRouterForAdoptedClient(t *testing.T) { |
| 69 | // Stage merge + fail before commit: router stays pre-stage. Real wire |
| 70 | // chunk/end after rollback: sidecar.TestRollbackAfterStageKeepsOldRouterConsumingRealStream. |
| 71 | old := &namedRouter{name: "old-gen"} |
| 72 | client := sidecar.NewProbeClient("keep", []protocol.ProviderDescriptor{{ |
| 73 | Ref: "plugin/keep/fake/x", Model: "x", |
| 74 | }}, old) |
| 75 | |
| 76 | prev := &sidecar.Manager{} |
| 77 | _ = prev.Adopt("keep", client) |
| 78 | |
| 79 | next := &sidecar.Manager{} |
| 80 | // Adopt unchanged (as StartPackagesWithPlan does). |
| 81 | if c := prev.Detach("keep"); c != nil { |
| 82 | _ = next.Adopt("keep", c) |
| 83 | // record as planAdopted via Rollback path: set by StartPackagesWithPlan; |
| 84 | // for unit test call merge on next then rollback without install. |
| 85 | } |
| 86 | if _, err := mergeSidecarProviders(baseResolver{}, next, nil); err != nil { |
| 87 | t.Fatal(err) |
| 88 | } |
| 89 | if client.StreamRouter() != old { |
| 90 | t.Fatal("stage merge must not rewrite adopted client router") |
| 91 | } |
| 92 | // Failure: reattach without ever calling installSidecarStreamRouters. |
| 93 | if c := next.Detach("keep"); c != nil { |
| 94 | _ = prev.Adopt("keep", c) |
| 95 | } |
| 96 | if prev.Client("keep") != client { |
| 97 | t.Fatal("client should be back on previous manager") |
| 98 | } |
| 99 | if client.StreamRouter() != old { |
| 100 | t.Fatal("after rollback, router must still be old generation") |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | func TestMergeSidecarProvidersDoesNotInstallRouter(t *testing.T) { |
| 105 | installSidecarStreamRouters(nil, nil) |
| 106 | installSidecarStreamRouters(&sidecar.Manager{}, nil) |
| 107 | installSidecarStreamRouters(nil, baseResolver{}) |
| 108 | } |
| 109 | |
| 110 | func TestInstallSidecarStreamRoutersAcceptsProviderext(t *testing.T) { |
| 111 | r, err := providerext.New(baseResolver{}, func() []providerext.ProviderClient { return nil }, nil) |
| 112 | if err != nil { |
| 113 | t.Fatal(err) |
| 114 | } |
| 115 | installSidecarStreamRouters(&sidecar.Manager{}, r) |
| 116 | if _, ok := any(r).(sidecar.StreamRouter); !ok { |
| 117 | t.Fatal("providerext.Resolver must implement sidecar.StreamRouter") |
| 118 | } |
| 119 | } |
| 120 |