library-comparison.test.js 5.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import { libraryFilterPatch, libraryRequest } from '../src/core/library.js';
  4. import { comparisonIds, comparisonQuery, toggleComparison, comparisonProductUrl, comparisonEvidenceUrl, comparisonPriceRows, matchingPrice, comparisonMoney } from '../src/core/library-comparison.js';
  5. import { readProductComparison } from '../src/services/library-comparison.js';
  6. test('店铺必须绑定平台,跨平台/移除平台清店铺和游标,不因店名更改品牌', () => {
  7. const query = { type: 'products', platform: 'jd', shopId: 'same-shop', brand: 'other', cursor: 'old' };
  8. assert.deepEqual(libraryFilterPatch(query, { platform: 'tmall' }), { type: 'products', platform: 'tmall', brand: 'other' });
  9. assert.deepEqual(libraryFilterPatch(query, { platform: null }), { type: 'products', brand: 'other' });
  10. assert.equal(libraryFilterPatch(query, { sort: 'price_asc' }).shopId, 'same-shop');
  11. assert.equal(libraryRequest({ platform: 'tmall', shopId: 'same-shop' }).filters.shopId, 'same-shop');
  12. assert.equal(libraryRequest({ shopId: 'same-shop' }).filters.shopId, undefined);
  13. assert.equal(libraryFilterPatch({ shopId: 'same-shop', cursor: 'old' }, {}).shopId, undefined);
  14. });
  15. test('2–4件对比按完整复合编号去重、拒绝第5件,URL刷新可恢复', () => {
  16. const ids = ['jd:1', 'tmall:1', 'jd:2', 'jd:3'];
  17. assert.deepEqual(comparisonIds(JSON.stringify([...ids, 'jd:1', 'jd:4'])), ids);
  18. assert.deepEqual(toggleComparison(ids, 'jd:4'), { ids, full: true });
  19. assert.deepEqual(toggleComparison(ids, 'tmall:1').ids, ['jd:1', 'jd:2', 'jd:3']);
  20. assert.deepEqual(comparisonIds('bad-json'), []);
  21. assert.deepEqual(comparisonIds(['title', null, 'jd:1', 'jd:1', 'jd:\n2']), ['jd:1']);
  22. const query = comparisonQuery({ platform: 'jd', cursor: 'page2' }, ids, true);
  23. assert.equal(query.comparing, '1'); assert.equal(query.cursor, 'page2');
  24. assert.deepEqual(comparisonIds(new URLSearchParams(new URL('https://example.test' + comparisonProductUrl('tmall:1', query)).search).get('compare')), ids);
  25. assert.equal(comparisonQuery(query, ['jd:1'], true).comparing, undefined);
  26. assert.equal(comparisonQuery(query, []).compare, undefined);
  27. });
  28. test('原评价证据固定商品与评价编号,跳详情关闭弹窗但保留列表筛选和所选项', () => {
  29. const context = { platform: 'jd', shopId: 'shop1', cursor: 'page2', panel: 'prices', comparing: '1', compare: '["jd:1","tmall:1"]', relatedCursor: 'old' };
  30. assert.equal(comparisonEvidenceUrl('jd:1', { productId: 'tmall:1', reviewId: 'r1' }, context), null);
  31. const url = new URL(comparisonEvidenceUrl('jd:1', { productId: 'jd:1', reviewId: 'r 1' }, context), 'https://example.test');
  32. assert.equal(url.pathname, '/library/products/jd%3A1');
  33. assert.equal(url.searchParams.get('panel'), 'reviews'); assert.equal(url.searchParams.get('relatedReviewId'), 'r 1');
  34. assert.equal(url.searchParams.get('comparing'), null); assert.equal(url.searchParams.get('relatedCursor'), null);
  35. assert.equal(url.searchParams.get('shopId'), 'shop1'); assert.equal(url.searchParams.get('cursor'), 'page2');
  36. });
  37. test('价格严格按币种和口径拆行,0元和缺失不混淆,不补算单位价格', () => {
  38. const group = (currency, price_basis, min) => ({ currency, price_basis, min, max: min, count: 1 });
  39. const record = { overview: { prices: [group('CNY', 'listing', 0), group('USD', 'listing', 100), group('CNY', 'reference', 200)] } };
  40. const rows = comparisonPriceRows([{ detail: { record } }, { error: 'failed' }]);
  41. assert.equal(rows.length, 3);
  42. assert.equal(matchingPrice(record, rows[0]).min, 0);
  43. assert.equal(comparisonMoney(0, 'CNY'), '¥0.00'); assert.equal(comparisonMoney(null, 'CNY'), '金额未知');
  44. assert.equal(matchingPrice({ overview: { prices: [] } }, rows[0]), undefined);
  45. });
  46. test('固定同一代际并限制两路并发,局部失败保留成功列且不填0', async () => {
  47. let active = 0, maxActive = 0;
  48. const calls = [];
  49. const result = await readProductComparison(['jd:1', 'tmall:1', 'jd:2', 'jd:3'], { read: async request => {
  50. calls.push(request);
  51. if (request.op === 'summary') return { generation: 'one' };
  52. assert.equal(request.generation, 'one'); active++; maxActive = Math.max(active, maxActive);
  53. await new Promise(resolve => setTimeout(resolve, 5)); active--;
  54. if (request.id === 'tmall:1') throw Error('暂不可用');
  55. return { generation: 'one', record: { id: request.id, overview: { reviews: { collected: 0 } } } };
  56. } });
  57. assert.equal(maxActive, 2); assert.equal(calls.length, 5); assert.equal(result.stale, false);
  58. assert.equal(result.items[0].detail.record.overview.reviews.collected, 0);
  59. assert.equal(result.items[1].error, '暂不可用'); assert.equal(result.items[1].detail, undefined);
  60. });
  61. test('代际错误/响应代际不一致使整组失效,再读取从新summary开始;不同编号响应不能串列', async () => {
  62. let generation = 'old';
  63. const read = async request => {
  64. if (request.op === 'summary') return { generation };
  65. if (generation === 'old' && request.id === 'jd:2') throw Object.assign(Error('changed'), { code: 'LIBRARY_VERSION_CHANGED' });
  66. return { generation, record: { id: request.id } };
  67. };
  68. const stale = await readProductComparison(['jd:1', 'jd:2'], { read });
  69. assert.equal(stale.stale, true); assert.ok(stale.items.every(item => !item.detail));
  70. generation = 'new';
  71. assert.ok((await readProductComparison(['jd:1', 'jd:2'], { read })).items.every(item => item.detail.generation === 'new'));
  72. const mismatch = await readProductComparison(['jd:1'], { read: async r => r.op === 'summary' ? { generation: 'one' } : { generation: 'two', record: { id: 'jd:1' } } });
  73. assert.equal(mismatch.stale, true);
  74. const wrongId = await readProductComparison(['jd:1'], { read: async r => r.op === 'summary' ? { generation: 'one' } : { generation: 'one', record: { id: 'tmall:1' } } });
  75. assert.ok(wrongId.items[0].error); assert.equal(wrongId.items[0].detail, undefined);
  76. });