1import * as Schema from "effect/Schema";
2import { describe, expect, it } from "vite-plus/test";
3
4import { ExecutionEnvironmentDescriptor } from "./environment.ts";
5import {
6 applyServerProviderDeltas,
7 OtlpEndpointCheckInput,
8 resolveEnvironmentMachineKind,
9 ServerConfigProviderStatusDeltasPayload,
10 ServerObservability,
11 ServerProvider,
12 serverProviderDeltas,
13 ServerProviders,
14} from "./server.ts";
15import { ServerSettings } from "./settings.ts";
16
17const decodeServerProvider = Schema.decodeUnknownSync(ServerProvider);
18const decodeServerProviders = Schema.decodeUnknownSync(ServerProviders);
19
20const baseProviderSnapshot = {
21 instanceId: "codex",
22 driver: "codex",
23 enabled: true,
24 installed: true,
25 version: "1.0.0",
26 status: "ready",
27 auth: { status: "authenticated" },
28 checkedAt: "2026-04-10T00:00:00.000Z",
29 models: [],
30};
31
32describe("ServerProvider", () => {
33 it("preserves installed-version presence through the provider wire codec", () => {
34 const encode = Schema.encodeSync(ServerProvider);
35 for (const installation of [{}, { installedVersion: null }, { installedVersion: "1.2.3" }]) {
36 const decoded = decodeServerProvider({ ...baseProviderSnapshot, ...installation });
37 const wire = JSON.parse(JSON.stringify(encode(decoded)));
38 expect(wire).toMatchObject({ version: "1.0.0", ...installation });
39 expect(Object.hasOwn(wire, "installedVersion")).toBe(
40 Object.hasOwn(installation, "installedVersion"),
41 );
42 expect(decodeServerProvider(wire)).toEqual(decoded);
43 }
44 });
45});
46
47describe("provider status deltas", () => {
48 it("send a provider's lists only when they change and rebuild the full list", () => {
49 const codex = decodeServerProvider({
50 ...baseProviderSnapshot,
51 models: [{ slug: "gpt", name: "GPT", isCustom: false, capabilities: null }],
52 slashCommands: [{ name: "compact" }],
53 skills: [{ name: "review", path: "/skills/review", enabled: true }],
54 });
55 const claude = decodeServerProvider({ ...baseProviderSnapshot, instanceId: "claudeAgent" });
56 const previous = [codex];
57 const next = [
58 claude,
59 { ...codex, checkedAt: "2026-04-10T00:05:00.000Z", skills: [], message: "Checked" },
60 ];
61
62 const deltas = serverProviderDeltas(previous, next);
63 // Unchanged lists stay home; a provider the reader never saw travels whole.
64 expect(deltas[0]).toEqual(claude);
65 expect(deltas[1]).not.toHaveProperty("models");
66 expect(deltas[1]).not.toHaveProperty("slashCommands");
67 expect(deltas[1]).toMatchObject({ skills: [], message: "Checked" });
68
69 const codec = Schema.toCodecJson(ServerConfigProviderStatusDeltasPayload);
70 const wire = JSON.parse(JSON.stringify(Schema.encodeSync(codec)({ providers: deltas })));
71 const received = Schema.decodeUnknownSync(codec)(wire).providers;
72 // A decoding default would turn the absent slash commands into an empty list.
73 expect(received[1]).not.toHaveProperty("slashCommands");
74 expect(applyServerProviderDeltas(previous, received)).toEqual(next);
75 // Without the provider the delta builds on, its lists are unknown.
76 expect(applyServerProviderDeltas([], received)).toEqual([claude]);
77 });
78});
79
80describe("server config forward compatibility", () => {
81 it("drops providers this build cannot decode instead of failing the whole array", () => {
82 const decodedBase = decodeServerProvider(baseProviderSnapshot);
83
84 const parsed = decodeServerProviders([
85 baseProviderSnapshot,
86 { ...baseProviderSnapshot, instanceId: "future", status: "some-future-status" },
87 ]);
88
89 expect(parsed).toEqual([decodedBase]);
90 });
91
92 it("drops usage windows this build cannot decode instead of failing the provider", () => {
93 const parsed = decodeServerProvider({
94 ...baseProviderSnapshot,
95 usageLimits: {
96 checkedAt: "2026-04-10T00:00:00.000Z",
97 windows: [
98 { id: "primary", kind: "session", label: "Session", usedPercent: 12 },
99 { id: "future", kind: "some-future-kind", label: "Future", usedPercent: 1 },
100 { id: "bad", kind: "weekly", label: "Weekly", usedPercent: 120 },
101 ],
102 },
103 });
104
105 expect(parsed.usageLimits?.windows).toEqual([
106 { id: "primary", kind: "session", label: "Session", usedPercent: 12 },
107 ]);
108 });
109});
110
111describe("ServerObservability", () => {
112 const decodeServerObservability = Schema.decodeUnknownSync(ServerObservability);
113
114 it("rejects an unknown endpoint source", () => {
115 expect(() =>
116 decodeServerObservability({
117 logsDirectoryPath: "/tmp/t3/logs",
118 localTracingEnabled: true,
119 otlpTracesUrl: "https://collector.example.com/v1/traces",
120 otlpTracesSource: "cli",
121 otlpTracesEnabled: true,
122 otlpMetricsEnabled: false,
123 }),
124 ).toThrow();
125 });
126});
127
128describe("resolveEnvironmentMachineKind", () => {
129 const decodeDescriptor = Schema.decodeUnknownSync(ExecutionEnvironmentDescriptor);
130 const decodeSettings = Schema.decodeUnknownSync(ServerSettings);
131 const descriptor = (platform: Record<string, unknown>) =>
132 decodeDescriptor({
133 environmentId: "env-1",
134 label: "Box",
135 platform: { os: "linux", arch: "x64", ...platform },
136 serverVersion: "1.0.0",
137 capabilities: {},
138 });
139
140 it("prefers the user's pick over what the server detected", () => {
141 expect(
142 resolveEnvironmentMachineKind({
143 environment: descriptor({ machine: "mac-mini" }),
144 settings: decodeSettings({ environmentIcon: "laptop" }),
145 }),
146 ).toBe("laptop");
147 });
148
149 it("drops a machine kind this build does not know instead of failing the descriptor", () => {
150 const parsed = descriptor({ machine: "toaster" });
151
152 expect(parsed.platform.machine).toBeUndefined();
153 expect(
154 resolveEnvironmentMachineKind({ environment: parsed, settings: decodeSettings({}) }),
155 ).toBe("server");
156 });
157});
158
159describe("OtlpEndpointCheckInput", () => {
160 const decode = Schema.decodeUnknownSync(OtlpEndpointCheckInput);
161
162 it("accepts HTTP receivers only", () => {
163 expect(decode({ signal: "traces", url: "https://collector.example.com/v1/traces" }).url).toBe(
164 "https://collector.example.com/v1/traces",
165 );
166 for (const url of ["data:text/plain,ok", "file:///etc/hosts", "collector:4318/v1/traces"]) {
167 expect(() => decode({ signal: "traces", url })).toThrow();
168 }
169 });
170});