import assert from 'node:assert/strict'; import { before, after, test } from 'node:test'; import { readFile } from 'node:fs/promises'; import { createServer } from 'vite'; let server, api; before(async () => { server = await createServer({appType: 'custom', logLevel: 'silent', server: {middlewareMode: true}}); api = await server.ssrLoadModule('/src/core/missions/planningProjects.ts'); }); after(async () => { await server?.close(); }); const item = {key: 'live:a', id: 'a', name: 'same name', kind: 'live', state: 'completed', revision: 2}; test('only terminal runs or drafts admit catalog deletion', () => { for (const state of ['completed', 'cancelled', 'error', 'interrupted']) assert.equal(api.planningProjectDeletable({...item, state}), true); for (const state of ['running', 'waiting', 'preparing', 'new-unknown']) assert.equal(api.planningProjectDeletable({...item, state}), false); assert.equal(api.planningProjectDeletable({...item, kind: 'draft', state: 'draft'}), true); assert.equal(api.planningProjectDeletable({...item, kind: 'recorded', state: 'ready'}), true); }); test('delete sends exact kind/id/revision and requires a matching receipt', async t => { const calls = []; let receipt = {key: item.key, deleted: true}; t.mock.method(globalThis, 'fetch', async (...args) => { calls.push(args); return {ok: true, json: async () => receipt}; }); await api.deletePlanningProject(item); assert.equal(calls[0][0], '/api/v1/mission-planner/projects/live/a'); assert.equal(calls[0][1].method, 'DELETE'); assert.deepEqual(JSON.parse(calls[0][1].body), {revision: 2}); receipt = {key: 'live:another-same-name', deleted: true}; await assert.rejects(api.deletePlanningProject(item), /не подтверждено/); const count = calls.length; await assert.rejects(api.deletePlanningProject({...item, state: 'running'}), /завершите/); assert.equal(calls.length, count); }); test('server refusal stays an error, never a successful deletion', async t => { t.mock.method(globalThis, 'fetch', async () => ({ok: false, json: async () => ({detail: 'Проект изменён.'})})); await assert.rejects(api.deletePlanningProject(item), /Проект изменён/); }); test('UI uses canonical row actions and modal, and stale loads cannot resurrect a deleted item', async () => { const component = await readFile(new URL('../src/components/missions/PlanningProjectSelect.tsx', import.meta.url), 'utf8'); const hook = await readFile(new URL('../src/core/missions/usePlanningProjects.ts', import.meta.url), 'utf8'); assert.match(component, /