feat: implémentation et QA ticket99 - agent model configuration v2
- backend A-C: VO Codex/Claude, renderers/projecteurs modèle, SecretRef/env, catalogues/use cases/Tauri commands - frontend D: profils Codex/Claude provider->model->secret, validations, conservation SecretRef - fix: app-tauri embedded_server isolant IDEA_WEB_ROOT QA: backend 57/57 tests, frontend 74/74 tests OK
This commit is contained in:
@ -183,6 +183,131 @@ describe("FirstRunWizard (with MockProfileGateway)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("FirstRunWizard — Codex/Claude provider configuration (ticket #99)", () => {
|
||||
it("saves a Codex provider profile and clears the literal key", async () => {
|
||||
const { profile } = renderWizard();
|
||||
await waitForLoaded();
|
||||
|
||||
fireEvent.click(screen.getByLabelText("use OpenAI Codex CLI"));
|
||||
|
||||
const row = within(
|
||||
screen.getByLabelText("use OpenAI Codex CLI").closest("li")!,
|
||||
);
|
||||
const providerSelect = await row.findByLabelText("OpenAI Codex CLI provider");
|
||||
const modelSelect = row.getByLabelText(
|
||||
"OpenAI Codex CLI model",
|
||||
) as HTMLSelectElement;
|
||||
expect(modelSelect.disabled).toBe(true);
|
||||
|
||||
fireEvent.change(providerSelect, { target: { value: "openai" } });
|
||||
expect(modelSelect.value).toBe("");
|
||||
expect(modelSelect.disabled).toBe(false);
|
||||
fireEvent.change(modelSelect, { target: { value: "gpt-5-codex" } });
|
||||
fireEvent.change(row.getByLabelText("OpenAI Codex CLI api key"), {
|
||||
target: { value: "sk-codex-secret" },
|
||||
});
|
||||
|
||||
fireEvent.click(row.getByRole("button", { name: "Enregistrer Codex" }));
|
||||
|
||||
await waitFor(async () => {
|
||||
const saved = await profile.listProfiles();
|
||||
const codex = saved.find((p) => p.command === "codex");
|
||||
expect(codex?.codexProvider).toEqual({
|
||||
providerId: "openai",
|
||||
model: "gpt-5-codex",
|
||||
apiKeyRef: "mock-secret-mock-codex",
|
||||
custom: undefined,
|
||||
});
|
||||
expect(JSON.stringify(codex)).not.toContain("sk-codex-secret");
|
||||
});
|
||||
expect((row.getByLabelText("OpenAI Codex CLI api key") as HTMLInputElement).value).toBe("");
|
||||
});
|
||||
|
||||
it("saves a Claude provider profile without exposing the literal key", async () => {
|
||||
const { profile } = renderWizard();
|
||||
await waitForLoaded();
|
||||
|
||||
const claudeToggle = screen.getByLabelText(
|
||||
"use Claude Code",
|
||||
) as HTMLInputElement;
|
||||
if (!claudeToggle.checked) fireEvent.click(claudeToggle);
|
||||
|
||||
const row = within(claudeToggle.closest("li")!);
|
||||
fireEvent.change(await row.findByLabelText("Claude Code provider"), {
|
||||
target: { value: "anthropic" },
|
||||
});
|
||||
fireEvent.change(row.getByLabelText("Claude Code model"), {
|
||||
target: { value: "claude-sonnet-4-5" },
|
||||
});
|
||||
const apiKey = row.getByLabelText("Claude Code api key") as HTMLInputElement;
|
||||
fireEvent.change(apiKey, { target: { value: "sk-claude-secret" } });
|
||||
|
||||
fireEvent.click(row.getByRole("button", { name: "Enregistrer Claude" }));
|
||||
|
||||
await waitFor(async () => {
|
||||
const saved = await profile.listProfiles();
|
||||
const claude = saved.find((p) => p.command === "claude");
|
||||
expect(claude?.claudeProvider).toEqual({
|
||||
providerId: "anthropic",
|
||||
model: "claude-sonnet-4-5",
|
||||
apiKeyRef: "mock-secret-mock-claude",
|
||||
});
|
||||
expect(JSON.stringify(claude)).not.toContain("sk-claude-secret");
|
||||
});
|
||||
expect(apiKey.value).toBe("");
|
||||
});
|
||||
|
||||
it("keeps an existing Codex SecretRef when editing provider/model", async () => {
|
||||
const profile = new MockProfileGateway();
|
||||
await profile.configureProfiles([
|
||||
{
|
||||
id: "codex-existing",
|
||||
name: "Codex configured",
|
||||
command: "codex",
|
||||
args: [],
|
||||
contextInjection: { strategy: "conventionFile", target: "AGENTS.md" },
|
||||
detect: "codex --version",
|
||||
cwdTemplate: "{projectRoot}",
|
||||
structuredAdapter: "codex",
|
||||
codexProvider: {
|
||||
providerId: "openai",
|
||||
model: "gpt-5-mini",
|
||||
apiKeyRef: "existing-secret-ref",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const gateways = {
|
||||
profile,
|
||||
modelServer: new MockModelServerGateway(),
|
||||
} as unknown as Gateways;
|
||||
render(
|
||||
<DIProvider gateways={gateways}>
|
||||
<FirstRunWizard forceOpen />
|
||||
</DIProvider>,
|
||||
);
|
||||
await waitForLoaded();
|
||||
|
||||
const row = within(
|
||||
screen.getByLabelText("use Codex configured").closest("li")!,
|
||||
);
|
||||
expect((row.getByLabelText("Codex configured api key") as HTMLInputElement).value).toBe("");
|
||||
fireEvent.change(row.getByLabelText("Codex configured model"), {
|
||||
target: { value: "gpt-5" },
|
||||
});
|
||||
fireEvent.change(row.getByLabelText("Codex configured api key"), {
|
||||
target: { value: "sk-new-secret" },
|
||||
});
|
||||
fireEvent.click(row.getByRole("button", { name: "Enregistrer Codex" }));
|
||||
|
||||
await waitFor(async () => {
|
||||
const [saved] = await profile.listProfiles();
|
||||
expect(saved.codexProvider?.model).toBe("gpt-5");
|
||||
expect(saved.codexProvider?.apiKeyRef).toBe("existing-secret-ref");
|
||||
expect(JSON.stringify(saved)).not.toContain("sk-new-secret");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("FirstRunWizard — OpenCode + llama.cpp local profile", () => {
|
||||
const OPENCODE = "OpenCode + llama.cpp";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user