Files
Luncher/server/src/tests/scrapers.test.ts
T
mates 9fee4a1538
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 30s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 41s
CI / Playwright E2E tests (push) Successful in 2m0s
CI / Build and push Docker image (push) Successful in 47s
CI / Notify (push) Successful in 2s
fix: oprava parsování TechTower po změně jejich webu
2026-10-09 14:37:09 +02:00

197 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as fs from 'fs';
import * as path from 'path';
import axios from 'axios';
import { getMenuSladovnicka, getMenuTechTower, getMenuSenkSerikova, StaleWeekError } from '../restaurants';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
const fixturesDir = path.join(__dirname, 'fixtures');
const loadFixture = (name: string) => fs.readFileSync(path.join(fixturesDir, name), 'utf-8');
// Pondělí 12.5.2025
const MONDAY = new Date('2025-05-12');
beforeEach(() => {
jest.resetAllMocks();
});
describe('Sladovnicka parser', () => {
beforeEach(() => {
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('sladovnicka.html') });
});
test('vrátí pole o délce 5 (jeden záznam na každý pracovní den)', async () => {
const menu = await getMenuSladovnicka(MONDAY);
expect(menu).toHaveLength(5);
});
test('pondělní menu obsahuje aspoň jedno jídlo', async () => {
const menu = await getMenuSladovnicka(MONDAY);
expect(menu[0].length).toBeGreaterThan(0);
});
test('první položka pondělního dne je polévka (isSoup=true)', async () => {
const menu = await getMenuSladovnicka(MONDAY);
expect(menu[0][0].isSoup).toBe(true);
});
test('jídla mají name, price a amount', async () => {
const menu = await getMenuSladovnicka(MONDAY);
const jidlo = menu[0][1];
expect(jidlo.name).toBeTruthy();
expect(jidlo.price).toBeTruthy();
expect(jidlo.amount).toBeTruthy();
});
test('alergeny jsou naparsovány jako čísla', async () => {
const menu = await getMenuSladovnicka(MONDAY);
const polievka = menu[0][0];
expect(Array.isArray(polievka.allergens)).toBe(true);
expect(polievka.allergens!.length).toBeGreaterThan(0);
expect(typeof polievka.allergens![0]).toBe('number');
});
});
describe('TechTower parser', () => {
beforeEach(() => {
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('techtower.html') });
});
test('vrátí pole o délce 5', async () => {
const menu = await getMenuTechTower(MONDAY);
expect(menu).toHaveLength(5);
});
test('pondělní menu obsahuje polévku a hlavní jídla', async () => {
const menu = await getMenuTechTower(MONDAY);
expect(menu[0].some(f => f.isSoup)).toBe(true);
expect(menu[0].some(f => !f.isSoup)).toBe(true);
});
test('TechTower hodí StaleWeekError, pokud datum v hlavičce neodpovídá', async () => {
// Fixture obsahuje "12.5.-16.5.2025" – jiný týden = stale
const jinaStreda = new Date('2025-04-14');
await expect(getMenuTechTower(jinaStreda)).rejects.toBeInstanceOf(StaleWeekError);
});
test('StaleWeekError obsahuje naparsovaná data', async () => {
const jinaStreda = new Date('2025-04-14');
try {
await getMenuTechTower(jinaStreda);
} catch (e) {
expect(e).toBeInstanceOf(StaleWeekError);
const err = e as StaleWeekError;
expect(err.food).toHaveLength(5);
}
});
});
describe('TechTower parser – nový formát 07/2026', () => {
// Pondělí 6.7.2026 (pondělí je svátek, hlavička začíná úterým "7. 7.-10. 7.")
const MONDAY_2026 = new Date('2026-07-06');
beforeEach(() => {
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('techtower-2026-07.html') });
});
test('vrátí pole o délce 5', async () => {
const menu = await getMenuTechTower(MONDAY_2026);
expect(menu).toHaveLength(5);
});
test('středeční menu obsahuje polévku a hlavní jídla s cenou', async () => {
const menu = await getMenuTechTower(MONDAY_2026);
// index 2 = středa (Boršč + hlavní jídla)
expect(menu[2].some(f => f.isSoup)).toBe(true);
expect(menu[2].some(f => !f.isSoup && f.price.includes('Kč'))).toBe(true);
});
test('cena i alergeny jsou správně odděleny od názvu jídla', async () => {
const menu = await getMenuTechTower(MONDAY_2026);
const polevka = menu[2].find(f => f.isSoup);
expect(polevka).toBeDefined();
// "Boršč 1,7,9 — 45 Kč" → název bez ceny a bez alergenů, cena a alergeny zvlášť
expect(polevka!.name).not.toMatch(/Kč/);
expect(polevka!.name).not.toMatch(/\d/);
expect(polevka!.price).toContain('Kč');
expect(Array.isArray(polevka!.allergens)).toBe(true);
expect(polevka!.allergens!.length).toBeGreaterThan(0);
});
test('nehodí StaleWeekError pro aktuální týden, i když pondělí je svátek', async () => {
await expect(getMenuTechTower(MONDAY_2026)).resolves.toHaveLength(5);
});
test('hodí StaleWeekError pro jiný týden', async () => {
const jinyTyden = new Date('2026-06-01');
await expect(getMenuTechTower(jinyTyden)).rejects.toBeInstanceOf(StaleWeekError);
});
});
describe('TechTower parser – nový formát 10/2026', () => {
// Pondělí 5.10.2026, stránka obsahuje i menu na následující týden
const MONDAY_2026_10 = new Date('2026-10-05');
beforeEach(() => {
mockedAxios.get = jest.fn().mockResolvedValue({ data: loadFixture('techtower-2026-10.html') });
});
test('vrátí pole o délce 5 s jídly pro každý den', async () => {
const menu = await getMenuTechTower(MONDAY_2026_10);
expect(menu).toHaveLength(5);
for (const den of menu) {
expect(den).toHaveLength(5);
}
});
test('nezahrne menu následujícího týdne', async () => {
const menu = await getMenuTechTower(MONDAY_2026_10);
expect(menu[0][0].name).toBe('Mexická fazolačka s kukuřicí a masem');
expect(menu.flat().some(f => f.name.startsWith('Hrášková'))).toBe(false);
});
test('každý den začíná polévkou s cenou a alergeny', async () => {
const menu = await getMenuTechTower(MONDAY_2026_10);
for (const den of menu) {
expect(den[0].isSoup).toBe(true);
expect(den[0].price).toBe('45\xA0Kč');
expect(den[0].allergens?.length).toBeGreaterThan(0);
expect(den.slice(1).some(f => f.isSoup)).toBe(false);
}
});
test('hodí StaleWeekError pro jiný týden', async () => {
await expect(getMenuTechTower(new Date('2026-10-12'))).rejects.toBeInstanceOf(StaleWeekError);
});
});
describe('SenkSerikova parser', () => {
beforeEach(() => {
// SenkSerikova parsuje arraybuffer – musíme vrátit Buffer, ne string
mockedAxios.get = jest.fn().mockResolvedValue({
data: Buffer.from(loadFixture('senkserikova.html')),
headers: {}
});
});
test('parser provede HTTP request a vrátí pole', async () => {
const menu = await getMenuSenkSerikova(MONDAY);
expect(mockedAxios.get).toHaveBeenCalledTimes(1);
expect(Array.isArray(menu)).toBe(true);
});
test('výsledné dny s obsahem mají správnou strukturu (name, price, isSoup)', async () => {
const menu = await getMenuSenkSerikova(MONDAY);
// Protože MONDAY je v minulosti, parser vrátí placeholdery pro všechny pracovní
// dny a .menicka elementy přidá za ně – hledáme aspoň jeden den s reálnými daty
const denSJidlem = menu.find(den =>
den.length > 0 && den[0].name !== 'Pro tento den není uveřejněna nabídka jídel'
);
if (denSJidlem) {
expect(typeof denSJidlem[0].name).toBe('string');
expect(typeof denSJidlem[0].isSoup).toBe('boolean');
}
});
});