// tests/providers/radancy.test.mjs — Radancy (TalentBrew) SSR search-results parser. import { pass, fail, ROOT } from '../helpers.mjs'; import { join } from 'path'; import { pathToFileURL } from 'url'; console.log('\nProvider — radancy (TalentBrew SSR search-results parser)'); try { const radancyModule = await import(pathToFileURL(join(ROOT, 'providers/radancy.mjs')).href); const radancy = radancyModule.default; const { resolveListUrl: radListUrl, parseResults } = radancyModule; if (radancy.id === 'radancy') pass('radancy.id is "radancy"'); else fail(`radancy.id is ${JSON.stringify(radancy.id)}`); // resolveListUrl — keeps explicit search-jobs URLs, defaults to /{lang}. if (radListUrl({ api: 'https://careers.munichre.com/en/search-jobs' }) === 'https://careers.munichre.com/en/search-jobs') pass('radancy.resolveListUrl() keeps an explicit search-jobs URL'); else fail('radancy.resolveListUrl() should keep the search-jobs URL'); if (radListUrl({ careers_url: 'https://careers.munichre.com/de/some-page' }) === 'https://careers.munichre.com/de/search-jobs') pass('radancy.resolveListUrl() defaults to /{lang}/search-jobs'); else fail(`radancy.resolveListUrl() default wrong: ${radListUrl({ careers_url: 'https://careers.munichre.com/de/some-page' })}`); // detect — never auto-claims (branded hosts, wire explicitly). if (radancy.detect({ careers_url: 'https://careers.munichre.com/en/search-jobs' }) === null) pass('radancy.detect() returns null (explicit wiring only)'); else fail('radancy.detect() should not auto-claim'); // parseResults — the tricky part: anchor on the STABLE generic class prefix, // read title + location within one
  • , resolve the relative href. const card = (id, title, loc) => '
  • ' + '
    ' + `
    ${title}
    ` + `` + '
  • '; const html = '' + card('40548453568', 'Innendienst für Versicherungsagentur', 'Bingen am Rhein, Germany') + card('40546200896', 'Category Manager', 'London, United Kingdom') + ''; const rows = parseResults(html, 'https://careers.munichre.com'); if (rows.length === 2) pass('radancy.parseResults() yields one row per search-results item'); else fail(`radancy.parseResults() returned ${rows.length}, expected 2`); if (rows[0]?.title === 'Innendienst für Versicherungsagentur') pass('radancy.parseResults() decodes entities in titles'); else fail(`radancy.parseResults() title wrong: ${JSON.stringify(rows[0]?.title)}`); if (rows[0]?.url === 'https://careers.munichre.com/en/job/city/40548453568-slug/3193/40548453568' && rows[0]?.location === 'Bingen am Rhein, Germany') pass('radancy.parseResults() builds absolute URLs and extracts the location span'); else fail(`radancy.parseResults() url/loc wrong: ${JSON.stringify(rows[0])}`); if (parseResults('no items', 'https://x').length === 0 && parseResults(undefined, 'https://x').length === 0) pass('radancy.parseResults() returns [] for item-less / non-string input'); else fail('radancy.parseResults() should return [] without items'); // decodeEntities (exercised via parseResults) — a malformed/out-of-range // numeric entity (a lone surrogate half) must degrade to the literal text, // never throw RangeError and abort the whole parse. const badEntityCard = card('999', 'Bad�Entity', 'Berlin, Germany'); const badRows = parseResults('' + badEntityCard + '', 'https://careers.munichre.com'); if (badRows.length === 1 && badRows[0].title === 'Bad�Entity') pass('radancy.parseResults() tolerates an invalid numeric entity (no RangeError crash)'); else fail(`radancy.parseResults() should degrade a malformed entity to literal text, got: ${JSON.stringify(badRows)}`); // fetch — paginates ?p=N, stops when a page brings no fresh ids. const radPages = [html, '' + card('111', 'C', 'Kiel, Germany') + '', '']; let radCalls = 0; const radSeen = []; const radCtx = { sleep: async () => {}, fetchText: async (url) => { radSeen.push(url); return radPages[radCalls++] ?? ''; } }; const radJobs = await radancy.fetch({ name: 'Munich Re', api: 'https://careers.munichre.com/en/search-jobs' }, radCtx); if (radJobs.length === 3 && radCalls === 3) pass('radancy.fetch() paginates and stops on the first empty page'); else fail(`radancy.fetch() returned ${radJobs.length} jobs after ${radCalls} calls`); if (radSeen[0]?.endsWith('?p=1') && radSeen[1]?.endsWith('?p=2')) pass('radancy.fetch() pages via ?p=N (1-based)'); else fail(`radancy.fetch() paged wrong: ${JSON.stringify(radSeen)}`); // fetch — a mid-scan failure preserves jobs already collected, never // discards earlier pages. let partialCalls = 0; const partialCtx = { sleep: async () => {}, fetchText: async () => { partialCalls++; if (partialCalls === 1) return html; // 2 jobs throw new Error('network blip on page 2'); }, }; const partialJobs = await radancy.fetch({ name: 'Munich Re', api: 'https://careers.munichre.com/en/search-jobs' }, partialCtx); if (partialJobs.length === 2 && partialCalls === 2) pass('radancy.fetch() preserves jobs from earlier pages when a later page fetch throws'); else fail(`radancy.fetch() partial-failure handling wrong: ${partialJobs.length} jobs after ${partialCalls} calls`); // fetch — stops on a page whose ids are all already-seen (server clamped // ?p= to the last page, or looped), NOT just on a literally empty page. // fresh === 0 must halt pagination without appending duplicate jobs. const dupPage = '' + card('40548453568', 'Innendienst für Versicherungsagentur', 'Bingen am Rhein, Germany') + ''; const dupPages = [html, dupPage, '' + card('999', 'Never reached', 'X') + '']; let dupCalls = 0; const dupCtx = { sleep: async () => {}, fetchText: async () => dupPages[dupCalls++] ?? '' }; const dupJobs = await radancy.fetch({ name: 'Munich Re', api: 'https://careers.munichre.com/en/search-jobs' }, dupCtx); if (dupJobs.length === 2 && dupCalls === 2) pass('radancy.fetch() stops when a page brings only already-seen ids (fresh === 0), without appending duplicates'); else fail(`radancy.fetch() duplicate-page stop wrong: ${dupJobs.length} jobs after ${dupCalls} calls`); } catch (e) { fail(`radancy provider tests crashed: ${e.message}`); }