import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import type { AddressInfo } from 'node:net'; import type { Database } from '@atlas/database'; import type { NestExpressApplication } from '@nestjs/platform-express'; import { afterAll, beforeAll, describe, it } from 'vitest'; import { DATABASE } from '../src/database/database.module.js'; import { createApplication } from '../src/main.js'; let app: NestExpressApplication; let database: Database; let baseUrl = ''; let workspaceId = ''; let userId = ''; let token = ''; describe('水印派生 API 集成测试', () => { beforeAll(async () => { Object.assign(process.env, { NODE_ENV: 'test', SWAGGER_ENABLED: 'false', AUTH_REGISTER_IP_LIMIT: '100', AUTH_REGISTER_EMAIL_LIMIT: '100', }); const created = await createApplication(); app = created.app; database = app.get(DATABASE); await app.listen(0, '127.0.0.1'); baseUrl = `http://127.0.0.1:${(app.getHttpServer().address() as AddressInfo).port}`; const response = await fetch(`${baseUrl}/api/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: `watermark-derivative-${randomUUID()}@example.test`, password: 'correct-horse-battery-staple-123', password_confirmation: 'correct-horse-battery-staple-123', }), }); assert.equal(response.status, 201); const body = await response.json() as { data: { user: { id: string }; workspace: { id: string }; access_token: string; } }; userId = body.data.user.id; workspaceId = body.data.workspace.id; token = body.data.access_token; }); afterAll(async () => { if (workspaceId) { await database.workspaceTransaction(workspaceId, async (tx) => { await tx.query('DELETE FROM atlas.idempotency_requests WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.outbox_events WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.operations WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.media_aspect_ratio_derivatives WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.media_watermark_derivatives WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.watermark_template_versions WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.watermark_templates WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.media_assets WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.products WHERE workspace_id = $1', [workspaceId]); await tx.query('DELETE FROM atlas.collection_items WHERE workspace_id = $1', [workspaceId]); }); await database.query('DELETE FROM atlas.workspace_memberships WHERE user_id = $1', [userId]); await database.query('DELETE FROM atlas.workspaces WHERE id = $1', [workspaceId]); await database.query('DELETE FROM atlas.users WHERE id = $1', [userId]); } await app?.close(); }); it('HTTP 创建任务锁定模板版本,结果查询保持工作空间边界', async () => { const { assetId, templateId, productId } = await database.workspaceTransaction(workspaceId, async (tx) => { const item = await tx.query<{ id: string }>( `INSERT INTO atlas.collection_items (workspace_id, source_kind, source_item_key) VALUES ($1, 'manual', $2) RETURNING id`, [workspaceId, `watermark-history-${randomUUID()}`], ); const product = await tx.query<{ id: string }>( `INSERT INTO atlas.products (workspace_id, collection_item_id) VALUES ($1, $2) RETURNING id`, [workspaceId, item.rows[0].id], ); const asset = await tx.query<{ id: string }>( `INSERT INTO atlas.media_assets (workspace_id, storage_provider, bucket, object_key, content_type, byte_size, checksum_sha256, width_px, height_px) VALUES ($1, 'local_minio', 'atlas-media', $2, 'image/jpeg', 100, $3, 120, 100) RETURNING id`, [workspaceId, `test/watermark-${randomUUID()}.jpg`, randomUUID().replaceAll('-', '').padEnd(64, '0')], ); const template = await tx.query<{ id: string }>( `INSERT INTO atlas.watermark_templates (workspace_id, name, kind) VALUES ($1, $2, 'text') RETURNING id`, [workspaceId, `集成测试水印 ${randomUUID()}`], ); await tx.query( `INSERT INTO atlas.watermark_template_versions (workspace_id, template_id, version, kind, text_content, font_code, font_size_px, opacity, position, position_x, position_y, created_by_user_id) VALUES ($1, $2, 1, 'text', 'Atlas', 'NotoSans', 20, 0.5, 'bottom_right', 1, 1, $3)`, [workspaceId, template.rows[0].id, userId], ); return { assetId: asset.rows[0].id, templateId: template.rows[0].id, productId: product.rows[0].id }; }); const key = randomUUID(); const create = () => fetch(`${baseUrl}/api/media-assets/${assetId}/watermark-derivatives`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'Idempotency-Key': key, }, body: JSON.stringify({ watermark_template_id: templateId, product_id: productId }), }); const response = await create(); assert.equal(response.status, 202); const body = await response.json() as { data: { id: string; watermark_template_id: string; watermark_template_version: number; } }; assert.equal(body.data.watermark_template_id, templateId); assert.equal(body.data.watermark_template_version, 1); const replay = await create(); assert.equal(replay.status, 202); assert.equal((await replay.json() as { data: { id: string } }).data.id, body.data.id); const read = await fetch(`${baseUrl}/api/media-watermark-derivatives/${body.data.id}`, { headers: { Authorization: `Bearer ${token}` }, }); assert.equal(read.status, 200); assert.equal((await read.json() as { data: { id: string } }).data.id, body.data.id); const stored = await database.workspaceTransaction(workspaceId, async (tx) => tx.query<{ product_id: string }>( 'SELECT product_id FROM atlas.media_watermark_derivatives WHERE id = $1', [body.data.id], ), ); assert.equal(stored.rows[0]?.product_id, productId); const outputs = await database.workspaceTransaction(workspaceId, async (tx) => { const ids: string[] = []; for (const kind of ['watermark', 'ratio']) { const asset = await tx.query<{ id: string }>( `INSERT INTO atlas.media_assets (workspace_id, storage_provider, bucket, object_key, content_type, byte_size, checksum_sha256, width_px, height_px) VALUES ($1, 'local_minio', 'atlas-media', $2, 'image/jpeg', 100, $3, 1200, 1600) RETURNING id`, [workspaceId, `test/${kind}-${randomUUID()}.jpg`, randomUUID().replaceAll('-', '').padEnd(64, '0')], ); ids.push(asset.rows[0].id); } await tx.query( `INSERT INTO atlas.media_watermark_derivatives (workspace_id, product_id, source_asset_id, watermark_template_id, watermark_template_version, output_asset_id, status, completed_at) VALUES ($1, $2, $3, $4, 1, $5, 'succeeded', now())`, [workspaceId, productId, assetId, templateId, ids[0]], ); await tx.query( `INSERT INTO atlas.media_aspect_ratio_derivatives (workspace_id, product_id, source_asset_id, output_asset_id, status, completed_at) VALUES ($1, $2, $3, $4, 'succeeded', now())`, [workspaceId, productId, assetId, ids[1]], ); return ids; }); const history = await fetch(`${baseUrl}/api/products/${productId}/image-derivatives`, { headers: { Authorization: `Bearer ${token}` }, }); assert.equal(history.status, 200); const records = (await history.json() as { data: Array<{ kind: string; output_asset: { id: string; preview_url: string }; }> }).data; assert.deepEqual(new Set(records.map((record) => record.kind)), new Set(['watermark', 'aspect_ratio_3_4'])); assert.deepEqual(new Set(records.map((record) => record.output_asset.id)), new Set(outputs)); assert.ok(records.every((record) => record.output_asset.preview_url)); }); });