Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 77 additions & 23 deletions assistant/src/__tests__/config-loader-platform-defaults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ function readConfig(): Record<string, unknown> {
}

const MANAGED_SERVICES = [
"inference",
"image-generation",
"web-search",
"google-oauth",
Expand Down Expand Up @@ -126,7 +125,7 @@ describe("platform-managed config defaults", () => {
}
});

test("IS_PLATFORM=true, no config file → all 8 service modes written as 'managed'", () => {
test("IS_PLATFORM=true, no config file → all 7 managed service modes written as 'managed'", () => {
process.env.IS_PLATFORM = "true";

loadConfig();
Expand All @@ -140,7 +139,7 @@ describe("platform-managed config defaults", () => {
}
});

test("IS_PLATFORM=false, no config file → service modes default to 'your-own'", () => {
test("IS_PLATFORM=false, no config file → managed service modes default to 'your-own'", () => {
process.env.IS_PLATFORM = "false";

loadConfig();
Expand All @@ -154,7 +153,7 @@ describe("platform-managed config defaults", () => {
}
});

test("IS_PLATFORM unset, no config file → service modes default to 'your-own'", () => {
test("IS_PLATFORM unset, no config file → managed service modes default to 'your-own'", () => {
delete process.env.IS_PLATFORM;

loadConfig();
Expand All @@ -171,13 +170,13 @@ describe("platform-managed config defaults", () => {
test("IS_PLATFORM=true, config file already exists → existing service mode values are preserved", () => {
process.env.IS_PLATFORM = "true";

// Write an existing config with inference mode explicitly set to "your-own"
// Write an existing config with image-generation mode explicitly set to "your-own"
writeFileSync(
CONFIG_PATH,
JSON.stringify(
{
services: {
inference: { mode: "your-own" },
"image-generation": { mode: "your-own" },
},
},
null,
Expand All @@ -191,13 +190,11 @@ describe("platform-managed config defaults", () => {
expect(written.services).toBeDefined();
// The existing value must be preserved — backfill path, not fresh-write path
expect(
(written.services!["inference"] as { mode?: string })?.mode,
(written.services!["image-generation"] as { mode?: string })?.mode,
).toBe("your-own");
// ...and the in-memory config must mirror the explicit user choice (the
// fill-defaults pass must not override an explicit "your-own").
expect(
(config.services.inference as { mode: string }).mode,
).toBe("your-own");
expect(config.services["image-generation"].mode).toBe("your-own");
});

test("IS_PLATFORM=true, config file exists without a services key → in-memory config has all managed modes", () => {
Expand Down Expand Up @@ -367,15 +364,15 @@ describe("GET /v1/config handler — context-default fill on raw response", () =
}
});

test("IS_PLATFORM=true, raw config has explicit services.inference.mode='your-own' → preserved", () => {
test("IS_PLATFORM=true, raw config has explicit services.image-generation.mode='your-own' → preserved", () => {
process.env.IS_PLATFORM = "true";

// User has explicitly chosen "your-own" via the macOS Save flow.
// The patch handler persisted that to disk; the fill pass must not
// override an explicit user choice.
// User has explicitly chosen "your-own" for image-generation via the macOS
// Save flow. The patch handler persisted that to disk; the fill pass must
// not override an explicit user choice.
const raw: Record<string, unknown> = {
services: {
inference: { mode: "your-own" },
"image-generation": { mode: "your-own" },
},
};

Expand All @@ -384,10 +381,12 @@ describe("GET /v1/config handler — context-default fill on raw response", () =
unknown
>;
const services = result["services"] as Record<string, { mode: string }>;
expect(services["inference"]!.mode).toBe("your-own");
// Other services were missing entirely → context defaults fill them in.
expect(services["image-generation"]!.mode).toBe("managed");
expect(services["image-generation"]!.mode).toBe("your-own");
// web-search was missing → fill.
expect(services["web-search"]!.mode).toBe("managed");
// inference.mode is a legacy backwards-compat wire field — synthesized
// here for old macOS clients (SettingsStore.swift) that still read it.
expect(services["inference"]!.mode).toBe("managed");
});

test("IS_PLATFORM=false, raw config has no services key → response is unchanged", () => {
Expand All @@ -408,12 +407,12 @@ describe("GET /v1/config handler — context-default fill on raw response", () =
expect(result["services"]).toBeUndefined();
});

test("IS_PLATFORM=true, raw config has partial services.inference subtree → preserves user fields, fills missing mode", () => {
test("IS_PLATFORM=true, raw config has partial services subtree → preserves user fields, fills missing mode", () => {
process.env.IS_PLATFORM = "true";

// User set image-generation.provider but never chose a mode for any
// service. The fill pass adds the missing modes without clobbering
// the user-supplied provider.
// User set image-generation.provider but never chose a mode.
// The fill pass adds the missing mode without clobbering the user-supplied
// provider.
const raw: Record<string, unknown> = {
services: {
"image-generation": { provider: "openai" },
Expand All @@ -430,10 +429,65 @@ describe("GET /v1/config handler — context-default fill on raw response", () =
>;
expect(services["image-generation"]!.mode).toBe("managed");
expect(services["image-generation"]!.provider).toBe("openai");
// Inference, which was missing entirely, picks up the context default.
// services.inference.mode is synthesized as a legacy wire-only field for
// older macOS clients during the rollout window (Phase 1.2 schema removal
// landed before the macOS Providers UI ships).
expect(services["inference"]!.mode).toBe("managed");
});

test("IS_PLATFORM=true, raw config has no inference subtree → synthesizes legacy mode='managed'", () => {
process.env.IS_PLATFORM = "true";

const raw: Record<string, unknown> = {
llm: {
profiles: {
balanced: { provider: "anthropic", model: "claude-sonnet-4.5" },
},
},
};

const result = applyContextDefaultsToRawConfig(raw) as Record<
string,
unknown
>;
const services = result["services"] as Record<string, { mode: string }>;
expect(services["inference"]!.mode).toBe("managed");
});

test("IS_PLATFORM=true, raw config has explicit services.inference.mode='your-own' → preserved (legacy override)", () => {
process.env.IS_PLATFORM = "true";

// Pre-migration upgrade: workspace config still carries the legacy
// mode value. The synthesis only fills when absent, so an explicit
// disk value wins until migration 076 strips it.
const raw: Record<string, unknown> = {
services: {
inference: { mode: "your-own" },
},
};

const result = applyContextDefaultsToRawConfig(raw) as Record<
string,
unknown
>;
const services = result["services"] as Record<string, { mode: string }>;
expect(services["inference"]!.mode).toBe("your-own");
});

test("IS_PLATFORM=false, raw config has no inference subtree → no synthesis", () => {
process.env.IS_PLATFORM = "false";

const raw: Record<string, unknown> = {
llm: {},
};

const result = applyContextDefaultsToRawConfig(raw) as Record<
string,
unknown
>;
expect(result["services"]).toBeUndefined();
});

// -------------------------------------------------------------------------
// Malformed-but-parseable config.json — must not 500 the GET endpoint.
//
Expand Down
6 changes: 3 additions & 3 deletions assistant/src/__tests__/config-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ function writeConfig(obj: unknown): void {
describe("AssistantConfigSchema", () => {
test("parses empty object with full defaults", () => {
const result = AssistantConfigSchema.parse({});
// services.inference now carries only `mode`; provider/model live under
// llm.default.{provider,model} (see PR 19 of unify-llm-callsites).
expect(result.services.inference.mode).toBe("your-own");
// services.inference is now an empty object; provider/model live under
// llm.default.{provider,model}, auth routing via provider_connections.
expect(result.services.inference).toEqual({});
expect(result.llm.default.provider).toBe("anthropic");
expect(result.llm.default.model).toBe("claude-opus-4-7");
expect(result.services["image-generation"].provider).toBe("gemini");
Expand Down
62 changes: 0 additions & 62 deletions assistant/src/__tests__/config-set-platform-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,27 +197,6 @@ describe("config set — platform connection guard for service mode paths", () =
mockGetNestedValue = () => undefined;
});

test("config set services.inference.mode managed — fails when not connected", async () => {
const { exitCode, stdout } = await runCli([
"node",
"assistant",
"--json",
"config",
"set",
"services.inference.mode",
"managed",
]);

expect(exitCode).toBe(1);
const parsed = JSON.parse(stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error).toContain("vellum platform connect");
expect(parsed.error).toContain("Not connected");
// Config should NOT have been written
expect(mockSaveRawConfigCalls).toHaveLength(0);
expect(mockSetNestedValueCalls).toHaveLength(0);
});

test("config set services.image-generation.mode your-own — succeeds without platform connection", async () => {
const { exitCode } = await runCli([
"node",
Expand Down Expand Up @@ -284,26 +263,6 @@ describe("config set — platform connection guard for service mode paths", () =
});
});

test("config get services.inference.mode — works without platform connection", async () => {
mockGetNestedValue = (_obj, key) => {
if (key === "services.inference.mode") return "your-own";
return undefined;
};

const { exitCode } = await runCli([
"node",
"assistant",
"config",
"get",
"services.inference.mode",
]);

expect(exitCode).toBe(0);
// No writes should have occurred
expect(mockSaveRawConfigCalls).toHaveLength(0);
expect(mockSetNestedValueCalls).toHaveLength(0);
});

test("config set services.web-search.mode managed — fails when not connected", async () => {
const { exitCode, stdout } = await runCli([
"node",
Expand All @@ -322,25 +281,4 @@ describe("config set — platform connection guard for service mode paths", () =
expect(mockSaveRawConfigCalls).toHaveLength(0);
});

test("config set services.inference.mode managed — succeeds when connected", async () => {
mockPlatformClientCreate = async () => ({
platformAssistantId: "asst-123",
fetch: async () => new Response(),
});

const { exitCode } = await runCli([
"node",
"assistant",
"config",
"set",
"services.inference.mode",
"managed",
]);

expect(exitCode).toBe(0);
expect(mockSetNestedValueCalls).toHaveLength(1);
expect(mockSetNestedValueCalls[0]!.key).toBe("services.inference.mode");
expect(mockSetNestedValueCalls[0]!.value).toBe("managed");
expect(mockSaveRawConfigCalls).toHaveLength(1);
});
});
Loading
Loading