Files
IdeaSDK/frontend/src/adapters/mock/profile.test.ts
Blomios 6de4e5a6e0 feat(agent): menu de profils restreint Claude/Codex + retrait custom (D7) — §17
Dernier lot de §17 : seuls les profils pilotables en mode structuré sont
proposés à la sélection/création.

- domain : AgentProfile::is_selectable() = structured_adapter.is_some(),
  source unique de vérité du prédicat.
- infrastructure : AgentSessionFactory::supports délègue à is_selectable
  (supports et is_selectable ne peuvent plus diverger).
- application : selectable_reference_profiles() = reference_profiles()
  filtré ; ReferenceProfiles et FirstRunState exposent la liste filtrée
  (Claude/Codex). reference_profiles() brut reste à 4 (data intacte) ⇒
  un agent Gemini/Aider/custom legacy déjà configuré continue de tourner.
- frontend : bloc AddCustomProfile retiré du wizard first-run, action
  addCustom retirée du viewmodel ; wizard n'affiche que la liste filtrée.

Tests (QA) : is_selectable (table de vérité), cohérence stricte
is_selectable<->supports, liste exposée=2 / data brute=4, garde
anti-régression Vitest sur l'absence du bloc custom — validées par
mutation. cargo test --workspace : 820 passed. npx vitest run : 344 passed.

§17 COMPLET (D0->D7). Suivi restant : D6b (surfacer reply dans le writer
wire .response.json pour la délégation par protocole fichier).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:57:51 +02:00

93 lines
3.2 KiB
TypeScript

/**
* L5 — the in-memory {@link MockProfileGateway}: first-run flag lifecycle,
* reference catalogue, simulated detection (only `claude` installed), and the
* save/list/delete/configure CRUD.
*/
import { describe, it, expect } from "vitest";
import type { AgentProfile } from "@/domain";
import { MockProfileGateway, MOCK_REFERENCE_PROFILES } from "./index";
function customProfile(id: string, command: string): AgentProfile {
return {
id,
name: `Custom ${id}`,
command,
args: [],
contextInjection: { strategy: "stdin" },
detect: null,
cwdTemplate: "{projectRoot}",
};
}
describe("MockProfileGateway", () => {
it("firstRunState is first-run with only the selectable reference profiles", async () => {
// §17.3/D7: only structured-drivable profiles (Claude/Codex) are offered;
// Gemini/Aider are filtered out of the selection path server-side.
const gw = new MockProfileGateway();
const state = await gw.firstRunState();
expect(state.isFirstRun).toBe(true);
expect(state.referenceProfiles.map((p) => p.command)).toEqual([
"claude",
"codex",
]);
});
it("referenceProfiles returns a clone of the catalogue", async () => {
const gw = new MockProfileGateway();
const refs = await gw.referenceProfiles();
expect(refs).toEqual(MOCK_REFERENCE_PROFILES);
refs[0].command = "mutated";
const again = await gw.referenceProfiles();
expect(again[0].command).toBe("claude");
});
it("detectProfiles marks only claude as installed", async () => {
const gw = new MockProfileGateway();
const results = await gw.detectProfiles([...MOCK_REFERENCE_PROFILES]);
const byCommand = Object.fromEntries(
results.map((r) => [r.profile.command, r.available]),
);
expect(byCommand).toEqual({
claude: true,
codex: false,
});
});
it("saveProfile upserts and listProfiles reflects it", async () => {
const gw = new MockProfileGateway();
await gw.saveProfile(customProfile("c1", "foo"));
await gw.saveProfile(customProfile("c1", "bar")); // same id ⇒ replace
const list = await gw.listProfiles();
expect(list).toHaveLength(1);
expect(list[0].command).toBe("bar");
});
it("deleteProfile removes by id", async () => {
const gw = new MockProfileGateway();
await gw.saveProfile(customProfile("a", "a"));
await gw.saveProfile(customProfile("b", "b"));
await gw.deleteProfile("a");
const list = await gw.listProfiles();
expect(list.map((p) => p.id)).toEqual(["b"]);
});
it("configureProfiles persists the batch and closes the first run", async () => {
const gw = new MockProfileGateway();
expect((await gw.firstRunState()).isFirstRun).toBe(true);
const chosen = [customProfile("x", "x")];
const out = await gw.configureProfiles(chosen);
expect(out).toEqual(chosen);
expect((await gw.firstRunState()).isFirstRun).toBe(false);
expect(await gw.listProfiles()).toEqual(chosen);
});
it("configureProfiles with an empty list still closes the first run", async () => {
const gw = new MockProfileGateway();
await gw.configureProfiles([]);
expect((await gw.firstRunState()).isFirstRun).toBe(false);
});
});