krona-staff/tests/integration/live-postgres-remediation.test.ts
Ochenstarik ea68e12350 fix(database): make live PostgreSQL workflows valid
Route every transactional outbox write through one complete event-envelope helper so occurred_at is always populated. Separate employee position and profession lookup parameters to avoid PostgreSQL 42P08, and add unit plus live-database regression coverage.
2026-09-26 15:14:53 +07:00

127 lines
4.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { buildApp } from '../../src/app.js';
import { loadConfig } from '../../src/config.js';
import { newUlid } from '../../src/core/ids.js';
import { insertOutboxEvent } from '../../src/core/outbox.js';
import { Database } from '../../src/infrastructure/database.js';
const connectionString = process.env.DATABASE_TEST_URL;
const isolatedDatabase = process.env.DATABASE_TEST_ISOLATED === 'true';
const describeDatabase = connectionString && isolatedDatabase ? describe : describe.skip;
describeDatabase('live PostgreSQL audit remediations', () => {
it('creates an employee and completes the outbox-producing shift and payroll workflow', async () => {
const database = new Database(connectionString!);
const tenantId = newUlid();
const actorId = newUlid();
const managerToken = 'm'.repeat(64);
const orgUnitId = newUlid();
const sessionId = newUlid();
const currentDatabase = await database.pool.query<{ name: string }>(
'SELECT current_database() AS name',
);
expect(currentDatabase.rows[0]?.name).toMatch(/^krona_staff_audit_[a-z0-9_]+$/);
const existingData = await database.pool.query<{ has_data: boolean }>(
`SELECT EXISTS (SELECT 1 FROM staff_employees)
OR EXISTS (SELECT 1 FROM staff_outbox)
OR EXISTS (SELECT 1 FROM staff_audit_log)
OR EXISTS (SELECT 1 FROM staff_work_sessions)
OR EXISTS (SELECT 1 FROM staff_payroll_runs) AS has_data`,
);
expect(existingData.rows[0]?.has_data).toBe(false);
const probeOutboxId = await database.tenantTransaction(tenantId, actorId, (client) =>
insertOutboxEvent(client, {
tenantId,
eventType: 'staff.audit_probe.created',
subjectType: 'AuditProbe',
subjectId: newUlid(),
actorType: 'user',
actorId,
payload: { source: 'live-postgres-test' },
correlationId: newUlid(),
}),
);
const role = await database.pool.query(
'SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user',
);
expect(role.rows[0]).toEqual({ rolsuper: false, rolbypassrls: false });
const probe = await database.tenantTransaction(tenantId, actorId, (client) =>
client.query<{ occurred_at: Date }>('SELECT occurred_at FROM staff_outbox WHERE id = $1', [
probeOutboxId,
]),
);
expect(probe.rows[0]?.occurred_at).toBeTruthy();
const app = await buildApp({
config: loadConfig({
DATABASE_URL: connectionString!,
STANDALONE_TENANT_ID: tenantId,
STANDALONE_ACTOR_ID: actorId,
STANDALONE_MANAGER_TOKEN: managerToken,
STANDALONE_TERMINAL_TOKEN: 't'.repeat(64),
STANDALONE_ACCESS: 'manager',
LOG_LEVEL: 'silent',
NODE_ENV: 'test',
}),
database,
});
const authorization = { authorization: `Bearer ${managerToken}` };
try {
const employee = await app.inject({
method: 'POST',
url: '/api/v1/staff/employees',
headers: authorization,
payload: {
personnel_number: `AUDIT-${newUlid()}`,
display_name: 'Тестовый сотрудник',
position: 'Тестовая профессия',
},
});
expect(employee.statusCode, employee.body).toBe(201);
const employeeId = employee.json<{ id: string }>().id;
await database.tenantTransaction(tenantId, actorId, async (client) => {
await client.query(
`INSERT INTO staff_org_units (id, tenant_id, code, name)
VALUES ($1, $2, $3, 'Тестовая точка')`,
[orgUnitId, tenantId, `audit-${newUlid().toLowerCase()}`],
);
await client.query(
`INSERT INTO staff_work_sessions
(id, tenant_id, employee_id, org_unit_id, business_date, started_at, ended_at,
worked_minutes, status)
VALUES ($1, $2, $3, $4, '2040-01-15', '2040-01-15T01:00:00Z',
'2040-01-15T09:00:00Z', 480, 'pending_review')`,
[sessionId, tenantId, employeeId, orgUnitId],
);
});
const confirmed = await app.inject({
method: 'PATCH',
url: `/api/v1/staff/work-sessions/${sessionId}`,
headers: authorization,
payload: { status: 'confirmed' },
});
expect(confirmed.statusCode).toBe(200);
const calculated = await app.inject({
method: 'POST',
url: '/api/v1/staff/payroll/calculate',
headers: authorization,
payload: { month: '2040-01' },
});
expect(calculated.statusCode).toBe(200);
const runId = calculated.json<{ run_id: string }>().run_id;
const approved = await app.inject({
method: 'POST',
url: `/api/v1/staff/payroll/runs/${runId}/approve`,
headers: authorization,
payload: {},
});
expect(approved.statusCode).toBe(200);
} finally {
await app.close();
}
});
});