feat: 完成 SKU 图片选择及素材导入后端

This commit is contained in:
R524809
2026-09-24 17:46:34 +08:00
parent d24ce8f4e0
commit e670d06ca6
26 changed files with 2287 additions and 119 deletions
+83
View File
@@ -132,6 +132,14 @@ describe('商品编辑 M3.0–M3.3 HTTP 集成测试', () => {
'DELETE FROM atlas.collection_asset_transfers WHERE workspace_id = $1',
[workspaceId],
);
await tx.query(
'DELETE FROM atlas.product_image_imports WHERE workspace_id = $1',
[workspaceId],
);
await tx.query(
'DELETE FROM atlas.media_upload_intents WHERE workspace_id = $1',
[workspaceId],
);
await tx.query(
'DELETE FROM atlas.product_variants WHERE workspace_id = $1',
[workspaceId],
@@ -1900,6 +1908,81 @@ describe('商品编辑 M3.0–M3.3 HTTP 集成测试', () => {
after.body.data.variants[0].media.map((item: any) => item.asset_id),
[asset],
);
const additionalAssets = await database.workspaceTransaction(workspaceId, async (tx) => {
const ids: string[] = [];
for (let index = 0; index < 29; index += 1) {
const checksum = randomUUID().replaceAll('-', '').repeat(2);
const objectKey = `m36/${randomUUID()}.png`;
const created = 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 (current_setting('app.workspace_id')::uuid, 'test', 'test', $1,
'image/png', 1024, $2, 1200, 900) RETURNING id`,
[objectKey, checksum],
);
const assetId = created.rows[0].id;
ids.push(assetId);
if (index === 0) {
await tx.query(
`INSERT INTO atlas.media_upload_intents
(workspace_id, purpose, storage_provider, bucket, object_key,
expected_content_type, expected_byte_size, expected_checksum_sha256,
status, asset_id, expires_at, completed_at)
VALUES (current_setting('app.workspace_id')::uuid, 'product_image',
'test', 'test', $1, 'image/png', 1024, $2,
'ready', $3, now() + interval '1 hour', now())`,
[objectKey, checksum, assetId],
);
} else {
await tx.query(
`INSERT INTO atlas.product_image_imports
(workspace_id, source_url, asset_id)
VALUES (current_setting('app.workspace_id')::uuid, $1, $2)`,
[`https://example.test/${randomUUID()}.png`, assetId],
);
}
}
return ids;
});
const thirtyImages = {
...payload,
variants: payload.variants.map((item: any) => ({
...item,
media_asset_ids: item.id === variant.id ? [asset, ...additionalAssets] : [],
})),
};
const expanded = await request(`/api/products/${first.productId}/editor-draft`, {
method: 'PUT',
headers: headers({
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': randomUUID(),
'If-Match': `"${saved.body.data.product_revision}:${saved.body.data.profile_revision}"`,
}),
body: JSON.stringify(thirtyImages),
});
assert.equal(expanded.response.status, 200);
const expandedSnapshot = await request(`/api/products/${first.productId}/editor`, {
headers: headers({ 'X-Atlas-Platform-Id': platformId }),
});
assert.equal(expandedSnapshot.body.data.variants[0].media.length, 30);
const uploaded = await request('/api/media-assets/product-uploads', {
headers: headers(),
});
assert.equal(uploaded.response.status, 200);
assert.ok(uploaded.body.data.some((item: any) => item.id === additionalAssets[0]));
const imported = await request('/api/media-assets/product-imports', {
headers: headers(),
});
assert.equal(imported.response.status, 200);
assert.ok(imported.body.data.some((item: any) => item.id === additionalAssets[1]));
const privateImport = await request('/api/media-assets/product-imports', {
method: 'POST',
headers: headers({ 'Idempotency-Key': randomUUID() }),
body: JSON.stringify({ url: 'https://127.0.0.1/private.png' }),
});
assert.equal(privateImport.response.status, 422);
assert.equal(privateImport.body.error.code, 'PRODUCT_IMAGE_IMPORT_INVALID');
});
it('独立保存采集描述会持久化覆盖值并保持幂等', async () => {