Files
IdeA/frontend/src/adapters/mock/mock.test.ts
Blomios 7007b2cc7e feat(backend,frontend): tâches planifiées projet — tool MCP idea_scheduled_task_* et menu utilisateur #305, fix transcript lié indisponible après reboot #314 (QA verte)
#305: nouvel agrégat durable ScheduledProjectTask dans domain (scheduled_task.rs, ids, ports ScheduledProjectTaskStore), source de vérité <app_data_dir>/scheduled-project-tasks/<projectId>.json, schedules bornés V1 interval/daily/weekly avec fuseau, cycle À approuver/Active/Suspendue avec approbation obligatoire des tâches demandées par agent; use cases application/scheduled_task (list/create/update/delete/run_now/reconcile/fire) réutilisant BackgroundTask pour les runs avec distinction planifié/manuel; store infrastructure dédié + scheduler reconcile au boot sans rattrapage en rafale, cwd confiné au projet; 7 tools MCP idea_scheduled_task_* (list/read lecture seule, create/update/delete/run_now/set_enabled write-actions soumises aux permissions natives); commandes Tauri + routes HTTP aux contrats DTO identiques (camelCase nextRunAtMs/lastRunAtMs/lastTaskId/createdBy/updatedBy); frontend: gateways Tauri/HTTP/mock + types ports/domain, surface projet 'Tâches planifiées' (liste utilisateur+agent unifiée, création/édition avec récapitulatif avant activation, approbation/refus, exécuter maintenant, suspendre/reprendre, suppression), catalogage surface web (webSurfaceCatalog/viewPlacement/ViewPanelBody/WebWorkspace); docs/scheduled-project-tasks.md et sdk docs/services.md (047c38b: pas un service plugin V1, pas d'émulation via cron système).

#314: correction de 'Conversation liée — transcript indisponible' après reboot: restauration du transcript lié côté application (orchestrator service, lifecycle, session_limit, background sink, terminal registry) et côté frontend (adapters agent/http frames/stream/requestResponse, workStateNormalization, CustomAgentChatView avec état et action utile), tests dto_chat/agent_lifecycle/structured_registry_d1/background_completion_sink étendus.

QA verte rapportée par QA sur feature/305-scheduled-project-tasks-mcp, sans bloquant, pour #305 et #314.

Hors périmètre worktree: tickets non liés, attachments/, scripts/; aucun push.
2026-09-11 10:32:01 +02:00

113 lines
3.5 KiB
TypeScript

/**
* L1 — each mock gateway satisfies its port interface (typecheck via the typed
* `Gateways` binding below) and behaves sensibly offline.
*/
import { describe, it, expect } from "vitest";
import type { DomainEvent } from "@/domain";
import type { Gateways } from "@/ports";
import { createMockGateways, MockSystemGateway } from "./index";
// Typechecking this assignment proves every mock implements its port.
const gateways: Gateways = createMockGateways();
describe("createMockGateways", () => {
it("exposes all gateways", () => {
expect(Object.keys(gateways).sort()).toEqual([
"agent",
"conversation",
"desktopServer",
"device",
"embedder",
"focusedProject",
"git",
"input",
"layout",
"memory",
"modelServer",
"permission",
"plugin",
"pluginConfig",
"pluginEvents",
"pluginStorage",
"pluginTask",
"pluginToolchain",
"pluginWorkspace",
"profile",
"project",
"remote",
"scheduledTask",
"skill",
"system",
"template",
"terminal",
"ticket",
"uiPreferences",
"userSettings",
"window",
"workState",
]);
});
});
describe("MockSystemGateway", () => {
it("health returns an alive report echoing the note", async () => {
const sys = new MockSystemGateway();
const report = await sys.health("ping");
expect(report.alive).toBe(true);
expect(report.note).toBe("ping");
expect(typeof report.version).toBe("string");
expect(typeof report.timeMillis).toBe("number");
expect(typeof report.correlationId).toBe("string");
});
it("health note defaults to null when omitted", async () => {
const report = await new MockSystemGateway().health();
expect(report.note).toBeNull();
});
it("delivers emitted domain events to subscribers and stops after unsubscribe", async () => {
const sys = new MockSystemGateway();
const received: DomainEvent[] = [];
const unsub = await sys.onDomainEvent((e) => received.push(e));
sys.emit({ type: "projectCreated", projectId: "p1" });
expect(received).toHaveLength(1);
expect(received[0]).toEqual({ type: "projectCreated", projectId: "p1" });
unsub();
sys.emit({ type: "projectCreated", projectId: "p2" });
expect(received).toHaveLength(1);
});
it("health emits a smoke domain event to subscribers", async () => {
const sys = new MockSystemGateway();
const received: DomainEvent[] = [];
await sys.onDomainEvent((e) => received.push(e));
await sys.health();
expect(received.some((e) => e.type === "projectCreated")).toBe(true);
});
});
describe("other mock gateways", () => {
it("project.createProject returns a project and listProjects reflects it", async () => {
expect(await gateways.project.listProjects()).toEqual([]);
const created = await gateways.project.createProject("n", "/root");
expect(created.name).toBe("n");
expect(created.root).toBe("/root");
expect(typeof created.id).toBe("string");
});
it("terminal.openTerminal returns handles with incrementing session ids", async () => {
const a = await gateways.terminal.openTerminal({ cwd: "/cwd", rows: 24, cols: 80 }, () => {});
const b = await gateways.terminal.openTerminal({ cwd: "/cwd", rows: 24, cols: 80 }, () => {});
expect(a.sessionId).not.toEqual(b.sessionId);
});
it("git.branches returns main as current branch", async () => {
const b = await gateways.git.branches("p");
expect(b.current).toBe("main");
expect(b.branches).toContain("main");
});
});