feat(edit): 优化图片导入ozon问题

This commit is contained in:
2026-10-07 21:49:28 +08:00
parent 79b310efe7
commit 06dbd817f0
13 changed files with 289 additions and 70 deletions
+76 -14
View File
@@ -45,7 +45,7 @@ async function request(
});
return { status: res.status, body: (await res.json()) as any };
}
async function fixture(count = 2) {
async function fixture(count = 2, mediaCount = 1) {
const created = await request(
'/collection-items',
'POST',
@@ -106,16 +106,18 @@ async function fixture(count = 2) {
[profile, `TEST-${randomUUID()}`, quote, variant],
)
).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) VALUES($1,'qiniu_kodo','test-bucket',$2,'image/png',100,$3) RETURNING id`,
[workspace, randomUUID(), randomUUID()],
)
).rows[0].id;
await tx.query(
`INSERT INTO atlas.product_variant_listing_media(workspace_id,product_id,profile_id,listing_id,product_variant_id,asset_id,sort_order) VALUES($1,$2,$3,$4,$5,$6,0)`,
[workspace, product, profile, listing, variant, asset],
);
for (let mediaIndex = 0; mediaIndex < mediaCount; mediaIndex++) {
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) VALUES($1,'qiniu_kodo','test-bucket',$2,'image/png',100,$3) RETURNING id`,
[workspace, randomUUID(), randomUUID()],
)
).rows[0].id;
await tx.query(
`INSERT INTO atlas.product_variant_listing_media(workspace_id,product_id,profile_id,listing_id,product_variant_id,asset_id,sort_order) VALUES($1,$2,$3,$4,$5,$6,$7)`,
[workspace, product, profile, listing, variant, asset, mediaIndex],
);
}
}
});
return { profile, product, ids };
@@ -666,7 +668,9 @@ describe('首次发布 HTTP / 数据库 / Worker 闭环', () => {
});
assert.equal(job.items[0].status, 'polling');
const offers = job.items.map((i) => i.offer_id);
job = await run(job, async () => ({
job = await run(job, async (_c, _k, path) => path === '/v2/product/pictures/info' ? ({
items: [{ product_id: 101, primary_photo: ['https://images.example.test/fixture.png'], photo: [], errors: [] }],
}) : ({
result: {
items: [
{
@@ -735,12 +739,70 @@ describe('首次发布 HTTP / 数据库 / Worker 闭环', () => {
});
assert.equal(job.status, 'reconciling');
assert.equal(imports, 1);
job = await run(job, async () => ({
items: [{ offer_id: job.items[0].offer_id, id: 999 }],
job = await run(job, async (_c, _k, path) => ({
items: path === '/v2/product/pictures/info'
? [{ product_id: 999, primary_photo: ['https://images.example.test/fixture.png'], photo: [], errors: [] }]
: [{ offer_id: job.items[0].offer_id, id: 999 }],
}));
assert.equal(job.status, 'succeeded');
assert.equal(imports, 1);
});
it('导入成功后等待图片结果,缺图警告保留到 HTTP 结果与 SKU,并且不重复导入', async () => {
const f = await fixture(1, 3);
let job = (await publish(f)).body.data.publish_job as ProductPublishJob;
let imports = 0;
job = await run(job, async (_c, _k, path) => {
if (path === '/v3/product/info/list') return { items: [] };
assert.equal(path, '/v3/product/import');
imports++;
return { result: { task_id: 456 } };
});
const imported = { result: { items: [{ offer_id: job.items[0].offer_id, product_id: 102,
status: 'imported', errors: [] }] } };
job = await run(job, async (_c, _k, path) => path === '/v1/product/import/info' ? imported
: { items: [{ product_id: 102, primary_photo: [], photo: [], errors: [] }] });
assert.equal(job.status, 'running');
assert.equal(job.items[0].status, 'polling');
job = await run(job, async (_c, _k, path) => path === '/v1/product/import/info' ? {
result: { items: [{ ...imported.result.items[0], errors: [{ code: 'primary_image_load_failed',
message: 'https://private.example.test/primary.png?token=hidden' }] }] },
} : { items: [{ product_id: 102, primary_photo: ['https://images.example.test/second.png'], photo: [],
errors: [{ url: 'https://private.example.test/primary.png?token=hidden', message: 'request process failed: unknown error host unreachable' },
{ url: 'https://private.example.test/third.png?token=hidden', message: 'failed https://private.example.test/third.png?token=hidden' }] }] });
assert.equal(job.status, 'succeeded');
assert.equal(job.items[0].status, 'imported');
assert.equal(job.items[0].platform_product_id, '102');
assert.ok(job.items[0].errors.some((error) => error.code === 'OZON_IMAGES_INCOMPLETE' && error.message.includes('1/3')));
assert.ok(job.items[0].errors.some((error) => error.code === 'primary_image_load_failed'));
assert.ok(job.items[0].errors.some((error) => error.message.includes('全球加速')));
assert.ok(!JSON.stringify(job).includes('token='));
assert.ok(!JSON.stringify(job).includes('private.example.test'));
const listing = (await db.workspaceTransaction(workspace, tx => tx.query(
'SELECT status,last_error FROM atlas.product_variant_platform_listings WHERE profile_id=$1', [f.profile],
))).rows[0];
assert.equal(listing.status, 'imported');
assert.deepEqual(listing.last_error, job.items[0].errors);
await run(job, async () => { imports++; throw new Error('终态不得重复上报'); });
assert.equal(imports, 1);
});
it('图片全部到齐才报告无警告成功,长时间未齐时以已导入并提示缺图结束', async () => {
for (const completePictures of [true, false]) {
const f = await fixture(1, 3);
let job = (await publish(f)).body.data.publish_job as ProductPublishJob;
job = await run(job, async (_c, _k, path) => path === '/v3/product/info/list'
? { items: [] } : { result: { task_id: 457 } });
if (!completePictures) await db.workspaceTransaction(workspace, tx => tx.query(
'UPDATE atlas.product_publish_jobs SET poll_count=121 WHERE id=$1', [job.id],
));
job = await run(job, async (_c, _k, path) => path === '/v1/product/import/info'
? { result: { items: [{ offer_id: job.items[0].offer_id, product_id: 103, status: 'imported', errors: [] }] } }
: { items: [{ product_id: 103, primary_photo: completePictures ? ['https://images.example.test/1.png'] : [],
photo: completePictures ? ['https://images.example.test/2.png', 'https://images.example.test/3.png'] : [], errors: [] }] });
assert.equal(job.status, 'succeeded');
assert.equal(job.items[0].status, 'imported');
assert.equal(job.items[0].errors.length, completePictures ? 0 : 1);
}
});
it('任一图片准备失败时整批不提交,并隐藏存储路径', async () => {
const f = await fixture(2);
const created = (await publish(f)).body.data