1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { expect, it } from "@effect/vitest";
3import { MuseSettings } from "../settings.ts";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Schema from "effect/Schema";
7import { vi } from "vite-plus/test";
8
9import { writeFakeCli } from "@t3tools/provider-testing/fakeCli";
10import type { MuseSdkHost } from "./sdk.ts";
11import { COMPACT_SLASH_COMMAND } from "@t3tools/provider-core/server/snapshotProbe";
12import { checkMuseProviderStatus } from "./status.ts";
13
14const settings = Schema.decodeSync(MuseSettings);
15const makeHost = (catalog: Record<string, unknown>) => {
16 const host: MuseSdkHost = {
17 initializeResult: { grantedCapabilities: [] },
18 connection: {
19 request: vi.fn(async () => catalog),
20 command: vi.fn(async () => ({})),
21 mintCommandId: () => "test-command",
22 onNotification: () => {},
23 onServerRequest: () => {},
24 onProtocolError: () => {},
25 closed: new Promise(() => {}),
26 },
27 exited: new Promise(() => {}),
28 close: vi.fn(async () => {}),
29 };
30 return host;
31};
32const metaCatalog = {
33 providerId: "meta",
34 models: [
35 {
36 modelId: "muse-discovered",
37 displayLabel: "Muse Discovered",
38 providerId: "meta",
39 isDefault: true,
40 },
41 {
42 modelId: "foreign-model",
43 displayLabel: "Another provider",
44 providerId: "another",
45 isDefault: false,
46 },
47 ],
48};
49
50const fakeCli = (source = 'process.stdout.write("muse 1.0.3-R2198.1\\n");') =>
51 Effect.gen(function* () {
52 const fs = yield* FileSystem.FileSystem;
53 const directory = yield* fs.makeTempDirectoryScoped({ prefix: "muse-status-test-" });
54 return writeFakeCli({ directory, name: "muse", source });
55 });
56
57it.layer(NodeServices.layer)("Muse status", (it) => {
58 it.effect("does not start a host when disabled", () =>
59 Effect.gen(function* () {
60 const createHost = vi.fn(async () => makeHost(metaCatalog));
61 const snapshot = yield* checkMuseProviderStatus(settings({}), {}, undefined, createHost);
62 expect(snapshot.status).toBe("disabled");
63 expect(createHost).not.toHaveBeenCalled();
64 }),
65 );
66
67 it.effect("gives host-local install and login guidance for a missing binary", () =>
68 Effect.gen(function* () {
69 const snapshot = yield* checkMuseProviderStatus(
70 settings({ enabled: true, binaryPath: "/definitely-missing/muse" }),
71 );
72 expect(snapshot.installed).toBe(false);
73 expect(snapshot.status).toBe("error");
74 expect(snapshot.message).toContain("muse login");
75 expect(snapshot.message).toContain("this T3 server host");
76 }),
77 );
78
79 it.effect("does not expose CLI stderr or start an SDK host after a failed version check", () =>
80 Effect.scoped(
81 Effect.gen(function* () {
82 const binaryPath = yield* fakeCli(
83 'process.stderr.write("secret-test-value");process.exit(2);',
84 );
85 const createHost = vi.fn(async () => makeHost(metaCatalog));
86 const snapshot = yield* checkMuseProviderStatus(
87 settings({ enabled: true, binaryPath }),
88 undefined,
89 undefined,
90 createHost,
91 );
92 expect(snapshot.installed).toBe(true);
93 expect(snapshot.status).toBe("error");
94 expect(snapshot.message).not.toContain("secret-test-value");
95 expect(createHost).not.toHaveBeenCalled();
96 }),
97 ),
98 );
99
100 it.effect("discovers the Meta catalog without treating it as authentication proof", () =>
101 Effect.scoped(
102 Effect.gen(function* () {
103 const binaryPath = yield* fakeCli();
104 const host = makeHost(metaCatalog);
105 const snapshot = yield* checkMuseProviderStatus(
106 settings({ enabled: true, binaryPath, customModels: ["muse-custom"] }),
107 undefined,
108 undefined,
109 async () => host,
110 );
111 expect(snapshot.status).toBe("ready");
112 expect(snapshot.message).toBeUndefined();
113 expect(snapshot.slashCommands).toEqual([COMPACT_SLASH_COMMAND]);
114 expect(snapshot.auth).toEqual({ status: "unknown" });
115 expect(snapshot.version).toBe("1.0.3-R2198.1");
116 expect(snapshot.models.map((model) => model.slug)).toEqual([
117 "muse-discovered",
118 "muse-custom",
119 ]);
120 const descriptor = snapshot.models[0]?.capabilities?.optionDescriptors?.[0];
121 expect(
122 descriptor?.type === "select" && descriptor.options.map((option) => option.id),
123 ).toEqual(["low", "medium", "high", "xhigh", "max"]);
124 expect(host.connection.request).toHaveBeenCalledWith("model/list", {});
125 expect(host.connection.command).not.toHaveBeenCalled();
126 expect(host.close).toHaveBeenCalledOnce();
127 }),
128 ),
129 );
130
131 it.effect("closes the host after malformed metadata and reports unavailable", () =>
132 Effect.scoped(
133 Effect.gen(function* () {
134 const binaryPath = yield* fakeCli();
135 const host = makeHost({ providerId: "meta", models: [{ modelId: 1 }] });
136 const snapshot = yield* checkMuseProviderStatus(
137 settings({ enabled: true, binaryPath }),
138 undefined,
139 undefined,
140 async () => host,
141 );
142 expect(snapshot.status).toBe("error");
143 expect(snapshot.auth.status).toBe("unknown");
144 expect(snapshot.models).toEqual([]);
145 expect(host.close).toHaveBeenCalledOnce();
146 }),
147 ),
148 );
149
150 it.effect("keeps an empty discovered catalog empty instead of advertising a fallback model", () =>
151 Effect.scoped(
152 Effect.gen(function* () {
153 const binaryPath = yield* fakeCli();
154 const host = makeHost({ providerId: "meta", models: [] });
155 const snapshot = yield* checkMuseProviderStatus(
156 settings({ enabled: true, binaryPath }),
157 undefined,
158 undefined,
159 async () => host,
160 );
161 expect(snapshot.status).toBe("warning");
162 expect(snapshot.models).toEqual([]);
163 expect(host.close).toHaveBeenCalledOnce();
164 }),
165 ),
166 );
167});