106 lines
4.8 KiB
TypeScript
106 lines
4.8 KiB
TypeScript
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 })
|
|
})
|