Files
ftb-project-management/apps/server/prisma/v22-partitioned-schema.spec.ts
2026-07-03 10:23:18 +08:00

105 lines
4.0 KiB
TypeScript

import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const migrationPath = join(
process.cwd(),
'prisma',
'migrations',
'20260703000000_v22_partitioned_domain_schema',
'migration.sql',
);
function readMigrationSql() {
if (!existsSync(migrationPath)) {
throw new Error(`Missing V2.2 migration: ${migrationPath}`);
}
return readFileSync(migrationPath, 'utf8');
}
function expectPartitionedTable(
sql: string,
table: string,
method: 'HASH' | 'RANGE',
key: string,
primaryKey: string[],
) {
const quotedPrimaryKey = primaryKey.map((column) => `"${column}"`).join(', ');
expect(sql).toMatch(
new RegExp(
`CREATE TABLE "${table}"[\\s\\S]*CONSTRAINT "${table}_pkey" PRIMARY KEY \\(${quotedPrimaryKey}\\)[\\s\\S]*PARTITION BY ${method} \\("${key}"\\);`,
),
);
}
function expectHashPartitions(sql: string, table: string, count: number) {
const matches = sql.match(
new RegExp(`PARTITION OF "${table}" FOR VALUES WITH \\(MODULUS ${count}, REMAINDER`, 'g'),
);
expect(matches).toHaveLength(count);
}
describe('V2.2 partitioned domain schema migration', () => {
it('keeps every hash partitioned business table primary key anchored by the partition key', () => {
const sql = readMigrationSql();
expectPartitionedTable(sql, 'requirements', 'HASH', 'product_id', ['id', 'product_id']);
expectPartitionedTable(sql, 'dev_tasks', 'HASH', 'version_id', ['id', 'version_id']);
expectPartitionedTable(sql, 'test_cases', 'HASH', 'version_id', ['id', 'version_id']);
expectPartitionedTable(sql, 'bugs', 'HASH', 'version_id', ['id', 'version_id']);
expectHashPartitions(sql, 'requirements', 16);
expectHashPartitions(sql, 'dev_tasks', 16);
expectHashPartitions(sql, 'test_cases', 16);
expectHashPartitions(sql, 'bugs', 16);
});
it('includes partition keys in business unique constraints', () => {
const sql = readMigrationSql();
expect(sql).toMatch(/CONSTRAINT "requirements_product_id_code_key" UNIQUE \("product_id", "code"\)/);
expect(sql).toMatch(/CONSTRAINT "dev_tasks_version_id_code_key" UNIQUE \("version_id", "code"\)/);
expect(sql).toMatch(/CONSTRAINT "test_cases_version_id_code_key" UNIQUE \("version_id", "code"\)/);
expect(sql).toMatch(/CONSTRAINT "bugs_version_id_code_key" UNIQUE \("version_id", "code"\)/);
});
it('uses composite foreign keys when a referenced table is partitioned', () => {
const sql = readMigrationSql();
expect(sql).toMatch(
/FOREIGN KEY \("requirement_id", "requirement_product_id"\) REFERENCES "requirements"\("id", "product_id"\)/,
);
expect(sql).toMatch(
/FOREIGN KEY \("test_case_id", "test_case_version_id"\) REFERENCES "test_cases"\("id", "version_id"\)/,
);
});
it('range partitions append-only evidence and snapshot tables by created_at', () => {
const sql = readMigrationSql();
expectPartitionedTable(sql, 'work_activities', 'RANGE', 'created_at', ['id', 'created_at']);
expectPartitionedTable(sql, 'task_worklogs', 'RANGE', 'created_at', ['id', 'created_at']);
expectPartitionedTable(sql, 'overtime_records', 'RANGE', 'created_at', ['id', 'created_at']);
expectPartitionedTable(sql, 'xiaobao_risk_snapshots', 'RANGE', 'created_at', ['id', 'created_at']);
expectPartitionedTable(sql, 'ai_logs', 'RANGE', 'created_at', ['id', 'created_at']);
for (const table of [
'work_activities',
'task_worklogs',
'overtime_records',
'xiaobao_risk_snapshots',
'ai_logs',
]) {
expect(sql).toMatch(new RegExp(`CREATE TABLE "${table}_default" PARTITION OF "${table}" DEFAULT;`));
}
});
it('keeps Xiaobao current summaries as one row per version instead of partitioned history', () => {
const sql = readMigrationSql();
expect(sql).toMatch(
/CREATE TABLE "xiaobao_risk_summaries"[\s\S]*CONSTRAINT "xiaobao_risk_summaries_pkey" PRIMARY KEY \("version_id"\)/,
);
expect(sql).not.toMatch(/CREATE TABLE "xiaobao_risk_summaries"[\s\S]*PARTITION BY/);
});
});