Files
photo-live/services/worker/test/reset.ts
T

118 lines
3.6 KiB
TypeScript
Raw Normal View History

// services/worker/test/reset.ts
// worker e2e 共用隔离工具(镜像 services/api/test/reset.ts):每个 e2e spec 的
// beforeAll 调用 resetDb()。MySQL 逐表 TRUNCATE(SET FOREIGN_KEY_CHECKS=0 包裹,
// users 被子表引用);MinIO 侧清理 photo-live/dev 前缀对象(worker 的 variants
// 写入与 api 的 staging/originals 同前缀)。S3Client 为 devDependencies
// (与 @photo/storage 内部同款)——StoragePort 不含列举/删除(YAGNI),隔离工具
// 直连 S3 协议。
import {
DeleteObjectsCommand,
ListObjectsV2Command,
S3Client,
} from '@aws-sdk/client-s3';
import {
PrismaClient,
USAGE_THRESHOLDS_SINGLETON_ID,
} from '@photo/db-cn';
export const RESET_TABLES: readonly string[] = [
// 与 api/test/reset.ts 同表同序(子表在前)。
'outbox_events',
'photo_jobs',
'photo_variants',
'upload_sessions',
'photos',
// 用量域(P4a Task 5,同 api/test/reset.ts:告警先于阈值单行,重种在尾部)。
'usage_alerts',
'usage_thresholds',
// 水印域(P4a Task 3,同 api/test/reset.ts)。
'watermark_jobs',
'watermark_configs',
'category_translations',
'categories',
'album_assets',
'album_translations',
'album_members',
'albums',
'audit_logs',
'sessions',
'users',
];
/** e2e 对象隔离边界:与 api src/uploads/support.ts 的 OBJECT_KEY_ROOT 一致。 */
const RESET_OBJECT_PREFIX = 'photo-live/dev/';
/** 删除 MinIO 中 RESET_OBJECT_PREFIX 前缀的全部对象(分页列举 + 批量删除)。 */
async function resetMinioObjects(): Promise<void> {
const required = [
'S3_ENDPOINT',
'S3_REGION',
'S3_ACCESS_KEY',
'S3_SECRET_KEY',
'S3_BUCKET',
] as const;
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(`resetMinioObjects: 缺少环境变量 ${missing.join(', ')}`);
}
const client = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY as string,
secretAccessKey: process.env.S3_SECRET_KEY as string,
},
forcePathStyle: true,
});
const bucket = process.env.S3_BUCKET as string;
try {
for (;;) {
const listed = await client.send(
new ListObjectsV2Command({
Bucket: bucket,
Prefix: RESET_OBJECT_PREFIX,
MaxKeys: 1000,
}),
);
const objects = (listed.Contents ?? []).filter(
(obj) => typeof obj.Key === 'string',
);
if (objects.length === 0) {
break;
}
await client.send(
new DeleteObjectsCommand({
Bucket: bucket,
Delete: {
Objects: objects.map((obj) => ({ Key: obj.Key as string })),
Quiet: true,
},
}),
);
if (!listed.IsTruncated) {
break;
}
}
} finally {
await client.destroy();
}
}
export async function resetDb(): Promise<void> {
await resetMinioObjects();
const prisma = new PrismaClient();
try {
await prisma.$executeRawUnsafe('SET FOREIGN_KEY_CHECKS = 0');
for (const table of RESET_TABLES) {
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${table}`);
}
// 用量阈值单行重种(同 api/test/reset.ts:保证「单行恒存在」不变量,
// 并清除跨 spec 的阈值残留)。
await prisma.$executeRaw`INSERT INTO usage_thresholds (id, storageBytes, perAlbumBytes, updatedAt)
VALUES (${USAGE_THRESHOLDS_SINGLETON_ID}, NULL, NULL, CURRENT_TIMESTAMP(6))`;
} finally {
await prisma.$executeRawUnsafe('SET FOREIGN_KEY_CHECKS = 1');
await prisma.$disconnect();
}
}