krona-staff/tests/integration/live-postgres-remediation.test.ts
Ochenstarik 1573860280 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 16:10:13 +07:00

126 lines
4.8 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 guardToken = process.env.DATABASE_TEST_GUARD_TOKEN;
const describeDatabase =
connectionString && isolatedDatabase && guardToken ? 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 role = await database.pool.query<{ rolsuper: boolean; rolbypassrls: boolean }>(
'SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user',
);
expect(role.rows[0]).toEqual({ rolsuper: false, rolbypassrls: false });
const guard = await database.pool.query<{ marker: string }>(
'SELECT marker FROM krona_staff_test_guard WHERE marker = $1',
[guardToken],
);
expect(guard.rows).toEqual([{ marker: guardToken }]);
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 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();
}
});
});