101 lines
4.7 KiB
JavaScript
101 lines
4.7 KiB
JavaScript
// tests/providers/ibm.test.mjs — moved verbatim from test-all.mjs (#1440).
|
|
import { pass, fail, ROOT } from '../helpers.mjs';
|
|
import { join } from 'path';
|
|
import { pathToFileURL } from 'url';
|
|
|
|
console.log('\nProvider — ibm');
|
|
|
|
try {
|
|
const ibmModule = await import(pathToFileURL(join(ROOT, 'providers/ibm.mjs')).href);
|
|
const ibm = ibmModule.default;
|
|
const { parseIbmResponse, buildPostFilter } = ibmModule;
|
|
|
|
if (ibm.id === 'ibm') pass('ibm.id is "ibm"');
|
|
else fail(`ibm.id is ${JSON.stringify(ibm.id)}`);
|
|
|
|
// buildPostFilter — empty config yields no filter terms
|
|
if (buildPostFilter({}).bool.must.length === 0) pass('buildPostFilter({}) → no must terms');
|
|
else fail(`buildPostFilter({}) = ${JSON.stringify(buildPostFilter({}))}`);
|
|
|
|
// buildPostFilter — country + categories produce the expected facet terms
|
|
const pf = buildPostFilter({ country: 'Germany', categories: ['Software Engineering', 'Data & Analytics'] });
|
|
const countryTerm = pf.bool.must.find(m => m.term && m.term.field_keyword_05);
|
|
const catTerm = pf.bool.must.find(m => m.bool && m.bool.should);
|
|
if (countryTerm?.term.field_keyword_05 === 'Germany' && catTerm?.bool.should.length === 2) {
|
|
pass('buildPostFilter maps country → field_keyword_05 and categories → field_keyword_08 should[]');
|
|
} else {
|
|
fail(`buildPostFilter facets = ${JSON.stringify(pf)}`);
|
|
}
|
|
|
|
// buildPostFilter — sanitizes empty/non-string category entries
|
|
const sanitized = buildPostFilter({ categories: ['Valid', '', ' ', 42, null] });
|
|
const sanitizedShould = sanitized.bool.must.find(m => m.bool && m.bool.should)?.bool.should;
|
|
if (sanitizedShould?.length === 1 && sanitizedShould[0].term.field_keyword_08 === 'Valid') {
|
|
pass('buildPostFilter drops empty/non-string category entries');
|
|
} else {
|
|
fail(`buildPostFilter sanitization = ${JSON.stringify(sanitizedShould)}`);
|
|
}
|
|
|
|
// parseIbmResponse — happy path, location assembled from keyword_19 · keyword_17
|
|
const sample = {
|
|
hits: {
|
|
hits: [
|
|
{ _source: { title: 'ML Engineer', url: 'https://ibm.com/careers/1', field_keyword_19: 'Berlin, Germany', field_keyword_17: 'Hybrid' } },
|
|
{ _source: { title: 'Data Scientist', url: 'https://ibm.com/careers/2', field_keyword_19: 'Remote' } },
|
|
],
|
|
},
|
|
};
|
|
const jobs = parseIbmResponse(sample);
|
|
if (jobs.length === 2 && jobs[0].company === 'IBM') pass('parseIbmResponse extracts 2 jobs with company "IBM"');
|
|
else fail(`parseIbmResponse returned ${JSON.stringify(jobs)}`);
|
|
|
|
if (jobs[0].location === 'Berlin, Germany · Hybrid') pass('parseIbmResponse joins location · work mode');
|
|
else fail(`row 0 location = ${JSON.stringify(jobs[0]?.location)}`);
|
|
|
|
if (jobs[1].location === 'Remote') pass('parseIbmResponse omits the separator when work mode is absent');
|
|
else fail(`row 1 location = ${JSON.stringify(jobs[1]?.location)}`);
|
|
|
|
// parseIbmResponse — drops title-less, url-less, and non-http(s) entries
|
|
const dirty = parseIbmResponse({
|
|
hits: {
|
|
hits: [
|
|
{ _source: { title: '', url: 'https://ibm.com/careers/3' } },
|
|
{ _source: { title: 'No URL' } },
|
|
{ _source: { title: 'Bad scheme', url: 'ftp://ibm.com/careers/4' } },
|
|
{ _source: { title: 'Good', url: 'https://ibm.com/careers/5' } },
|
|
],
|
|
},
|
|
});
|
|
if (dirty.length === 1 && dirty[0].title === 'Good') pass('parseIbmResponse drops title-less, url-less, and non-http(s) entries');
|
|
else fail(`parseIbmResponse dirty = ${JSON.stringify(dirty)}`);
|
|
|
|
// parseIbmResponse — throws on unexpected shape (endpoint drift surfaces loudly)
|
|
let drifted = false;
|
|
try { parseIbmResponse({ results: [] }); } catch { drifted = true; }
|
|
if (drifted) pass('parseIbmResponse throws when hits.hits[] is missing');
|
|
else fail('parseIbmResponse should throw on unexpected API response shape');
|
|
|
|
// fetch() — paginates until a short page, via mock ctx
|
|
let calls = 0;
|
|
const mockCtx = {
|
|
fetchJson: async (url, opts) => {
|
|
calls++;
|
|
if (url !== 'https://www-api.ibm.com/search/api/v2') throw new Error(`unexpected url ${url}`);
|
|
if (opts?.method !== 'POST') throw new Error('Expected POST');
|
|
// Page 1: a full page (30 hits) → keep paging; page 2: short page → stop.
|
|
const n = calls === 1 ? 30 : 2;
|
|
const hits = Array.from({ length: n }, (_, i) => ({
|
|
_source: { title: `Role ${calls}-${i}`, url: `https://ibm.com/careers/${calls}-${i}` },
|
|
}));
|
|
return { hits: { hits } };
|
|
},
|
|
};
|
|
const fetched = await ibm.fetch({ name: 'IBM', ibm: { country: 'Germany' } }, mockCtx);
|
|
if (calls === 2 && fetched.length === 32) pass('ibm.fetch() paginates and stops on the first short page');
|
|
else fail(`ibm.fetch() made ${calls} calls, returned ${fetched.length} jobs`);
|
|
|
|
} catch (e) {
|
|
fail(`ibm provider tests crashed: ${e.message}`);
|
|
}
|
|
|