419 lines
23 KiB
JavaScript
419 lines
23 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { test } from 'node:test';
|
|
import sharp from 'sharp';
|
|
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
import { createMediaAiEditRequestSchema } from '@atlas/contracts';
|
|
import { classifyAiEditFailure, downloadProviderTaskImage, normalizeAiEditOutput, pinnedLookup, providerImageResult, runMediaAiEdit } from '../src/media-ai-edit.ts';
|
|
import { buildMediaAiPrompt } from '../src/media-ai-prompt.ts';
|
|
|
|
const productId = '733d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
|
|
test('仅扩图在供应商改写中心后仍把原图区域覆盖回结果', async () => {
|
|
const source = await sharp({ create: { width: 600, height: 600, channels: 3, background: '#e1252a' } }).png().toBuffer();
|
|
const generated = await sharp({ create: { width: 1152, height: 1536, channels: 3, background: '#2055d8' } }).png().toBuffer();
|
|
const settings = createMediaAiEditRequestSchema.parse({ product_id: productId, mode: 'outpaint' });
|
|
const output = await normalizeAiEditOutput(generated, settings, source);
|
|
const { data, info } = await sharp(output).raw().toBuffer({ resolveWithObject: true });
|
|
assert.equal(info.width, 1152);
|
|
assert.equal(info.height, 1536);
|
|
const pixel = (x, y) => [...data.subarray((y * info.width + x) * 3, (y * info.width + x) * 3 + 3)];
|
|
assert.ok(pixel(576, 768)[0] > 200, '原图中心仍应为红色');
|
|
assert.ok(pixel(576, 20)[2] > 170, '扩充边缘仍应为蓝色');
|
|
});
|
|
|
|
test('提示词明确区分主图、辅助图和尺寸数值', () => {
|
|
const settings = createMediaAiEditRequestSchema.parse({
|
|
product_id: productId,
|
|
reference_asset_ids: ['50ea00b7-6675-4cc7-b7a2-d45f05c07100'],
|
|
dimensions: { length: 12, unit: 'cm' }, generate_size_chart: true,
|
|
});
|
|
const prompt = buildMediaAiPrompt(settings);
|
|
assert.match(prompt, /Image 1 is the fixed main product image/);
|
|
assert.match(prompt, /Images 2 through 2 are auxiliary references only/);
|
|
assert.match(prompt, /length: 12 cm/);
|
|
assert.match(prompt, /Do not infer other measurements/);
|
|
});
|
|
|
|
test('Pcore 完成结果的空 b64_json 回退到可下载 URL', () => {
|
|
const url = 'https://pcore.ai/v1/images/content/task-example/0';
|
|
assert.equal(providerImageResult({ data: [{ b64_json: '', url }] }), url);
|
|
assert.equal(providerImageResult({ data: [{ b64_json: ' ', url }] }), url);
|
|
assert.deepEqual(providerImageResult({ data: [{ b64_json: '', b64_image: Buffer.from('image').toString('base64'), url }] }),
|
|
Buffer.from('image'));
|
|
assert.deepEqual(providerImageResult({ data: [{ b64_json: Buffer.from('image').toString('base64'), url }] }),
|
|
Buffer.from('image'));
|
|
});
|
|
|
|
test('固定 DNS 回调按 Node 的 all 选项返回地址列表或单个地址', () => {
|
|
const lookup = pinnedLookup('8.8.8.8');
|
|
lookup('pcore.ai', { all: true }, (error, addresses) => {
|
|
assert.equal(error, null);
|
|
assert.deepEqual(addresses, [{ address: '8.8.8.8', family: 4 }]);
|
|
});
|
|
lookup('pcore.ai', { all: false }, (error, address, family) => {
|
|
assert.equal(error, null);
|
|
assert.equal(address, '8.8.8.8');
|
|
assert.equal(family, 4);
|
|
});
|
|
});
|
|
|
|
test('同源 Pcore 任务取图使用配置地址,不发送密钥且禁止重定向', async () => {
|
|
const oldFetch = globalThis.fetch;
|
|
const oldBase = process.env.PCORE_BASE_URL;
|
|
const oldKey = process.env.PCORE_API_KEY;
|
|
process.env.PCORE_BASE_URL = 'https://pcore.ai';
|
|
process.env.PCORE_API_KEY = 'test-key';
|
|
let requests = 0;
|
|
globalThis.fetch = async (url, init) => {
|
|
requests += 1;
|
|
assert.equal(url, 'https://pcore.ai/v1/images/content/provider-123/0');
|
|
assert.equal(init.redirect, 'error');
|
|
assert.equal(init.headers, undefined);
|
|
assert.ok(init.signal);
|
|
return new Response('image');
|
|
};
|
|
try {
|
|
assert.deepEqual(await downloadProviderTaskImage('https://pcore.ai/v1/images/content/provider-123/0', 'provider-123'), Buffer.from('image'));
|
|
for (const url of [
|
|
'https://pcore.ai/v1/images/content/another-task/0',
|
|
'https://pcore.ai/v1/images/content/provider-123/1',
|
|
'https://pcore.ai/v1/images/content/provider-123%2F0/0',
|
|
'https://pcore.ai/v1/images/content/provider-123/0?url=http://localhost',
|
|
'https://pcore.ai/v1/images/content/provider-123/0#fragment',
|
|
'https://user:password@pcore.ai/v1/images/content/provider-123/0',
|
|
'https://pcore.ai/v1/images/edits/provider-123',
|
|
'https://127.0.0.1/v1/images/content/provider-123/0',
|
|
'http://pcore.ai/v1/images/content/provider-123/0',
|
|
]) await assert.rejects(downloadProviderTaskImage(url, 'provider-123'), /结果地址不安全/);
|
|
assert.equal(requests, 1, '非当前任务或非安全地址不能进入供应商内容请求');
|
|
await assert.rejects(downloadProviderTaskImage('https://pcore.ai/v1/images/content/../0', '..'), /结果地址不安全/);
|
|
globalThis.fetch = async () => new Response(null, { status: 302, headers: { Location: 'https://127.0.0.1/' } });
|
|
await assert.rejects(downloadProviderTaskImage('https://pcore.ai/v1/images/content/provider-123/0', 'provider-123'), /HTTP 302/);
|
|
globalThis.fetch = async () => new Response('oversize', { headers: { 'Content-Length': String(26 * 1024 * 1024) } });
|
|
await assert.rejects(downloadProviderTaskImage('https://pcore.ai/v1/images/content/provider-123/0', 'provider-123'), /超过处理上限/);
|
|
globalThis.fetch = async () => new Response(new ReadableStream({ start(controller) {
|
|
controller.enqueue(new Uint8Array(25 * 1024 * 1024));
|
|
controller.enqueue(new Uint8Array(1));
|
|
controller.close();
|
|
} }));
|
|
await assert.rejects(downloadProviderTaskImage('https://pcore.ai/v1/images/content/provider-123/0', 'provider-123'), /超过处理上限/);
|
|
globalThis.fetch = async () => { throw new TypeError('fetch failed'); };
|
|
await assert.rejects(downloadProviderTaskImage('https://pcore.ai/v1/images/content/provider-123/0', 'provider-123'), (error) => {
|
|
assert.equal(classifyAiEditFailure(error).source, 'pcore');
|
|
assert.equal(error.message, 'Pcore 结果下载连接失败');
|
|
return true;
|
|
});
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
if (oldBase === undefined) delete process.env.PCORE_BASE_URL; else process.env.PCORE_BASE_URL = oldBase;
|
|
if (oldKey === undefined) delete process.env.PCORE_API_KEY; else process.env.PCORE_API_KEY = oldKey;
|
|
}
|
|
});
|
|
|
|
test('异步供应商任务登记独立素材,完成结果兼容 base64 和同源图片接口', async () => {
|
|
const sourceId = '664d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const referenceId = '6ebfe347-bc77-4270-8213-b2021271c6b2';
|
|
const outputId = '113d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const editId = '68db226f-3fcb-4579-ab9e-4932b1d84067';
|
|
const source = await sharp({ create: { width: 600, height: 800, channels: 3, background: '#ad3222' } }).jpeg().toBuffer();
|
|
const reference = await sharp({ create: { width: 400, height: 400, channels: 4, background: '#2452af80' } }).png().toBuffer();
|
|
const generated = await sharp({ create: { width: 1152, height: 1536, channels: 3, background: '#2f72b7' } }).png().toBuffer();
|
|
const settings = createMediaAiEditRequestSchema.parse({ product_id: productId,
|
|
reference_asset_ids: [referenceId], prompt: '保留商品,使用明亮背景' });
|
|
const task = { status: 'queued', source_asset_id: sourceId, reference_asset_ids: [referenceId], settings,
|
|
provider_task_id: null, created_at: new Date() };
|
|
const outbox = [];
|
|
const stored = [];
|
|
const database = { workspaceTransaction: async (_workspaceId, work) => work({
|
|
query: async (sql, params = []) => {
|
|
if (sql.includes('SELECT status, source_asset_id, reference_asset_ids')) return { rows: [{ ...task }] };
|
|
if (sql.includes('SELECT id, storage_provider, bucket, object_key')) return { rows: [
|
|
{ id: sourceId, storage_provider: 'local_minio', bucket: 'atlas-assets', object_key: 'main.jpg' },
|
|
{ id: referenceId, storage_provider: 'local_minio', bucket: 'atlas-assets', object_key: 'reference.jpg' },
|
|
] };
|
|
if (sql.includes('SELECT status, generation_attempt FROM atlas.media_ai_edits')) return { rows: [{ status: task.status, generation_attempt: 1 }] };
|
|
if (sql.includes('SELECT id FROM atlas.media_assets WHERE checksum_sha256')) return { rows: [] };
|
|
if (sql.includes('INSERT INTO atlas.media_assets')) return { rows: [{ id: outputId }] };
|
|
if (sql.includes('INSERT INTO atlas.outbox_events')) outbox.push(sql);
|
|
if (sql.includes("SET status = 'running'")) task.status = 'running';
|
|
if (sql.includes('SET provider_task_id =')) task.provider_task_id = params[1];
|
|
if (sql.includes("SET status = 'succeeded'")) task.status = 'succeeded';
|
|
return { rows: [] };
|
|
},
|
|
}) };
|
|
const storage = { send: async (command) => {
|
|
if (command instanceof GetObjectCommand) return { Body: (async function* () {
|
|
yield command.input.Key === 'main.jpg' ? source : reference;
|
|
})() };
|
|
if (command instanceof PutObjectCommand) { stored.push(command.input); return {}; }
|
|
throw new Error('unexpected object storage command');
|
|
} };
|
|
const oldFetch = globalThis.fetch;
|
|
const oldBase = process.env.PCORE_BASE_URL;
|
|
const oldKey = process.env.PCORE_API_KEY;
|
|
process.env.PCORE_BASE_URL = 'https://pcore.ai/v1';
|
|
process.env.PCORE_API_KEY = 'test-key';
|
|
let requests = 0;
|
|
let useContentUrl = false;
|
|
globalThis.fetch = async (url, init) => {
|
|
requests += 1;
|
|
if (init.method === 'POST') {
|
|
assert.equal(url, 'https://pcore.ai/v1/images/edits');
|
|
assert.equal(init.headers['Content-Type'], 'application/json');
|
|
const body = JSON.parse(init.body);
|
|
assert.equal(body.model, 'gpt-image-2');
|
|
assert.equal(body.size, '1152x1536');
|
|
assert.equal(body.n, 1);
|
|
assert.equal(body.async, true);
|
|
assert.equal(body.images.length, 2);
|
|
assert.deepEqual(body.images, [
|
|
{ image_url: `data:image/jpeg;base64,${source.toString('base64')}` },
|
|
{ image_url: `data:image/png;base64,${reference.toString('base64')}` },
|
|
]);
|
|
assert.equal(body.mask, undefined);
|
|
return Response.json({ task_id: 'provider-123' });
|
|
}
|
|
if (url === 'https://pcore.ai/v1/images/content/provider-123/0') {
|
|
assert.equal(init.headers, undefined);
|
|
assert.equal(init.redirect, 'error');
|
|
return new Response(generated);
|
|
}
|
|
assert.equal(url, 'https://pcore.ai/v1/images/edits/provider-123');
|
|
return Response.json({ status: 'completed', data: [useContentUrl
|
|
? { b64_json: '', url: 'https://pcore.ai/v1/images/content/provider-123/0' }
|
|
: { b64_json: generated.toString('base64') }] });
|
|
};
|
|
try {
|
|
const job = { workspaceId: '30cf54b7-06a3-4e5f-a5fc-d74165115094', payload: { edit_id: editId } };
|
|
await runMediaAiEdit(database, storage, job);
|
|
assert.equal(task.status, 'running');
|
|
assert.equal(task.provider_task_id, 'provider-123');
|
|
assert.equal(outbox.length, 1);
|
|
await runMediaAiEdit(database, storage, job);
|
|
assert.equal(task.status, 'succeeded');
|
|
assert.equal(stored.length, 1);
|
|
assert.equal(stored[0].ContentType, 'image/jpeg');
|
|
assert.equal(requests, 2);
|
|
task.status = 'running';
|
|
useContentUrl = true;
|
|
await runMediaAiEdit(database, storage, job);
|
|
assert.equal(task.status, 'succeeded');
|
|
assert.equal(stored.length, 2);
|
|
assert.equal(requests, 4, '已保存任务只查询状态和取图,不重新提交生成');
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
if (oldBase === undefined) delete process.env.PCORE_BASE_URL; else process.env.PCORE_BASE_URL = oldBase;
|
|
if (oldKey === undefined) delete process.env.PCORE_API_KEY; else process.env.PCORE_API_KEY = oldKey;
|
|
}
|
|
});
|
|
|
|
test('仅扩图以 JSON 图片和蒙版对象提交 Pcore', async () => {
|
|
const sourceId = '664d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const editId = '68db226f-3fcb-4579-ab9e-4932b1d84067';
|
|
const source = await sharp({ create: { width: 600, height: 600, channels: 3, background: '#ad3222' } }).jpeg().toBuffer();
|
|
const settings = createMediaAiEditRequestSchema.parse({ product_id: productId, mode: 'outpaint' });
|
|
const database = { workspaceTransaction: async (_workspaceId, work) => work({
|
|
query: async (sql) => {
|
|
if (sql.includes('SELECT status, source_asset_id, reference_asset_ids')) return { rows: [{
|
|
status: 'queued', source_asset_id: sourceId, reference_asset_ids: [], settings,
|
|
provider_task_id: null, created_at: new Date(),
|
|
}] };
|
|
if (sql.includes('SELECT id, storage_provider, bucket, object_key')) return { rows: [{
|
|
id: sourceId, storage_provider: 'local_minio', bucket: 'atlas-assets', object_key: 'main.jpg',
|
|
}] };
|
|
return { rows: [] };
|
|
},
|
|
}) };
|
|
const storage = { send: async () => ({ Body: (async function* () { yield source; })() }) };
|
|
const oldFetch = globalThis.fetch;
|
|
const oldBase = process.env.PCORE_BASE_URL;
|
|
const oldKey = process.env.PCORE_API_KEY;
|
|
process.env.PCORE_BASE_URL = 'https://pcore.ai';
|
|
process.env.PCORE_API_KEY = 'test-key';
|
|
globalThis.fetch = async (_url, init) => {
|
|
const body = JSON.parse(init.body);
|
|
assert.equal(init.headers['Content-Type'], 'application/json');
|
|
assert.equal(body.images.length, 1);
|
|
assert.match(body.images[0].image_url, /^data:image\/png;base64,/);
|
|
assert.match(body.mask.image_url, /^data:image\/png;base64,/);
|
|
const image = Buffer.from(body.images[0].image_url.split(',')[1], 'base64');
|
|
const mask = Buffer.from(body.mask.image_url.split(',')[1], 'base64');
|
|
assert.equal((await sharp(image).metadata()).width, 1152);
|
|
assert.equal((await sharp(mask).metadata()).height, 1536);
|
|
return Response.json({ task_id: 'provider-outpaint' });
|
|
};
|
|
try {
|
|
await runMediaAiEdit(database, storage, {
|
|
workspaceId: '30cf54b7-06a3-4e5f-a5fc-d74165115094', payload: { edit_id: editId },
|
|
});
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
if (oldBase === undefined) delete process.env.PCORE_BASE_URL; else process.env.PCORE_BASE_URL = oldBase;
|
|
if (oldKey === undefined) delete process.env.PCORE_API_KEY; else process.env.PCORE_API_KEY = oldKey;
|
|
}
|
|
});
|
|
|
|
test('Pcore 终态失败不重复轮询,泛化错误不会误判成内容拒绝', async () => {
|
|
const sourceId = '664d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const editId = '68db226f-3fcb-4579-ab9e-4932b1d84067';
|
|
const settings = createMediaAiEditRequestSchema.parse({ product_id: productId });
|
|
let status = 'running';
|
|
let failureCode = null;
|
|
let providerErrorRaw = null;
|
|
let polls = 0;
|
|
let providerError = {
|
|
code: 'failed', message: '您的请求无法用于生成图像。该请求可能因安全政策被拦截。',
|
|
};
|
|
const database = { workspaceTransaction: async (_workspaceId, work) => work({
|
|
query: async (sql, params = []) => {
|
|
if (sql.includes('SELECT status, source_asset_id, reference_asset_ids')) return { rows: [{
|
|
status, source_asset_id: sourceId, reference_asset_ids: [], settings,
|
|
provider_task_id: 'provider-123', created_at: new Date(),
|
|
}] };
|
|
if (sql.includes('SELECT status, generation_attempt FROM atlas.media_ai_edits')) return { rows: [{ status, generation_attempt: 1 }] };
|
|
if (sql.includes('SELECT id, storage_provider, bucket, object_key')) return { rows: [{
|
|
id: sourceId, storage_provider: 'local_minio', bucket: 'atlas-assets', object_key: 'main.jpg',
|
|
}] };
|
|
if (sql.includes("SET status = 'failed'")) {
|
|
status = 'failed';
|
|
failureCode = params[1];
|
|
if (sql.includes('atlas.operations')) {
|
|
providerErrorRaw = params[6];
|
|
assert.equal(params[3], 'pcore');
|
|
assert.equal(params[4], 'provider_polling');
|
|
}
|
|
}
|
|
return { rows: [] };
|
|
},
|
|
}) };
|
|
const oldFetch = globalThis.fetch;
|
|
const oldBase = process.env.PCORE_BASE_URL;
|
|
const oldKey = process.env.PCORE_API_KEY;
|
|
process.env.PCORE_BASE_URL = 'https://pcore.ai';
|
|
process.env.PCORE_API_KEY = 'test-key';
|
|
globalThis.fetch = async (url) => {
|
|
polls += 1;
|
|
assert.equal(url, 'https://pcore.ai/v1/images/edits/provider-123');
|
|
return Response.json({ status: 'failed', error: providerError });
|
|
};
|
|
try {
|
|
const job = { workspaceId: '30cf54b7-06a3-4e5f-a5fc-d74165115094', payload: { edit_id: editId } };
|
|
await runMediaAiEdit(database, {}, job);
|
|
assert.equal(status, 'failed');
|
|
assert.equal(failureCode, 'AI_IMAGE_PROVIDER_FAILED');
|
|
assert.equal(providerErrorRaw, JSON.stringify(providerError));
|
|
assert.equal(polls, 1);
|
|
status = 'running';
|
|
providerError = { code: 'content_policy_violation', message: 'The image was blocked.' };
|
|
await runMediaAiEdit(database, {}, job);
|
|
assert.equal(failureCode, 'AI_IMAGE_CONTENT_REJECTED');
|
|
assert.equal(providerErrorRaw, JSON.stringify(providerError));
|
|
assert.equal(polls, 2);
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
if (oldBase === undefined) delete process.env.PCORE_BASE_URL; else process.env.PCORE_BASE_URL = oldBase;
|
|
if (oldKey === undefined) delete process.env.PCORE_API_KEY; else process.env.PCORE_API_KEY = oldKey;
|
|
}
|
|
});
|
|
|
|
test('Pcore 提交时返回错误体会保留原始内容', async () => {
|
|
const sourceId = '664d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const editId = '68db226f-3fcb-4579-ab9e-4932b1d84067';
|
|
const source = await sharp({ create: { width: 600, height: 800, channels: 3, background: '#ad3222' } }).jpeg().toBuffer();
|
|
const settings = createMediaAiEditRequestSchema.parse({ product_id: productId });
|
|
const database = { workspaceTransaction: async (_workspaceId, work) => work({
|
|
query: async (sql) => {
|
|
if (sql.includes('SELECT status, source_asset_id, reference_asset_ids')) return { rows: [{
|
|
status: 'queued', source_asset_id: sourceId, reference_asset_ids: [], settings,
|
|
provider_task_id: null, created_at: new Date(),
|
|
}] };
|
|
if (sql.includes('SELECT id, storage_provider, bucket, object_key')) return { rows: [{
|
|
id: sourceId, storage_provider: 'local_minio', bucket: 'atlas-assets', object_key: 'main.jpg',
|
|
}] };
|
|
return { rows: [] };
|
|
},
|
|
}) };
|
|
const storage = { send: async () => ({ Body: (async function* () { yield source; })() }) };
|
|
const oldFetch = globalThis.fetch;
|
|
const oldBase = process.env.PCORE_BASE_URL;
|
|
const oldKey = process.env.PCORE_API_KEY;
|
|
process.env.PCORE_BASE_URL = 'https://pcore.ai';
|
|
process.env.PCORE_API_KEY = 'test-key';
|
|
const providerError = { code: 'invalid_request_error', message: '图像文件无效' };
|
|
globalThis.fetch = async () => Response.json({ status: 'failed', error: providerError });
|
|
try {
|
|
const job = { workspaceId: '30cf54b7-06a3-4e5f-a5fc-d74165115094', payload: { edit_id: editId } };
|
|
await assert.rejects(runMediaAiEdit(database, storage, job), (error) => {
|
|
assert.deepEqual(classifyAiEditFailure(error), {
|
|
code: 'AI_IMAGE_GENERATION_FAILED', source: 'pcore', stage: 'provider_submission',
|
|
detail: 'Pcore 图片任务请求被拒绝(invalid_request_error 图像文件无效)',
|
|
providerErrorRaw: JSON.stringify(providerError),
|
|
});
|
|
return true;
|
|
});
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
if (oldBase === undefined) delete process.env.PCORE_BASE_URL; else process.env.PCORE_BASE_URL = oldBase;
|
|
if (oldKey === undefined) delete process.env.PCORE_API_KEY; else process.env.PCORE_API_KEY = oldKey;
|
|
}
|
|
});
|
|
|
|
test('失败诊断区分 Pcore 查询中断、Atlas 图片处理与保存失败', async () => {
|
|
const sourceId = '664d0d73-3d39-4a98-ac0a-5c0e521d8998';
|
|
const settings = createMediaAiEditRequestSchema.parse({ product_id: productId });
|
|
const database = { workspaceTransaction: async (_workspaceId, work) => work({
|
|
query: async (sql) => {
|
|
if (sql.includes('SELECT status, source_asset_id, reference_asset_ids')) return { rows: [{
|
|
status: 'running', source_asset_id: sourceId, reference_asset_ids: [], settings,
|
|
provider_task_id: 'provider-123', created_at: new Date(),
|
|
}] };
|
|
if (sql.includes('SELECT id, storage_provider, bucket, object_key')) return { rows: [{
|
|
id: sourceId, storage_provider: 'local_minio', bucket: 'atlas-assets', object_key: 'main.jpg',
|
|
}] };
|
|
return { rows: [] };
|
|
},
|
|
}) };
|
|
const job = { workspaceId: '30cf54b7-06a3-4e5f-a5fc-d74165115094',
|
|
payload: { edit_id: '68db226f-3fcb-4579-ab9e-4932b1d84067' } };
|
|
const oldFetch = globalThis.fetch;
|
|
const oldBase = process.env.PCORE_BASE_URL;
|
|
const oldKey = process.env.PCORE_API_KEY;
|
|
process.env.PCORE_BASE_URL = 'https://pcore.ai/v1';
|
|
process.env.PCORE_API_KEY = 'test-key';
|
|
try {
|
|
globalThis.fetch = async () => { throw new TypeError('fetch failed'); };
|
|
await assert.rejects(runMediaAiEdit(database, {}, job), (error) => {
|
|
assert.deepEqual(classifyAiEditFailure(error), {
|
|
code: 'AI_IMAGE_GENERATION_FAILED', source: 'pcore', stage: 'provider_polling',
|
|
detail: 'fetch failed', providerErrorRaw: null,
|
|
});
|
|
return true;
|
|
});
|
|
globalThis.fetch = async () => Response.json({ status: 'completed',
|
|
data: [{ b64_json: Buffer.from('invalid image').toString('base64') }] });
|
|
await assert.rejects(runMediaAiEdit(database, {}, job), (error) => {
|
|
const failure = classifyAiEditFailure(error);
|
|
assert.equal(failure.source, 'atlas');
|
|
assert.equal(failure.stage, 'result_processing');
|
|
assert.match(failure.detail, /image|format|buffer/i);
|
|
return true;
|
|
});
|
|
const generated = await sharp({ create: { width: 100, height: 100, channels: 3,
|
|
background: '#445566' } }).png().toBuffer();
|
|
globalThis.fetch = async () => Response.json({ status: 'completed',
|
|
data: [{ b64_json: generated.toString('base64') }] });
|
|
const storage = { send: async () => { throw new Error('private/object-key.jpg'); } };
|
|
await assert.rejects(runMediaAiEdit(database, storage, job), (error) => {
|
|
const failure = classifyAiEditFailure(error);
|
|
assert.equal(failure.source, 'atlas');
|
|
assert.equal(failure.stage, 'result_storage');
|
|
assert.equal(failure.detail, 'Error');
|
|
return true;
|
|
});
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
if (oldBase === undefined) delete process.env.PCORE_BASE_URL; else process.env.PCORE_BASE_URL = oldBase;
|
|
if (oldKey === undefined) delete process.env.PCORE_API_KEY; else process.env.PCORE_API_KEY = oldKey;
|
|
}
|
|
});
|