import assert from 'node:assert/strict'; import test from 'node:test'; import { adaptJdProductProfile, adaptJdProductResponse } from '../src/modules/domestic-voc/adapters/jd-product.adapter.js'; import { adaptJdPriceResponse } from '../src/modules/domestic-voc/adapters/jd-price.adapter.js'; import { GatewayPayloadError } from '../src/modules/domestic-voc/adapters/jd-response.js'; import { adaptJdReviewResponse } from '../src/modules/domestic-voc/adapters/jd-review.adapter.js'; test('JD product adapter unwraps the company gateway envelope', () => { const product = adaptJdProductResponse({ code: 200, data: { code: 200, data: { item: { itemId: '11266507445', itemName: 'Demashi commercial cooker', brandName: 'Demashi', model: '35P6-CM1', firstCategoryName: 'Commercial appliances', thirdCategoryName: 'Commercial cooker', }, }, }, }, '11266507445'); assert.equal(product.productId, '11266507445'); assert.equal(product.productKey, 'jd:11266507445'); assert.equal(product.title, 'Demashi commercial cooker'); assert.equal(product.brand, 'Demashi'); assert.equal(product.model, '35P6-CM1'); assert.equal(product.source, 'fmode_gateway'); }); test('JD product adapter preserves an explicit competitor role', () => { const product = adaptJdProductResponse({ data: { item: { itemId: '100119862001', itemName: 'Competitor' } } }, '100119862001', 'competitor'); assert.equal(product.role, 'competitor'); }); test('JD product profile exposes normalized catalog fields without the raw response', () => { const profile = adaptJdProductProfile({ mainImages: ['https://img.example/item.jpg'], color: 'Black', productArea: 'China', weight: '12.5', length: '300', width: '200', height: '100', saleDate: '2026-07-01 10:00:00', shopId: 'shop-1', venderId: 'seller-1', jdexpress: true, }, '2026-07-24T00:00:00.000Z'); assert.equal(profile?.imageUrl, 'https://img.example/item.jpg'); assert.equal(profile?.origin, 'China'); assert.equal(profile?.dimensionsMm.length, '300'); assert.equal(profile?.jdExpress, true); assert.equal(profile?.collectedAt, '2026-07-24T00:00:00.000Z'); }); test('JD price adapter maps exact product price minor units', () => { const priceCents = adaptJdPriceResponse({ code: 200, data: { code: 0, data: { data: [ { good_id: 'other-product', price: 999_900 }, { good_id: '100119862001', price: 229_900 }, ], }, }, }, '100119862001'); assert.equal(priceCents, 229_900); }); test('JD price adapter rejects a mismatched product identity', () => { assert.equal(adaptJdPriceResponse({ data: { data: { data: [{ good_id: 'different-product', price: 229_900 }] } }, }, '100119862001'), null); }); test('JD review adapter maps evidence and pagination without reviewer identity', () => { const page = adaptJdReviewResponse({ code: 200, data: { code: 200, data: { comments: [{ commentId: 'comment-1', score: 5, content: 'Easy to clean and heats quickly.', creationTime: '2026-07-20T10:00:00+08:00', nickname: 'not-exposed-by-snapshot', }], pageCount: 2, }, }, }, '11266507445', 1); assert.equal(page.reviews.length, 1); assert.equal(page.reviews[0]?.reviewId, 'comment-1'); assert.equal(page.reviews[0]?.rating, 5); assert.equal(page.reviews[0]?.reviewDate, '2026-07-20T02:00:00.000Z'); assert.match(page.reviews[0]?.reviewKey ?? '', /^jd:11266507445:/); assert.equal(page.hasNextPage, true); assert.equal('nickname' in page.reviews[0]!.rawPayload, false); }); test('JD review adapter maps the live commentScore and commentDate fields and removes identity fields', () => { const page = adaptJdReviewResponse({ data: { data: { data: [{ commentId: 'live-comment-1', commentData: 'Heating is fast.', commentScore: '4', commentDate: '2026-07-21 08:30:00', userNickName: 'private-name', userImgURL: 'https://private.example/avatar.jpg', guid: 'private-guid', }] } }, }, '100095574033', 1); assert.equal(page.reviews[0]?.rating, 4); assert.equal(page.reviews[0]?.reviewDate, '2026-07-21T00:30:00.000Z'); assert.equal('userNickName' in page.reviews[0]!.rawPayload, false); assert.equal('userImgURL' in page.reviews[0]!.rawPayload, false); assert.equal('guid' in page.reviews[0]!.rawPayload, false); }); test('JD adapters classify pending collection as retryable without raw body leakage', () => { assert.throws( () => adaptJdProductResponse({ code: 301, message: 'COLLECT FAILED, SEND REQUEST AGAIN', data: null }, '1'), (error: unknown) => { assert.ok(error instanceof GatewayPayloadError); assert.equal(error.retryable, true); assert.equal(error.message, 'Gateway data is not ready'); assert.doesNotMatch(error.message, /COLLECT FAILED/); return true; }, ); });