import assert from 'node:assert/strict' import test from 'node:test' import { PgDialect } from 'drizzle-orm/pg-core' import * as schema from '../db/schema' import { cancelManualGeneration, finishManualGeneration } from '../src/modules/serialexecution.service' const id = '00000000-0000-4000-8000-000000000001' const dialect = new PgDialect() function fixture(status: string | null, options: { cancelDuringFinish?: boolean } = {}) { const execution = status ? { id, tenant: 7, status } : null const calls: any[] = [] const db: any = { transaction: async (fn: any) => fn(db), select: () => query('select'), update: (table: any) => query('update', table), } function query(kind: string, table?: any) { let values: any let condition: any const builder: any = { from: (value: any) => { table = value; return builder }, set: (value: any) => { values = value; return builder }, where: (value: any) => { condition = value; return builder }, limit: () => builder, for: (lock: string) => { assert.equal(lock, 'update'); return builder }, returning: () => builder, then: (resolve: any, reject: any) => Promise.resolve().then(() => { const sql = condition ? dialect.sqlToQuery(condition) : null calls.push({ kind, table, values, sql }) if (table === schema.serialExecutions) { if (!execution || !sql.params.includes(7)) return [] if (values?.status === 'processing' && !['draft', 'error'].includes(execution.status)) return [] if (values) Object.assign(execution, values) return [{ ...execution }] } if (table === schema.createddocuments && kind === 'select' && options.cancelDuringFinish) { execution!.status = 'cancelling' return [{ id: 123, state: 'Entwurf' }] } if (table === schema.tenants) return [{ id: 7 }] return [] }).then(resolve, reject), } return builder } return { server: { db } as any, execution, calls } } test('cancels drafts and archives only unarchived draft invoices of the same tenant and run', async () => { const f = fixture('draft') const result = await cancelManualGeneration(f.server, id, 7) assert.equal(result.execution.status, 'cancelled') const archive = f.calls.find(c => c.table === schema.createddocuments) assert.deepEqual(archive.values, { archived: true }) assert.deepEqual(archive.sql.params, [id, 7, 'Entwurf', false]) for (const column of ['serialexecution', 'tenant', 'state', 'archived']) { assert.ok(archive.sql.sql.includes(`"${column}"`)) } }) for (const status of ['generating', 'processing']) { test(`requests cooperative cancellation while ${status}`, async () => { const f = fixture(status) const result = await cancelManualGeneration(f.server, id, 7) assert.equal(result.execution.status, 'cancelling') assert.equal(f.calls.some(c => c.table === schema.createddocuments), false) }) } for (const status of ['cancelled', 'cancelling']) { test(`repeated cancellation of ${status} is idempotent`, async () => { const f = fixture(status) await cancelManualGeneration(f.server, id, 7) assert.equal(f.calls.some(c => c.kind === 'update'), false) }) } test('rejects completed runs and missing or foreign-tenant executions without writes', async () => { for (const [status, tenant, code] of [['completed', 7, 409], [null, 7, 404], ['draft', 8, 404]] as const) { const f = fixture(status) await assert.rejects(cancelManualGeneration(f.server, id, tenant), { statusCode: code }) assert.equal(f.calls.some(c => c.kind === 'update'), false) } }) test('cannot finalize cancelled, cancelling, completed or already processing runs', async () => { for (const status of ['cancelled', 'cancelling', 'completed', 'processing', 'generating']) { const f = fixture(status) await assert.rejects(finishManualGeneration(f.server, id, 7), { statusCode: 409 }) assert.equal(f.execution!.status, status) assert.equal(f.calls.length, 1) } }) test('cancellation during finalization skips remaining invoices and settles as cancelled', async () => { const f = fixture('draft', { cancelDuringFinish: true }) const result = await finishManualGeneration(f.server, id, 7) assert.equal(result.execution.status, 'cancelled') assert.equal(result.processed, 0) assert.equal(result.success, false) const writes = f.calls.filter(c => c.kind === 'update' && c.table === schema.createddocuments) assert.equal(writes.length, 1) assert.deepEqual(writes[0].values, { archived: true }) })