Files
personal-website/src/lib/house-tracker/pararius.test.ts
Stan Runge 0846cf7d77 feat: house tracker with home worker, alerts and listing map
Collects Pararius rent/buy listings into Postgres via a worker that runs on a
residential connection, sends Discord/ntfy alerts for new listings matching
rules, and shows an admin-only map, market stats and worker log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HfF5tnChi8tkCx9svJP6D
2026-10-11 20:57:20 +00:00

159 lines
5.1 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import {
cityFromSlug,
isChallenge,
listingRef,
parseDetailPage,
parseEuros,
parseLocation,
parseSearchPage,
searchPageUrl
} from './pararius';
import { listingDetailSchema, listingCardSchema } from './types';
const fixture = (name: string) =>
readFileSync(new URL(`./fixtures/${name}`, import.meta.url), 'utf8');
describe('pararius helpers', () => {
it('reads listing urls', () => {
expect(listingRef('/appartement-te-huur/gouda/541493b8/graaf-hendrikstraat')).toEqual({
sourceId: '541493b8',
url: 'https://www.pararius.nl/appartement-te-huur/gouda/541493b8/graaf-hendrikstraat',
offer: 'rent',
propertyType: 'apartment',
citySlug: 'gouda'
});
expect(listingRef('https://www.pararius.nl/huis-te-koop/den-haag/0a1b2c3d/laan')?.offer).toBe(
'buy'
);
expect(listingRef('/huurwoningen/gouda')).toBeNull();
});
it('parses Dutch and English prices', () => {
expect(parseEuros('€ 1.558 per maand')).toBe(1558);
expect(parseEuros('€1,600 per month')).toBe(1600);
expect(parseEuros('€ 425.000 k.k.')).toBe(425000);
expect(parseEuros('€ 1.250,50')).toBe(1250);
expect(parseEuros('Prijs op aanvraag')).toBeNull();
});
it('splits a location line', () => {
expect(parseLocation('2801 BC Gouda (Centrum)')).toEqual({
postalCode: '2801 BC',
city: 'Gouda',
neighbourhood: 'Centrum'
});
expect(parseLocation('Alphen aan den Rijn')).toEqual({
postalCode: null,
city: 'Alphen aan den Rijn',
neighbourhood: null
});
});
it('names cities from slugs', () => {
expect(cityFromSlug('gouda')).toBe('Gouda');
expect(cityFromSlug('den-haag')).toBe('Den Haag');
expect(cityFromSlug('alphen-aan-den-rijn')).toBe('Alphen aan den Rijn');
});
it('builds page urls', () => {
const base = 'https://www.pararius.nl/huurwoningen/gouda/1300-1700';
expect(searchPageUrl(base, 1)).toBe(base);
expect(searchPageUrl(base, 3)).toBe(`${base}/page-3`);
expect(searchPageUrl(`${base}/page-3`, 2)).toBe(`${base}/page-2`);
});
it('detects Cloudflare challenges', () => {
expect(isChallenge('<html><head><title>Just a moment...</title>')).toBe(true);
expect(isChallenge('<html>ok</html>', 403)).toBe(true);
expect(isChallenge(fixture('pararius-detail-house.html'))).toBe(false);
});
});
describe('parseDetailPage', () => {
it('reads a house listing', () => {
const detail = parseDetailPage(fixture('pararius-detail-house.html'))!;
expect(listingDetailSchema.parse(detail)).toBeTruthy();
expect(detail).toMatchObject({
sourceId: '8f976237',
offer: 'rent',
propertyType: 'house',
externalId: '8f976237-2ce0-5613-adbe-883495268361',
street: 'Kongsbergstraat 45',
postalCode: '2804 XV',
city: 'Gouda',
neighbourhood: 'De Mammoet',
priceEur: 1600,
areaM2: 113,
rooms: 5,
bedrooms: 4,
constructionYear: 1981,
energyLabel: 'B',
depositEur: 1600,
agent: 'LIV Residential',
offeredAt: '2026-09-30T09:47:00.000Z'
});
expect(detail.lat).toBeCloseTo(52.0284, 3);
expect(detail.lng).toBeCloseTo(4.7174, 3);
});
it('reads interior and availability', () => {
const detail = parseDetailPage(fixture('pararius-detail-apartment.html'))!;
expect(detail).toMatchObject({
propertyType: 'apartment',
priceEur: 1558,
interior: 'gestoffeerd',
availableFrom: 'Per 15-10-2026',
contractType: 'Onbepaalde tijd',
depositEur: 3316,
offeredAt: '2026-10-02T05:35:37.000Z'
});
});
it('keeps price-on-request listings', () => {
const detail = parseDetailPage(fixture('pararius-detail-on-request.html'))!;
expect(detail.priceEur).toBeNull();
expect(detail.interior).toBe('gemeubileerd');
expect(listingDetailSchema.parse(detail)).toBeTruthy();
});
it('rejects pages that are not listings', () => {
expect(parseDetailPage('<html><body>nothing</body></html>')).toBeNull();
});
});
describe('parseSearchPage', () => {
it('reads listing cards and pagination', () => {
const page = parseSearchPage(
fixture('pararius-search-synthetic.html'),
'https://www.pararius.nl/huurwoningen/gouda'
);
expect(page.cards).toHaveLength(3);
for (const card of page.cards) expect(listingCardSchema.parse(card)).toBeTruthy();
expect(page.cards[0]).toMatchObject({
sourceId: 'd5d8a72c',
title: 'Appartement Raam',
postalCode: '2801 VN',
city: 'Gouda',
neighbourhood: 'Binnenstad',
priceEur: 1475,
areaM2: 58,
rooms: 3,
interior: 'Gestoffeerd',
agent: 'Voorbeeld Makelaardij'
});
expect(page.cards[1].priceEur).toBe(1600);
expect(page.cards[2].priceEur).toBeNull();
expect(page.nextPageUrl).toBe('https://www.pararius.nl/huurwoningen/gouda/page-2');
expect(page.totalResults).toBe(12);
});
it('falls back to bare links when card markup changes', () => {
const html = `<div><a href="/huis-te-huur/gouda/0a1b2c3d/straat">Huis Straat 1</a> € 1.400 per maand 80 m²</div>`;
const page = parseSearchPage(html, 'https://www.pararius.nl/huurwoningen/gouda');
expect(page.cards).toHaveLength(1);
expect(page.cards[0]).toMatchObject({ sourceId: '0a1b2c3d', priceEur: 1400, areaM2: 80 });
});
});