110 lines
4.4 KiB
TypeScript
110 lines
4.4 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { MediaService } from '../src/media/media.service.js';
|
|
|
|
vi.mock('../src/media/product-image-download.js', async (importOriginal) => {
|
|
const original =
|
|
await importOriginal<
|
|
typeof import('../src/media/product-image-download.js')
|
|
>();
|
|
return {
|
|
...original,
|
|
downloadProductImage: vi.fn(async () => ({
|
|
bytes: Buffer.from('validated-image'),
|
|
contentType: 'image/png',
|
|
width: 800,
|
|
height: 800,
|
|
})),
|
|
};
|
|
});
|
|
|
|
describe('网络商品图片导入', () => {
|
|
it('复用同工作空间素材并返回正式 ID 与短时预览地址', async () => {
|
|
const productId = '733d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const assetId = '7c910684-7070-4f36-8a7b-b10bd91acd79';
|
|
const bytes = Buffer.from('validated-image');
|
|
const checksum = createHash('sha256').update(bytes).digest('hex');
|
|
const query = vi.fn(async (sql: string, args?: unknown[]) => {
|
|
if (sql.includes('FROM atlas.products')) {
|
|
expect(args).toEqual([productId]);
|
|
return { rows: [{ id: productId }] };
|
|
}
|
|
if (sql.includes('WHERE checksum_sha256 = $1')) {
|
|
expect(args).toEqual([checksum]);
|
|
return { rows: [{ id: assetId }] };
|
|
}
|
|
if (sql.includes('INSERT INTO atlas.product_image_imports')) {
|
|
expect(args?.[0]).toBe('https://example.com/photo.png');
|
|
expect(args?.[1]).toBe(assetId);
|
|
expect(args?.[3]).toBe(productId);
|
|
expect(sql).toContain('ON CONFLICT (workspace_id, product_id, source_url)');
|
|
return { rows: [] };
|
|
}
|
|
if (sql.includes('SELECT id, content_type')) {
|
|
return {
|
|
rows: [
|
|
{
|
|
id: assetId,
|
|
content_type: 'image/png',
|
|
byte_size: '15',
|
|
width_px: 800,
|
|
height_px: 800,
|
|
created_at: new Date('2026-09-24T00:00:00Z'),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
if (sql.includes('SELECT storage_provider, bucket, object_key')) {
|
|
return {
|
|
rows: [
|
|
{
|
|
storage_provider: 'local_minio',
|
|
bucket: 'test',
|
|
object_key: 'product-imports/existing.png',
|
|
},
|
|
],
|
|
};
|
|
}
|
|
throw new Error(`未知 SQL: ${sql}`);
|
|
});
|
|
const run = vi.fn(
|
|
async (
|
|
request: { path: string; key: string },
|
|
callback: (tx: { query: typeof query }) => Promise<unknown>,
|
|
) => {
|
|
expect(request.path).toBe('/api/media-assets/product-imports');
|
|
expect(request.key).toBe('idempotency-key');
|
|
return callback({ query });
|
|
},
|
|
);
|
|
const service = new MediaService(
|
|
{ run } as unknown as ConstructorParameters<typeof MediaService>[0],
|
|
{} as ConstructorParameters<typeof MediaService>[1],
|
|
{
|
|
OBJECT_STORAGE_ENDPOINT: 'http://127.0.0.1:9000',
|
|
OBJECT_STORAGE_REGION: 'us-east-1',
|
|
OBJECT_STORAGE_BUCKET: 'test',
|
|
objectStorageForcePathStyle: true,
|
|
objectStorageAccessKey: 'test',
|
|
objectStorageSecretKey: 'test',
|
|
} as ConstructorParameters<typeof MediaService>[2],
|
|
);
|
|
const result = await service.importProductImage(
|
|
'workspace-id',
|
|
'user-id',
|
|
'idempotency-key',
|
|
{ url: 'https://example.com/photo.png', product_id: productId },
|
|
);
|
|
const body = result.body as {
|
|
data: { id: string; source_url: string; preview_url: string };
|
|
};
|
|
expect(result.status).toBe(201);
|
|
expect(body.data).toMatchObject({
|
|
id: assetId,
|
|
source_url: 'https://example.com/photo.png',
|
|
});
|
|
expect(body.data.preview_url).toContain('product-imports/existing.png');
|
|
expect(query).toHaveBeenCalledTimes(5);
|
|
});
|
|
});
|