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('No products here'), status: 200, })); const result = await scrapeStore(mockStore, 'https://teststore.com/search?q=nothing'); expect(result.items).toHaveLength(0); }); });