import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import { DIProvider } from "@/app/di"; import { MockAgentGateway, MockScheduledTaskGateway, MockSystemGateway, MockWorkStateGateway, } from "@/adapters/mock"; import type { ScheduledProjectTask } from "@/domain"; import type { Gateways } from "@/ports"; import { ScheduledTasksPanel } from "./ScheduledTasksPanel"; const PROJECT = "project-schedules"; function renderPanel(scheduledTask = new MockScheduledTaskGateway()) { const gateways = { scheduledTask, agent: new MockAgentGateway(), system: new MockSystemGateway(), workState: new MockWorkStateGateway(), } as unknown as Gateways; return { scheduledTask, ...render() }; } describe("ScheduledTasksPanel", () => { it("shows the prescribed empty state and creates only after security review", async () => { const { scheduledTask } = renderPanel(); expect(await screen.findByText("Aucune tâche planifiée pour ce projet")).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Nouvelle tâche planifiée" })); const dialog = screen.getByRole("dialog", { name: "Nouvelle tâche planifiée" }); fireEvent.change(within(dialog).getByLabelText("Nom"), { target: { value: "Nettoyage" } }); fireEvent.change(within(dialog).getByLabelText("Commande"), { target: { value: "npm" } }); fireEvent.change(within(dialog).getByLabelText("Arguments"), { target: { value: "run\nclean" } }); fireEvent.click(within(dialog).getByRole("button", { name: "Vérifier avant activation" })); expect(within(dialog).getByText("Cette tâche pourra modifier ou supprimer des fichiers automatiquement.")).toBeTruthy(); expect(within(dialog).getByText("npm run clean")).toBeTruthy(); fireEvent.click(within(dialog).getByRole("button", { name: "Créer la tâche" })); await waitFor(async () => expect(await scheduledTask.list(PROJECT)).toHaveLength(1)); expect(await screen.findByText("Nettoyage")).toBeTruthy(); expect(screen.getByText("Active")).toBeTruthy(); }); it("surfaces an agent proposal and supports refusal", async () => { const scheduledTask = new MockScheduledTaskGateway(); const now = Date.now(); const pending: ScheduledProjectTask = { id: "agent-request", projectId: PROJECT, name: "Purge", description: "Limiter la taille du build", enabled: false, approval: "pending", sandbox: "projectReadWrite", command: { command: "find", args: ["dist", "-delete"] }, schedule: { kind: "daily", time: "02:00" }, timezone: "Europe/Paris", nextRunAtMs: null, createdBy: { kind: "agent", agentId: "agent-1" }, updatedBy: { kind: "agent", agentId: "agent-1" }, createdAtMs: now, updatedAtMs: now, auditRevision: 1, }; scheduledTask._seed(pending); renderPanel(scheduledTask); expect(await screen.findByText("À approuver")).toBeTruthy(); expect(screen.getByText(/Cette tâche ne s’exécutera pas avant votre approbation/)).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Refuser" })); await waitFor(() => expect(screen.getByText("Suspendue")).toBeTruthy()); }); });