feat: add price scraper with JSON-LD/meta/regex fallback
- Implement extractPrice() to extract price from HTML with three fallback methods: 1. JSON-LD Product schema (json-ld) 2. og:price:amount meta tag (meta) 3. Euro symbol regex pattern (regex) - Implement fetchAndExtractPrice() for fetching URLs and extracting prices - Includes helper functions for parsing and normalizing price values - Add comprehensive test suite with fixtures for each extraction method - All tests passing (14/14) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gwc3aCiyQxmCWQke8RvEz
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { extractPrice, fetchAndExtractPrice } = require('../src/scraper');
|
||||
|
||||
function loadFixture(name) {
|
||||
return fs.readFileSync(path.join(__dirname, 'fixtures', name), 'utf8');
|
||||
}
|
||||
|
||||
test('extractPrice reads price from JSON-LD Product offers', () => {
|
||||
const result = extractPrice(loadFixture('json-ld.html'));
|
||||
assert.deepEqual(result, { price: 19.99, method: 'json-ld' });
|
||||
});
|
||||
|
||||
test('extractPrice reads price from og:price:amount meta tag', () => {
|
||||
const result = extractPrice(loadFixture('meta-tag.html'));
|
||||
assert.deepEqual(result, { price: 24.50, method: 'meta' });
|
||||
});
|
||||
|
||||
test('extractPrice falls back to euro-amount regex in page text', () => {
|
||||
const result = extractPrice(loadFixture('regex-fallback.html'));
|
||||
assert.deepEqual(result, { price: 14.95, method: 'regex' });
|
||||
});
|
||||
|
||||
test('extractPrice returns null price when nothing matches', () => {
|
||||
const result = extractPrice(loadFixture('no-price.html'));
|
||||
assert.deepEqual(result, { price: null, method: null });
|
||||
});
|
||||
|
||||
test('fetchAndExtractPrice returns error when fetch rejects', async () => {
|
||||
const fetchImpl = async () => { throw new Error('network down'); };
|
||||
const result = await fetchAndExtractPrice('https://example.invalid', { fetchImpl });
|
||||
assert.equal(result.price, null);
|
||||
assert.ok(result.error instanceof Error);
|
||||
});
|
||||
|
||||
test('fetchAndExtractPrice returns error on non-OK response', async () => {
|
||||
const fetchImpl = async () => ({ ok: false, status: 404, text: async () => '' });
|
||||
const result = await fetchAndExtractPrice('https://example.invalid', { fetchImpl });
|
||||
assert.equal(result.price, null);
|
||||
assert.ok(result.error instanceof Error);
|
||||
});
|
||||
|
||||
test('fetchAndExtractPrice extracts price from fetched HTML', async () => {
|
||||
const html = loadFixture('meta-tag.html');
|
||||
const fetchImpl = async () => ({ ok: true, status: 200, text: async () => html });
|
||||
const result = await fetchAndExtractPrice('https://example.invalid', { fetchImpl });
|
||||
assert.deepEqual(result, { price: 24.50, method: 'meta', error: null });
|
||||
});
|
||||
Reference in New Issue
Block a user