krona-staff/tests/unit/tenant-sql.test.ts
Ochenstarik fd1ec3d819 style: establish repository formatting baseline
Run the configured formatter across the repository before audit remediation so later security fixes produce reviewable, conflict-resistant diffs.
2026-09-26 14:22:25 +07:00

249 lines
11 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
describe('tenant isolation migration', () => {
const sql = readFileSync('migrations/0001_infrastructure.up.sql', 'utf8');
const tables = ['staff_outbox', 'staff_inbox', 'staff_idempotency_keys', 'staff_audit_log'];
it.each(tables)('%s is tenant scoped and protected by RLS', (table) => {
const create = sql.slice(
sql.indexOf(`CREATE TABLE IF NOT EXISTS ${table}`),
sql.indexOf(';', sql.indexOf(`CREATE TABLE IF NOT EXISTS ${table}`)),
);
expect(create).toContain('tenant_id VARCHAR(26) NOT NULL');
expect(sql).toContain(`ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY`);
expect(sql).toContain(`ALTER TABLE ${table} FORCE ROW LEVEL SECURITY`);
expect(sql).toMatch(new RegExp(`CREATE POLICY [a-z_]+ ON ${table} `));
});
it('sets tenant and actor locally inside each transaction', () => {
const source = readFileSync('src/infrastructure/database.ts', 'utf8');
expect(source).toContain("set_config('app.tenant_id', $1, true)");
expect(source).toContain("set_config('app.user_id', $2, true)");
});
});
describe('attendance portal migrations', () => {
const sql = readFileSync('migrations/0005_attendance_portal.up.sql', 'utf8');
const tables = ['staff_employees', 'staff_attendance_events', 'staff_timesheet_entries'];
it.each(tables)('%s is tenant scoped and forces RLS', (table) => {
expect(sql).toContain(`ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY`);
expect(sql).toContain(`ALTER TABLE ${table} FORCE ROW LEVEL SECURITY`);
expect(sql).toMatch(new RegExp(`CREATE POLICY ${table}_[a-z_]+ ON ${table}`));
});
it('keeps raw attendance immutable for the runtime role', () => {
expect(sql).toContain('GRANT SELECT, INSERT ON staff_attendance_events TO krona_staff_app');
expect(sql).toContain(
'REVOKE UPDATE, DELETE, TRUNCATE ON staff_attendance_events FROM krona_staff_app',
);
});
});
describe('payroll foundation migration', () => {
const sql = readFileSync('migrations/0006_payroll_foundation.up.sql', 'utf8');
const tables = [
'staff_org_units',
'staff_org_unit_terms',
'staff_metric_definitions',
'staff_daily_metrics',
'staff_work_items',
'staff_pay_rules',
'staff_pay_assignments',
'staff_work_sessions',
'staff_payroll_runs',
'staff_payroll_lines',
'staff_payouts',
];
it.each(tables)('%s is tenant scoped and included in the RLS setup', (table) => {
expect(sql).toContain(`CREATE TABLE ${table}`);
expect(sql).toContain(`'${table}'`);
});
it('enables and forces RLS with a tenant policy for every listed payroll table', () => {
expect(sql).toContain(
"EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', target_table)",
);
expect(sql).toContain(
"EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY', target_table)",
);
expect(sql).toContain("target_table || '_tenant_isolation', target_table");
});
it('uses tenant-scoped foreign keys for payroll ownership', () => {
expect(sql).toContain(
'FOREIGN KEY (tenant_id, employee_id) REFERENCES staff_employees (tenant_id, id)',
);
expect(sql).toContain(
'FOREIGN KEY (tenant_id, org_unit_id) REFERENCES staff_org_units (tenant_id, id)',
);
expect(sql).toContain(
'FOREIGN KEY (tenant_id, pay_rule_id) REFERENCES staff_pay_rules (tenant_id, id)',
);
});
const payrollTables = [
'staff_pay_rules',
'staff_pay_assignments',
'staff_payroll_runs',
'staff_payroll_lines',
'staff_payroll_ledger',
];
it.each(payrollTables)('verifies %s has tenant_id column', (table) => {
const create = sql.slice(
sql.indexOf(`CREATE TABLE ${table}`),
sql.indexOf(';', sql.indexOf(`CREATE TABLE ${table}`)),
);
expect(create).toContain('tenant_id VARCHAR(26) NOT NULL');
});
it('enforces tenant isolation and RLS on staff_payroll_ledger with select and insert policies', () => {
expect(sql).toContain('ALTER TABLE staff_payroll_ledger ENABLE ROW LEVEL SECURITY');
expect(sql).toContain('ALTER TABLE staff_payroll_ledger FORCE ROW LEVEL SECURITY');
expect(sql).toContain(
'CREATE POLICY staff_payroll_ledger_tenant_select ON staff_payroll_ledger',
);
expect(sql).toContain("FOR SELECT USING (tenant_id = current_setting('app.tenant_id', true))");
expect(sql).toContain(
'CREATE POLICY staff_payroll_ledger_tenant_insert ON staff_payroll_ledger',
);
expect(sql).toContain("tenant_id = current_setting('app.tenant_id', true)");
expect(sql).toContain("created_by = current_setting('app.user_id', true)");
});
it('keeps the money ledger append-only for the runtime role', () => {
expect(sql).toContain('CREATE TABLE staff_payroll_ledger');
expect(sql).toContain('ALTER TABLE staff_payroll_ledger ENABLE ROW LEVEL SECURITY');
expect(sql).toContain('ALTER TABLE staff_payroll_ledger FORCE ROW LEVEL SECURITY');
expect(sql).toContain('GRANT SELECT, INSERT ON staff_payroll_ledger TO krona_staff_app');
expect(sql).toContain(
'REVOKE UPDATE, DELETE, TRUNCATE ON staff_payroll_ledger FROM krona_staff_app',
);
});
});
describe('employee access migration', () => {
const sql = readFileSync('migrations/0008_employee_access.up.sql', 'utf8');
it('declares external_key and staff_payroll_ledger_external_key_idx index on staff_payroll_ledger', () => {
expect(sql).toContain('ALTER TABLE staff_payroll_ledger ADD COLUMN external_key TEXT;');
expect(sql).toContain('CREATE UNIQUE INDEX staff_payroll_ledger_external_key_idx');
expect(sql).toContain(
'ON staff_payroll_ledger (tenant_id, external_key) WHERE external_key IS NOT NULL;',
);
});
it('protects standalone employee access with tenant isolation and RLS', () => {
const create = sql.slice(
sql.indexOf('CREATE TABLE staff_standalone_employee_access'),
sql.indexOf(';', sql.indexOf('CREATE TABLE staff_standalone_employee_access')),
);
expect(create).toContain('tenant_id VARCHAR(26) NOT NULL');
expect(sql).toContain('ALTER TABLE staff_standalone_employee_access ENABLE ROW LEVEL SECURITY');
expect(sql).toContain('ALTER TABLE staff_standalone_employee_access FORCE ROW LEVEL SECURITY');
expect(sql).toContain('CREATE POLICY staff_standalone_employee_access_tenant_isolation');
expect(sql).toContain("tenant_id = current_setting('app.tenant_id', true)");
});
});
describe('employee push notifications migration', () => {
const sql = readFileSync('migrations/0010_employee_push_notifications.up.sql', 'utf8');
const tables = ['staff_push_subscriptions', 'staff_notification_deliveries'];
it.each(tables)('%s is tenant scoped and forces RLS', (table) => {
const create = sql.slice(
sql.indexOf(`CREATE TABLE ${table}`),
sql.indexOf(';', sql.indexOf(`CREATE TABLE ${table}`)),
);
expect(create).toContain('tenant_id VARCHAR(26) NOT NULL');
expect(sql).toContain(`ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY`);
expect(sql).toContain(`ALTER TABLE ${table} FORCE ROW LEVEL SECURITY`);
expect(sql).toContain(`CREATE POLICY ${table}_tenant_isolation`);
expect(sql).toContain(`tenant_id = current_setting('app.tenant_id', true)`);
});
it('declares uniqueness and idempotency constraints with tenant scope', () => {
expect(sql).toContain('UNIQUE (tenant_id, id)');
expect(sql).toContain('UNIQUE (tenant_id, endpoint)');
expect(sql).toContain('UNIQUE (tenant_id, outbox_id, endpoint)');
});
it('uses tenant-scoped foreign keys to staff_employees and staff_outbox', () => {
expect(sql).toContain(
'FOREIGN KEY (tenant_id, employee_id) REFERENCES staff_employees (tenant_id, id) ON DELETE CASCADE',
);
expect(sql).toContain('FOREIGN KEY (outbox_id) REFERENCES staff_outbox (id) ON DELETE CASCADE');
});
it('grants required operations on both push tables to krona_staff_app', () => {
expect(sql).toContain(
'GRANT SELECT, INSERT, UPDATE, DELETE ON staff_push_subscriptions TO krona_staff_app;',
);
expect(sql).toContain(
'GRANT SELECT, INSERT, UPDATE, DELETE ON staff_notification_deliveries TO krona_staff_app;',
);
});
});
describe('offline sync backend migration', () => {
const sql = readFileSync('migrations/0011_offline_sync_backend.up.sql', 'utf8');
const tables = [
'staff_sync_devices',
'staff_sync_change_log',
'staff_sync_command_inbox',
'staff_sync_device_checkpoints',
];
it.each(tables)('%s is tenant scoped and forces RLS', (table) => {
const create = sql.slice(
sql.indexOf(`CREATE TABLE ${table}`),
sql.indexOf(';', sql.indexOf(`CREATE TABLE ${table}`)),
);
expect(create).toContain('tenant_id VARCHAR(26) NOT NULL');
expect(sql).toContain(`ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY;`);
expect(sql).toContain(`ALTER TABLE ${table} FORCE ROW LEVEL SECURITY;`);
expect(sql).toContain(`CREATE POLICY ${table}_tenant_isolation`);
expect(sql).toContain("tenant_id = current_setting('app.tenant_id', true)");
});
it('declares uniqueness, cursor sequencing and idempotency constraints with tenant scope', () => {
expect(sql).toContain('UNIQUE (tenant_id, id)');
expect(sql).toContain('UNIQUE (tenant_id, device_code)');
expect(sql).toContain('UNIQUE (tenant_id, token_hash)');
expect(sql).toContain('UNIQUE (tenant_id, device_id, idempotency_key)');
expect(sql).toContain('PRIMARY KEY (tenant_id, device_id)');
expect(sql).toContain('cursor_seq BIGSERIAL NOT NULL');
});
it('declares foreign key cascades for device ownership', () => {
expect(sql).toContain(
'FOREIGN KEY (tenant_id, device_id) REFERENCES staff_sync_devices (tenant_id, id) ON DELETE CASCADE',
);
});
it('grants required operations on all sync tables and sequence to krona_staff_app', () => {
expect(sql).toContain(
'GRANT SELECT, INSERT, UPDATE, DELETE ON staff_sync_devices TO krona_staff_app;',
);
expect(sql).toContain(
'GRANT SELECT, INSERT, UPDATE, DELETE ON staff_sync_change_log TO krona_staff_app;',
);
expect(sql).toContain(
'GRANT USAGE, SELECT ON SEQUENCE staff_sync_change_log_cursor_seq_seq TO krona_staff_app;',
);
expect(sql).toContain(
'GRANT SELECT, INSERT, UPDATE, DELETE ON staff_sync_command_inbox TO krona_staff_app;',
);
expect(sql).toContain(
'GRANT SELECT, INSERT, UPDATE, DELETE ON staff_sync_device_checkpoints TO krona_staff_app;',
);
});
it('declares and grants retention cleanup function', () => {
expect(sql).toContain('CREATE OR REPLACE FUNCTION staff_sync_cleanup_retention');
expect(sql).toContain(
'GRANT EXECUTE ON FUNCTION staff_sync_cleanup_retention(INTEGER) TO krona_staff_app;',
);
});
});