Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions apps/importer/src/shopee/__tests__/arena-audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import assert from 'node:assert/strict';
import {
ArenaSpecSyncer,
UPSTREAM_2026_ARENA_REFERENCE_URLS,
parseGsmArenaHtmlToSpecs,
parseGsmArenaBrandCatalogHtml,
parsePhoneArenaCatalogHtml,
type ArenaAuditMasterRow,
} from '../arena-spec-syncer.js';

Expand Down Expand Up @@ -118,6 +121,54 @@ async function run() {
assert.equal(empty.missingFromDatabase.length, UPSTREAM_2026_ARENA_REFERENCE_URLS.length);
assert.deepEqual(empty.incompleteSpecMasters, []);

// parseGsmArenaHtmlToSpecs enforces integer RAM/storage bounds, clean TB formatting, and meta URL checks (#831)
const parsedSpec = parseGsmArenaHtmlToSpecs(
`<h1 data-spec="modelname">Samsung Galaxy S26 Ultra</h1>
<td data-spec="internalmemory">0.5GB 0.5GB RAM, 1200GB 12.5GB RAM, 1200GB 16GB RAM</td>`,
{
gsmarenaUrl: 'https://www.gsmarena.com/samsung_galaxy_s26_ultra-14015.php',
phonearenaUrl: 'https://evil.example/phones/Samsung',
}
);
assert.equal(parsedSpec.ramGb, 16);
assert.equal(parsedSpec.storageGb, 1200);
assert.equal(parsedSpec.keySpecs.storage, '1200GB');
assert.equal(
parsedSpec.gsmarenaUrl,
'https://www.gsmarena.com/samsung_galaxy_s26_ultra-14015.php'
);
assert.equal(parsedSpec.phonearenaUrl, undefined);
assert.equal(parsedSpec.source, 'gsmarena');

const parsedTbSpec = parseGsmArenaHtmlToSpecs(
`<h1 data-spec="modelname">Apple iPad Pro M5</h1>
<td data-spec="internalmemory">1.5TB 16GB RAM</td>`
);
assert.equal(parsedTbSpec.storageGb, 1536);
assert.equal(parsedTbSpec.keySpecs.storage, '1.5TB');

// parseGsmArenaBrandCatalogHtml normalizes *.gsmarena.com image URLs and drops non-gsmarena image sources (#831)
const gsmCatalog = parseGsmArenaBrandCatalogHtml(
`<ul>
<li><a href="xiaomi_18_pro-14288.php"><img src="//fdn2.gsmarena.com/vv/bigpic/xiaomi-18.jpg"><span>18 Pro</span></a></li>
<li><a href="xiaomi_18-14287.php"><img src="https://evil.example/tracker.png"><span>18</span></a></li>
</ul>`,
'Xiaomi'
);
assert.equal(gsmCatalog.length, 2);
assert.equal(gsmCatalog[0]?.imageUrl, 'https://fdn2.gsmarena.com/vv/bigpic/xiaomi-18.jpg');
assert.equal(gsmCatalog[1]?.imageUrl, undefined);

// parsePhoneArenaCatalogHtml canonicalizes URLs and rejects path traversal outside /phones/ (#831)
const paCatalog = parsePhoneArenaCatalogHtml(
`<div>
<a href="/phones/Apple-iPhone-18-Pro_id12539">Apple iPhone 18 Pro</a>
<a href="/phones/../../news/leak">Leaked News</a>
</div>`
);
assert.equal(paCatalog.length, 1);
assert.equal(paCatalog[0]?.url, 'https://www.phonearena.com/phones/Apple-iPhone-18-Pro_id12539');

console.log('βœ… ArenaSpecSyncer catalog audit suite passed');
}

Expand Down
114 changes: 114 additions & 0 deletions apps/importer/src/shopee/__tests__/entity-matcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
EntityMatcher,
ENTITY_MATCH_ACCEPT_THRESHOLD,
ENTITY_MATCH_CANDIDATE_FLOOR,
sanitizeHttpUrl,
sanitizeHttpUrlList,
} from '../entity-matcher.js';
import {
ProductNormalizer,
Expand Down Expand Up @@ -515,6 +517,118 @@ async function runEntityMatcherSuite() {
' βœ… [7/7] Zod 4 JSON Schema, URL/phone prompt-injection filter, MasterProductSchema defaults & structured logger passed'
);

// 8. DOS/Rate#821: Tier 2 malformed fuzzy row filtering, Tier 3 image/spec bounds, and enrichMasterProductById zero/out-of-bounds fallback
assert.equal(sanitizeHttpUrl('https://rate.vn/img/a.png'), 'https://rate.vn/img/a.png');
assert.equal(sanitizeHttpUrl('javascript:alert(1)'), null);
assert.equal(sanitizeHttpUrl('[object Object]'), null);
assert.deepEqual(
sanitizeHttpUrlList([
'https://rate.vn/img/1.png',
'[object Object]',
'javascript:alert(1)',
'https://rate.vn/img/1.png',
]),
['https://rate.vn/img/1.png']
);

const malformedFuzzyDb = createMockSupabase({
fuzzyMatches: [
{ master_id: '', name: 'Missing ID', similarity: 0.95 },
{ master_id: 'nan-sim', name: 'NaN Sim', similarity: Number.NaN },
{ master_id: 'valid-fuzzy-821', name: 'Samsung Galaxy S24 Ultra 256GB', similarity: 0.78 },
],
});
const malformedFuzzyMatcher = new EntityMatcher(malformedFuzzyDb.client, offlineEnricher);
const malformedFuzzyRes = await malformedFuzzyMatcher.resolveOrCreateMasterProduct(s24New);
assert.deepEqual(malformedFuzzyRes, { masterId: 'valid-fuzzy-821', isNew: false });

const tier3BoundsDb = createMockSupabase();
const tier3BoundsMatcher = new EntityMatcher(tier3BoundsDb.client, offlineEnricher);
await tier3BoundsMatcher.resolveOrCreateMasterProduct({
...s24New,
ram_gb: 0,
storage_gb: 999_999,
screen_size_inch: -5,
battery_mah: 9_999_999,
images: ['[object Object]', 'https://rate.vn/img/valid.png', 'javascript:void(0)'],
});
const seededRow = tier3BoundsDb.capturedUpserts[0]?.payload;
assert.equal(seededRow?.ram_gb, null);
assert.equal(seededRow?.storage_gb, null);
assert.equal(seededRow?.screen_size_inch, null);
assert.equal(seededRow?.battery_mah, null);
assert.deepEqual(seededRow?.images, ['https://rate.vn/img/valid.png']);
assert.equal(seededRow?.thumbnail, 'https://rate.vn/img/valid.png');

let updatedRow821: Record<string, unknown> | undefined;
let forwardedParams821: Record<string, unknown> | undefined;
const enrichDb821 = {
from(table: string) {
assert.equal(table, 'master_products');
return {
select() {
return {
eq() {
return {
async maybeSingle() {
return {
data: {
id: 'master-821',
brand: 'Samsung',
model: 'Galaxy S24 Ultra',
storage_gb: 0,
ram_gb: 99999,
screen_size_inch: 0,
battery_mah: -100,
has_5g: false,
name: 'Samsung Galaxy S24 Ultra 256GB',
description: '',
colors: ['Titanium Gray'],
specifications: { category_slug: 'smartphones' },
key_specs: {},
},
error: null,
};
},
};
},
};
},
update(payload: Record<string, unknown>) {
updatedRow821 = payload;
return {
async eq() {
return { error: null };
},
};
},
};
},
} as unknown as RateSupabaseClient;
const stubEnricher821 = {
async fetchSpecsFromAi(params: Record<string, unknown>) {
forwardedParams821 = params;
return {
ram_gb: 12,
storage_gb: 256,
screen_size_inch: 6.8,
battery_mah: 5000,
has_5g: true,
specifications: { display: { size_inch: 6.8 } },
key_specs: { ram: '12GB' },
};
},
} as unknown as AiSpecEnricher;
const enrichMatcher821 = new EntityMatcher(enrichDb821, stubEnricher821);
assert.equal(await enrichMatcher821.enrichMasterProductById('master-821'), true);
assert.equal(forwardedParams821?.storageGb, null);
assert.equal(forwardedParams821?.ramGb, null);
assert.equal(updatedRow821?.ram_gb, 12);
assert.equal(updatedRow821?.storage_gb, 256);
assert.equal(updatedRow821?.screen_size_inch, 6.8);
assert.equal(updatedRow821?.battery_mah, 5000);
console.log(' βœ… [8/8] DOS/Rate#821 EntityMatcher bounds & HTTP URL sanitization passed');

console.log('πŸŽ‰ All EntityMatcher & Open-Core Audit C6/C8/T15/Low tests passed!');
}

Expand Down
Loading
Loading