Files
PriceHunter/tests/server/scraper/http-scraper.test.ts
mariosemes e0f67d0835 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>
2026-03-26 20:54:52 +01:00

71 lines
2.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { scrapeStore } from '../../../src/server/scraper/http-scraper.js';
import type { Store } from '../../../src/server/models/store.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const sampleHtml = fs.readFileSync(path.join(__dirname, '../../fixtures/sample-store.html'), 'utf-8');
const mockStore: Store = {
id: 1,
name: 'Test Store',
slug: 'test-store',
base_url: 'https://teststore.com',
search_url: 'https://teststore.com/search?q={query}',
enabled: 1,
sel_container: '.product-card',
sel_name: '.product-title',
sel_price: '.product-price',
sel_link: 'a',
sel_image: '.product-image',
rate_limit: 2,
rate_window: 1000,
proxy_url: null,
user_agent: null,
headers_json: null,
currency: 'EUR',
category_id: null,
created_at: '',
updated_at: '',
};
beforeEach(() => {
vi.restoreAllMocks();
});
describe('scrapeStore', () => {
it('extracts products from HTML using CSS selectors', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
text: () => Promise.resolve(sampleHtml),
status: 200,
}));
const result = await scrapeStore(mockStore, 'https://teststore.com/search?q=headphones');
expect(result.statusCode).toBe(200);
expect(result.items).toHaveLength(4);
expect(result.items[0]).toEqual({
name: 'Sony WH-1000XM5 Wireless Headphones',
priceText: '€299,99',
link: '/products/sony-wh1000xm5',
image: '/images/xm5.jpg',
});
// Item without image
expect(result.items[3].name).toBe('Bose QuietComfort 45');
expect(result.items[3].image).toBe(null);
});
it('returns empty items when no containers match', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
text: () => Promise.resolve('<html><body>No products here</body></html>'),
status: 200,
}));
const result = await scrapeStore(mockStore, 'https://teststore.com/search?q=nothing');
expect(result.items).toHaveLength(0);
});
});