Open History to see the last commit
1import {
2 DEFAULT_SERVER_SETTINGS,
3 ProjectId,
4 ProviderDriverKind,
5 ProviderInstanceId,
6 SecretProviderId,
7 SecretProviderKind,
8 type ServerSettings,
9} from "@t3tools/contracts";
10import * as NodeServices from "@effect/platform-node/NodeServices";
11import { it as effectIt } from "@effect/vitest";
12import * as Effect from "effect/Effect";
13import * as Layer from "effect/Layer";
14import * as Queue from "effect/Queue";
15import * as Stream from "effect/Stream";
16import { describe, expect, it } from "vite-plus/test";
17
18import * as ServerConfig from "../config.ts";
19import { ServerSettingsService } from "../serverSettings.ts";
20import {
21 DEV_REPLAY_INSTANCE_ID,
22 deriveProviderInstanceConfigMap,
23 layerSettingsWatcher,
24 seedDevReplayInstance,
25} from "./ProviderInstanceRegistryHydration.ts";
26import { ProviderInstanceRegistryMutator } from "./ProviderInstanceRegistryMutator.ts";
27
28const projectId = ProjectId.make("project-a");
29const instanceId = ProviderInstanceId.make("codex_work");
30
31function settingsWith(overrides: Partial<ServerSettings>): ServerSettings {
32 return { ...DEFAULT_SERVER_SETTINGS, ...overrides };
33}
34
35describe("deriveProviderInstanceConfigMap", () => {
36 it("turns a machine-disabled instance on when a project enables it", () => {
37 const settings = settingsWith({
38 providerInstances: {
39 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: false },
40 },
41 projectSettingsOverrides: {
42 [projectId]: { providerInstances: { [instanceId]: { enabled: true } } },
43 },
44 });
45
46 const merged = deriveProviderInstanceConfigMap(settings);
47
48 expect(merged[instanceId]).toEqual({ driver: ProviderDriverKind.make("codex"), enabled: true });
49 });
50
51 it("clears a nested config.enabled:false flag when a project enables the instance", () => {
52 const settings = settingsWith({
53 providerInstances: {
54 [instanceId]: {
55 driver: ProviderDriverKind.make("codex"),
56 config: { enabled: false },
57 },
58 },
59 projectSettingsOverrides: {
60 [projectId]: { providerInstances: { [instanceId]: { enabled: true } } },
61 },
62 });
63
64 const merged = deriveProviderInstanceConfigMap(settings);
65
66 expect(merged[instanceId]).toEqual({
67 driver: ProviderDriverKind.make("codex"),
68 enabled: true,
69 config: { enabled: true },
70 });
71 });
72
73 it("does not revive an instance a project override references but settings no longer configure", () => {
74 const settings = settingsWith({
75 providerInstances: {},
76 projectSettingsOverrides: {
77 [projectId]: { providerInstances: { [instanceId]: { enabled: true } } },
78 },
79 });
80
81 const merged = deriveProviderInstanceConfigMap(settings);
82
83 expect(merged[instanceId]).toBeUndefined();
84 });
85
86 it("ignores a project override that disables an instance, leaving the machine's config in charge", () => {
87 const settings = settingsWith({
88 providerInstances: {
89 [instanceId]: { driver: ProviderDriverKind.make("codex"), enabled: true },
90 },
91 projectSettingsOverrides: {
92 [projectId]: { providerInstances: { [instanceId]: { enabled: false } } },
93 },
94 });
95
96 const merged = deriveProviderInstanceConfigMap(settings);
97
98 expect(merged[instanceId]).toBe(settings.providerInstances[instanceId]);
99 });
100});
101
102describe("seedDevReplayInstance", () => {
103 const seed = (devUrl: URL | undefined) =>
104 Effect.gen(function* () {
105 const config = yield* ServerConfig.ServerConfig;
106 yield* seedDevReplayInstance().pipe(
107 Effect.provideService(ServerConfig.ServerConfig, { ...config, devUrl }),
108 );
109 const settings = yield* ServerSettingsService;
110 return (yield* settings.getSettings).providerInstances[DEV_REPLAY_INSTANCE_ID];
111 }).pipe(
112 Effect.provide(
113 Layer.mergeAll(
114 ServerConfig.layerTest(process.cwd(), { prefix: "t3-dev-replay-seed-" }),
115 ServerSettingsService.layerTest({
116 providerInstances: {
117 [DEV_REPLAY_INSTANCE_ID]: {
118 driver: ProviderDriverKind.make("acpRegistry"),
119 displayName: "My replay",
120 enabled: false,
121 config: { source: "local", commandPath: "/old/node" },
122 },
123 },
124 }),
125 ).pipe(Layer.provideMerge(NodeServices.layer)),
126 ),
127 );
128
129 effectIt.effect("leaves settings alone outside dev", () =>
130 seed(undefined).pipe(
131 Effect.map((instance) =>
132 expect(instance?.config).toEqual({ source: "local", commandPath: "/old/node" }),
133 ),
134 ),
135 );
136
137 effectIt.effect("points a dev server at the replay agent and keeps user edits", () =>
138 seed(new URL("http://localhost:5733")).pipe(
139 Effect.map((instance) => {
140 expect(instance?.displayName).toBe("My replay");
141 expect(instance?.enabled).toBe(false);
142 expect(instance?.config).toMatchObject({
143 source: "local",
144 commandArgs: [expect.stringMatching(/scripts[/\\]dev-replay-agent\.ts$/)],
145 });
146 }),
147 ),
148 );
149});
150
151describe("layerSettingsWatcher", () => {
152 const provider = (label: string) => ({
153 [SecretProviderId.make("personal")]: {
154 kind: SecretProviderKind.make("one_password"),
155 label,
156 config: { account: "my" },
157 },
158 });
159
160 const watch = (initial: ServerSettings | undefined) =>
161 Effect.gen(function* () {
162 const changes = yield* Queue.unbounded<ServerSettings>();
163 const reconciled = yield* Queue.unbounded<void>();
164 let reloads = 0;
165 const mutator = Layer.succeed(ProviderInstanceRegistryMutator, {
166 reconcile: () => Queue.offer(reconciled, undefined),
167 reloadSecrets: () => Effect.sync(() => void reloads++),
168 applySecret: () => Effect.succeed([]),
169 });
170 yield* Layer.build(
171 layerSettingsWatcher(Stream.fromQueue(changes), initial).pipe(Layer.provide(mutator)),
172 );
173 return {
174 reloads: () => reloads,
175 // Changes run one at a time, so the repeat's reconcile means the first one's reload decision is done.
176 change: (settings: ServerSettings) =>
177 Queue.offerAll(changes, [settings, settings]).pipe(
178 Effect.andThen(Queue.take(reconciled)),
179 Effect.andThen(Queue.take(reconciled)),
180 ),
181 };
182 });
183
184 effectIt.effect(
185 "retries unreadable secrets when a secret provider changes, not when it is renamed",
186 () =>
187 Effect.gen(function* () {
188 const settings = yield* ServerSettingsService;
189 const { reloads, change } = yield* watch(yield* settings.getSettings);
190
191 yield* change(yield* settings.updateSettings({ secretProviders: provider("Personal") }));
192 expect(reloads()).toBe(1);
193
194 // A label is not read by any vault, so renaming a provider retries nothing.
195 yield* change(yield* settings.updateSettings({ secretProviders: provider("Renamed") }));
196 expect(reloads()).toBe(1);
197 }).pipe(Effect.scoped, Effect.provide(ServerSettingsService.layerTest())),
198 );
199
200 effectIt.effect(
201 "compares a change queued during startup with the settings the registry started from",
202 () =>
203 Effect.gen(function* () {
204 const settings = yield* ServerSettingsService;
205 const started = yield* settings.getSettings;
206 const saved = yield* settings.updateSettings({ secretProviders: provider("Personal") });
207 const { reloads, change } = yield* watch(started);
208
209 yield* change(saved);
210
211 expect(reloads()).toBe(1);
212 }).pipe(Effect.scoped, Effect.provide(ServerSettingsService.layerTest())),
213 );
214});