feat: 完成分类、worksheet 接口开发,资源迁移到七牛
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import 'reflect-metadata';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { test } from 'node:test';
|
||||
import { PrismaService } from '../src/database/prisma.service';
|
||||
import { WorksheetImporter } from '../src/imports/worksheet-import';
|
||||
import { testConfig } from './test-config';
|
||||
import { worksheetFixture as fixture } from './worksheet-import.fixture';
|
||||
|
||||
const databaseUrl = process.env.TEST_DATABASE_URL;
|
||||
test(
|
||||
'真实 PostgreSQL:题型元数据导入、待迁移资产、对账、幂等与整批回滚',
|
||||
{ skip: !databaseUrl },
|
||||
async (context) => {
|
||||
const url = new URL(databaseUrl!);
|
||||
assert.ok(['127.0.0.1', 'localhost'].includes(url.hostname));
|
||||
assert.match(url.pathname, /^\/doodle_auth_test_[a-f0-9]{32}$/);
|
||||
const db = new PrismaService({
|
||||
...testConfig,
|
||||
databaseUrl: databaseUrl!,
|
||||
});
|
||||
context.after(() => db.onModuleDestroy());
|
||||
const prisma = db.client;
|
||||
const importer = new WorksheetImporter(db);
|
||||
await prisma.category.create({
|
||||
data: {
|
||||
status: 'active',
|
||||
id: fixture.category,
|
||||
name: '导入验证',
|
||||
icon: '📐',
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
const run = (rows: unknown[], dryRun = false, metadataOnly = true) => {
|
||||
const source = rows.map((row) => JSON.stringify(row)).join('\n');
|
||||
return importer.importExport(source, {
|
||||
dryRun,
|
||||
metadataOnly,
|
||||
requestId: randomUUID(),
|
||||
sourceSha256: createHash('sha256').update(source).digest('hex'),
|
||||
});
|
||||
};
|
||||
const counts = async () => ({
|
||||
assets: await prisma.asset.count(),
|
||||
worksheets: await prisma.worksheet.count(),
|
||||
audits: await prisma.adminAuditLog.count(),
|
||||
});
|
||||
|
||||
await context.test(
|
||||
'默认拒绝未就绪预览;预演及无效分类不写任何数据',
|
||||
async () => {
|
||||
const before = await counts();
|
||||
await assert.rejects(run([fixture], false, false), {
|
||||
status: 409,
|
||||
});
|
||||
await assert.rejects(
|
||||
run([{ ...fixture, category: 'missing-category' }]),
|
||||
{ status: 400 },
|
||||
);
|
||||
const preview = await run([fixture], true);
|
||||
assert.equal(preview.inserted, 1);
|
||||
assert.equal(preview.pendingPreviews, 1);
|
||||
assert.equal(preview.readyForPublicUse, false);
|
||||
assert.deepEqual(await counts(), before);
|
||||
},
|
||||
);
|
||||
await context.test(
|
||||
'同批保存原 ID、原始计数、时间、旧年级及 pending 资产,不伪造资源或年级关系',
|
||||
async () => {
|
||||
const report = await run([{ ...fixture, grade: 23 }]);
|
||||
assert.equal(report.inserted, 1);
|
||||
assert.equal(report.assetsInserted, 1);
|
||||
assert.equal(report.gradeRelationsInserted, 0);
|
||||
assert.equal(report.warnings[0]?.originalValue, 23);
|
||||
const row = await prisma.worksheet.findUniqueOrThrow({
|
||||
where: { id: fixture._id },
|
||||
});
|
||||
assert.equal(row.grade, 23);
|
||||
assert.equal(row.downloads, 23n);
|
||||
assert.equal(row.downloadsSeed, 12n);
|
||||
assert.equal(
|
||||
row.createdAt.toISOString(),
|
||||
fixture.createdAt.$date,
|
||||
);
|
||||
assert.equal(
|
||||
row.updatedAt.toISOString(),
|
||||
fixture.updatedAt.$date,
|
||||
);
|
||||
assert.equal(row.firstPublishedAt, null);
|
||||
assert.equal(row.contentUpdatedAt, null);
|
||||
assert.equal(row.status, 'active');
|
||||
const asset = await prisma.asset.findUniqueOrThrow({
|
||||
where: { id: row.previewAssetId },
|
||||
});
|
||||
assert.equal(asset.sourceRef, fixture.previewImg);
|
||||
assert.equal(
|
||||
asset.sourceRefHash,
|
||||
createHash('sha256')
|
||||
.update(fixture.previewImg)
|
||||
.digest('hex'),
|
||||
);
|
||||
assert.equal(asset.status, 'pending');
|
||||
assert.equal(asset.objectKey, null);
|
||||
assert.equal(asset.bucket, null);
|
||||
assert.equal(asset.mimeType, null);
|
||||
assert.equal(
|
||||
await prisma.worksheetGrade.count({
|
||||
where: { worksheetId: fixture._id },
|
||||
}),
|
||||
0,
|
||||
);
|
||||
assert.equal(
|
||||
await prisma.worksheetResource.count({
|
||||
where: { worksheetId: fixture._id },
|
||||
}),
|
||||
0,
|
||||
);
|
||||
const audit = await prisma.adminAuditLog.findFirstOrThrow({
|
||||
where: {
|
||||
resourceId: fixture._id,
|
||||
action: 'worksheet.import',
|
||||
},
|
||||
});
|
||||
assert.equal(audit.actorType, 'migration');
|
||||
assert.equal(
|
||||
(audit.afterData as { downloads: string }).downloads,
|
||||
'23',
|
||||
);
|
||||
},
|
||||
);
|
||||
await context.test(
|
||||
'重导不新增或更新时间,共享预览复用资产;不同旧快照禁止覆盖',
|
||||
async () => {
|
||||
const before = await counts();
|
||||
const stored = await prisma.worksheet.findUniqueOrThrow({
|
||||
where: { id: fixture._id },
|
||||
});
|
||||
const repeated = await run([{ ...fixture, grade: 23 }]);
|
||||
assert.equal(repeated.unchanged, 1);
|
||||
assert.equal(repeated.assetsInserted, 0);
|
||||
assert.deepEqual(await counts(), before);
|
||||
assert.deepEqual(
|
||||
await prisma.worksheet.findUniqueOrThrow({
|
||||
where: { id: fixture._id },
|
||||
}),
|
||||
stored,
|
||||
);
|
||||
const shared = await run([
|
||||
{ ...fixture, _id: 'worksheet-import-shared' },
|
||||
]);
|
||||
assert.equal(shared.assetsInserted, 0);
|
||||
assert.equal(shared.assetsReused, 1);
|
||||
assert.equal(await prisma.asset.count(), before.assets);
|
||||
await assert.rejects(
|
||||
run([{ ...fixture, grade: 23, downloads: 24 }]),
|
||||
{ status: 409 },
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await prisma.worksheet.findUniqueOrThrow({
|
||||
where: { id: fixture._id },
|
||||
})
|
||||
).downloads,
|
||||
23n,
|
||||
);
|
||||
},
|
||||
);
|
||||
await context.test(
|
||||
'后半批冲突会回滚前面新增题型、资产及审计',
|
||||
async () => {
|
||||
const before = await counts();
|
||||
await assert.rejects(
|
||||
run([
|
||||
{
|
||||
...fixture,
|
||||
_id: 'worksheet-import-rollback',
|
||||
previewImg:
|
||||
'cloud://fixture-env/assets/previews/rollback.jpg',
|
||||
},
|
||||
{ ...fixture, grade: 23, title: '禁止覆盖' },
|
||||
]),
|
||||
{ status: 409 },
|
||||
);
|
||||
assert.deepEqual(await counts(), before);
|
||||
assert.equal(
|
||||
await prisma.worksheet.count({
|
||||
where: { id: 'worksheet-import-rollback' },
|
||||
}),
|
||||
0,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user