Initial commit: Price Hunter — self-hosted price comparison engine

Complete application scaffolding with:
- Backend: Node.js + Fastify + sql.js (SQLite)
- Frontend: SvelteKit + Tailwind CSS
- Scraper engine with parallel fan-out, rate limiting, cheerio-based parsing
- Store management with CSS selector config and per-store test pages
- Docker setup for single-command deployment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
mariosemes
2026-03-26 20:54:52 +01:00
commit e0f67d0835
47 changed files with 9181 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { describe, it, expect } from 'vitest';
import { parsePrice, normalizeUrl } from '../../../src/server/scraper/result-parser.js';
describe('parsePrice', () => {
it('parses US format prices', () => {
expect(parsePrice('$12.99')).toBe(12.99);
expect(parsePrice('$1,299.00')).toBe(1299);
expect(parsePrice('$0.50')).toBe(0.5);
});
it('parses European format prices', () => {
expect(parsePrice('€12,99')).toBe(12.99);
expect(parsePrice('1.299,00 EUR')).toBe(1299);
expect(parsePrice('€299,99')).toBe(299.99);
});
it('handles free', () => {
expect(parsePrice('free')).toBe(0);
expect(parsePrice('FREE')).toBe(0);
expect(parsePrice('gratis')).toBe(0);
});
it('handles price ranges', () => {
expect(parsePrice('$12 - $15')).toBe(12);
expect(parsePrice('€10,00 €20,00')).toBe(10);
});
it('handles plain numbers', () => {
expect(parsePrice('42')).toBe(42);
expect(parsePrice('12.99')).toBe(12.99);
});
it('returns null for invalid input', () => {
expect(parsePrice('')).toBe(null);
expect(parsePrice('abc')).toBe(null);
});
});
describe('normalizeUrl', () => {
it('returns absolute URLs as-is', () => {
expect(normalizeUrl('https://example.com/product', 'https://base.com')).toBe('https://example.com/product');
});
it('resolves relative URLs against base', () => {
expect(normalizeUrl('/products/123', 'https://store.com')).toBe('https://store.com/products/123');
});
it('returns base URL for empty href', () => {
expect(normalizeUrl('', 'https://store.com')).toBe('https://store.com');
});
});