Files
santifer--career-ops/tests/providers/workday.test.mjs
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

592 lines
33 KiB
JavaScript

// tests/providers/workday.test.mjs — moved verbatim from test-all.mjs (#1440).
import { pass, fail, warn, run, ROOT, captureConsoleErrors } from '../helpers.mjs';
import { join } from 'path';
import { pathToFileURL } from 'url';
console.log('\nProvider — workday');
try {
const workdayModule = await import(pathToFileURL(join(ROOT, 'providers/workday.mjs')).href);
const workday = workdayModule.default;
const { parseWorkdayResponse } = workdayModule;
// Shared mock ctx shape for workday.fetch() calls below — only fetchJson varies per test.
// sleep is a no-op so retry-backoff delays don't slow the test suite down.
const mkWorkdayCtx = (fetchJson, extra = {}) => ({
transport: 'http',
fetchText: async () => { throw new Error('fetchText should not be called'); },
fetchJson,
sleep: async () => {},
...extra,
});
if (workday.id === 'workday') pass('workday.id is "workday"');
else fail(`workday.id is ${JSON.stringify(workday.id)}`);
// detect() — valid Workday URLs
const hitUs = workday.detect({ name: 'Acme', careers_url: 'https://acme.wd12.myworkdayjobs.com/en-US/acme-jobs' });
if (hitUs && hitUs.url === 'https://acme.wd12.myworkdayjobs.com/wday/cxs/acme/acme-jobs/jobs') {
pass('workday.detect() resolves wd12 URL to CXS API endpoint');
} else {
fail(`workday.detect(wd12) returned ${JSON.stringify(hitUs)}`);
}
const hitNoLocale = workday.detect({ name: 'Test', careers_url: 'https://test.wd5.myworkdayjobs.com/TestBoard' });
if (hitNoLocale && hitNoLocale.url === 'https://test.wd5.myworkdayjobs.com/wday/cxs/test/TestBoard/jobs') {
pass('workday.detect() works without locale segment in path');
} else {
fail(`workday.detect(no-locale) returned ${JSON.stringify(hitNoLocale)}`);
}
// detect() — null cases
if (workday.detect({ name: 'X', careers_url: 'https://example.com/careers' }) === null) {
pass('workday.detect() returns null for non-Workday URL');
} else {
fail('workday.detect() should return null for non-Workday URL');
}
// entry.api precedence: a branded careers_url is kept while the Workday tenant
// is pinned via api: (mirrors greenhouse/ashby).
const hitApiWd = workday.detect({
name: 'PTC',
careers_url: 'https://www.ptc.com/en/careers',
api: 'https://ptc.wd1.myworkdayjobs.com/PTC',
});
if (hitApiWd && hitApiWd.url === 'https://ptc.wd1.myworkdayjobs.com/wday/cxs/ptc/PTC/jobs') {
pass('workday.detect() honors api: over a branded careers_url');
} else {
fail(`workday.detect(api-pinned) returned ${JSON.stringify(hitApiWd)}`);
}
// A non-Workday api: must not shadow a valid Workday careers_url — resolution
// falls through to the next candidate instead of returning null.
const hitFallthrough = workday.detect({
name: 'Acme',
api: 'https://acme.com/careers',
careers_url: 'https://acme.wd12.myworkdayjobs.com/en-US/acme-jobs',
});
if (hitFallthrough && hitFallthrough.url === 'https://acme.wd12.myworkdayjobs.com/wday/cxs/acme/acme-jobs/jobs') {
pass('workday.detect() falls through a non-Workday api: to a valid careers_url');
} else {
fail(`workday.detect(fallthrough) returned ${JSON.stringify(hitFallthrough)}`);
}
// Path-spoofed URL: myworkdayjobs.com in path, not hostname
if (workday.detect({ name: 'Spoof', careers_url: 'https://evil.example/test.wd5.myworkdayjobs.com/en-US/board' }) === null) {
pass('workday.detect() rejects path-spoofed URL');
} else {
fail('workday.detect() must NOT detect path-spoofed URLs');
}
// Non-string careers_url
if (workday.detect({ name: 'X', careers_url: null }) === null && workday.detect({ name: 'X' }) === null) {
pass('workday.detect() returns null for null / missing careers_url');
} else {
fail('workday.detect() should return null for non-string careers_url');
}
// parseWorkdayResponse — normalization
const sampleJson = {
jobPostings: [
{ title: 'Senior QA Engineer', externalPath: '/job/board/Senior-QA-Engineer_JR001', locationsText: 'Berlin, Germany', postedOn: 'Posted 2 Days Ago' },
{ title: 'Lead Developer', externalPath: '/job/board/Lead-Developer_JR002', locationsText: 'Remote' },
{ title: '', externalPath: '/job/board/No-Title_JR003' }, // no title — skip
{ title: 'No Path Role', externalPath: '' }, // no externalPath — skip
{ title: 'Also No Path' }, // undefined externalPath — skip
],
};
const entry = { name: 'Acme', careers_url: 'https://acme.wd12.myworkdayjobs.com/en-US/acme-jobs' };
const parsed = parseWorkdayResponse(sampleJson, entry);
if (parsed.length === 2) pass('parseWorkdayResponse extracts 2 valid jobs (skips missing title/path)');
else fail(`parseWorkdayResponse returned ${parsed.length} jobs, expected 2`);
if (parsed[0].title === 'Senior QA Engineer' && parsed[0].location === 'Berlin, Germany') {
pass('parseWorkdayResponse maps title and location');
} else {
fail(`row 0 = ${JSON.stringify(parsed[0])}`);
}
if (parsed[0].url.includes('acme-jobs') && parsed[0].url.includes('/job/board/Senior-QA-Engineer_JR001')) {
pass('parseWorkdayResponse builds URL from jobBase + externalPath');
} else {
fail(`row 0 url = ${JSON.stringify(parsed[0].url)}`);
}
if (parsed[0].company === 'Acme') pass('parseWorkdayResponse sets company from entry.name');
else fail(`parseWorkdayResponse company = ${JSON.stringify(parsed[0].company)}`);
// parseWorkdayResponse — location fallback from URL path
const noLocEntry = { name: 'Globex', careers_url: 'https://globex.wd103.myworkdayjobs.com/globexcareers' };
const noLocJson = {
jobPostings: [
{ title: 'Quality Engineer', externalPath: '/job/Mumbai/Quality-Engineer_ATCI-123' }, // no locationsText
{ title: 'Test Lead', externalPath: '/job/Remote-Poland/Test-Lead_ATCI-456', locationsText: '' }, // empty locationsText
{ title: 'QA Analyst', externalPath: '/job/Remote-Hungary/QA-Analyst_ATCI-789', locationsText: 'Remote, Hungary' }, // has locationsText — use it
],
};
const noLocParsed = parseWorkdayResponse(noLocJson, noLocEntry);
if (noLocParsed[0]?.location === 'Mumbai') pass('parseWorkdayResponse falls back to URL path location when locationsText absent');
else fail(`parseWorkdayResponse path fallback: expected "Mumbai", got ${JSON.stringify(noLocParsed[0]?.location)}`);
if (noLocParsed[1]?.location === 'Remote Poland') pass('parseWorkdayResponse falls back to URL path location when locationsText empty');
else fail(`parseWorkdayResponse path fallback empty: expected "Remote Poland", got ${JSON.stringify(noLocParsed[1]?.location)}`);
if (noLocParsed[2]?.location === 'Remote, Hungary') pass('parseWorkdayResponse prefers locationsText over URL path when present');
else fail(`parseWorkdayResponse locationsText priority: expected "Remote, Hungary", got ${JSON.stringify(noLocParsed[2]?.location)}`);
// parseWorkdayResponse — malformed percent-encoding in the URL path segment
// must not throw (decodeURIComponent) and must not abort processing of
// other job records in the same response.
const malformedPathJson = {
jobPostings: [
{ title: 'Broken Encoding', externalPath: '/job/%E0%A4%A/Broken-Encoding_JR1' },
{ title: 'Fine Job', externalPath: '/job/Berlin/Fine-Job_JR2' },
],
};
try {
const malformedParsed = parseWorkdayResponse(malformedPathJson, entry);
if (malformedParsed.length === 2 && malformedParsed[1].location === 'Berlin') {
pass('parseWorkdayResponse tolerates malformed percent-encoding without dropping other records');
} else {
fail(`parseWorkdayResponse malformed encoding result = ${JSON.stringify(malformedParsed)}`);
}
} catch (e4) {
fail(`parseWorkdayResponse should not throw on malformed percent-encoding: ${e4.message}`);
}
// parseWorkdayResponse — empty / malformed input
if (parseWorkdayResponse({}, entry).length === 0) pass('parseWorkdayResponse({}) → empty result');
else fail('parseWorkdayResponse({}) should be empty');
if (parseWorkdayResponse({ jobPostings: null }, entry).length === 0) {
pass('parseWorkdayResponse handles null jobPostings');
} else {
fail('parseWorkdayResponse null jobPostings should be empty');
}
// fetch() with mock ctx — uses total field to bound sequential pagination
let postRequests = 0;
const capturedRedirects = [];
const fetchedJobs = await workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
postRequests++;
capturedRedirects.push(opts?.redirect);
const body = JSON.parse(opts.body);
if (body.offset === 0) {
return { total: 30, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job P1-${i}`, externalPath: `/job/board/p1-${i}` })) };
}
return { total: 30, jobPostings: Array.from({ length: 10 }, (_, i) => ({ title: `Job P2-${i}`, externalPath: `/job/board/p2-${i}` })) };
}));
if (postRequests === 2 && fetchedJobs.length === 30) {
pass('workday.fetch() uses total field to fetch exact pages sequentially (20+10=30)');
} else {
fail(`fetch pagination: requests=${postRequests}, total=${fetchedJobs.length} (expected 2 requests / 30 jobs)`);
}
if (capturedRedirects.length === 2 && capturedRedirects.every(r => r === 'error')) {
pass('workday.fetch() passes redirect:"error" on every page (SSRF guard)');
} else {
fail(`workday.fetch() redirect opts across pages = ${JSON.stringify(capturedRedirects)}`);
}
// parseWorkdayResponse — null/undefined entries in jobPostings must be
// skipped, not crash
const sparseWorkday = { jobPostings: [null, undefined, { title: 'Real Job', externalPath: '/job/board/real-job' }] };
try {
const parsedSparseWorkday = parseWorkdayResponse(sparseWorkday, entry);
if (parsedSparseWorkday.length === 1 && parsedSparseWorkday[0].title === 'Real Job') {
pass('parseWorkdayResponse skips null/undefined entries without crashing');
} else {
fail(`parseWorkdayResponse sparse result = ${JSON.stringify(parsedSparseWorkday)}`);
}
} catch (e2) {
fail(`parseWorkdayResponse should not throw on null/undefined entries: ${e2.message}`);
}
// fetch() pagination cap — an inflated `total` must not trigger unbounded
// requests (DEFAULT_MAX_PAGES = 100 in providers/workday.mjs), and hitting
// the cap must be visible (console.error), not silent — real tenants
// (Dollar Tree, total=23,609; CVS Health, total=16,974) already exceed the
// 100-page/2000-job default.
let hugeWorkdayRequests = 0;
const { result: fetchedHugeWorkday, errors: capturedWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async () => {
hugeWorkdayRequests++;
return { total: 1_000_000, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job ${i}`, externalPath: `/job/board/${i}` })) };
})));
if (hugeWorkdayRequests === 100 && fetchedHugeWorkday.length === 2000) {
pass('workday.fetch() caps pagination at DEFAULT_MAX_PAGES despite an inflated total');
} else {
fail(`workday fetch pagination cap: requests=${hugeWorkdayRequests}, total=${fetchedHugeWorkday.length} (expected 100/2000)`);
}
if (capturedWarnings.some(w => /truncated at max_pages=\d+/.test(w))) {
pass('workday.fetch() warns (console.error) when the cap truncates real results');
} else {
fail(`workday fetch cap: expected a truncation warning, got ${JSON.stringify(capturedWarnings)}`);
}
// fetch() pagination cap — entry.max_pages raises the cap for a genuinely
// large tenant (e.g. Deutsche Bank-scale postings)
let overriddenWorkdayRequests = 0;
const bigWorkdayEntry = { name: 'BigCo', careers_url: 'https://bigco.wd5.myworkdayjobs.com/careers', max_pages: 80 };
const fetchedOverriddenWorkday = await workday.fetch(bigWorkdayEntry, mkWorkdayCtx(async () => {
overriddenWorkdayRequests++;
return { total: 1_000_000, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job ${i}`, externalPath: `/job/board/${i}` })) };
}));
if (overriddenWorkdayRequests === 80 && fetchedOverriddenWorkday.length === 1600) {
pass('workday.fetch() honors entry.max_pages to raise the cap above the default');
} else {
fail(`workday fetch max_pages override: requests=${overriddenWorkdayRequests}, total=${fetchedOverriddenWorkday.length} (expected 80/1600)`);
}
// entry.max_pages is itself capped (MAX_PAGES_CAP = 1500) — an absurd
// override can't turn this into an unbounded scan either.
let absurdWorkdayRequests = 0;
const absurdWorkdayEntry = { name: 'AbsurdCo', careers_url: 'https://absurdco.wd5.myworkdayjobs.com/careers', max_pages: 100_000 };
const fetchedAbsurdWorkday = await workday.fetch(absurdWorkdayEntry, mkWorkdayCtx(async () => {
absurdWorkdayRequests++;
return { total: 10_000_000, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job ${i}`, externalPath: `/job/board/${i}` })) };
}));
if (absurdWorkdayRequests === 1500 && fetchedAbsurdWorkday.length === 30_000) {
pass('workday.fetch() caps an absurd entry.max_pages at MAX_PAGES_CAP');
} else {
fail(`workday fetch max_pages hard cap: requests=${absurdWorkdayRequests}, total=${fetchedAbsurdWorkday.length} (expected 1500/30000)`);
}
// Invalid max_pages values (negative, zero, non-numeric) fall back to
// DEFAULT_MAX_PAGES, same as omitting max_pages entirely.
for (const invalidMaxPages of [-5, 0, 'abc', NaN, null]) {
let invalidRequests = 0;
const invalidEntry = { name: 'InvalidCo', careers_url: 'https://invalidco.wd5.myworkdayjobs.com/careers', max_pages: invalidMaxPages };
const fetchedInvalid = await workday.fetch(invalidEntry, mkWorkdayCtx(async () => {
invalidRequests++;
return { total: 1_000_000, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job ${i}`, externalPath: `/job/board/${i}` })) };
}));
// Number.isNaN(NaN) is true but JSON.stringify(NaN) === 'null', which would
// be indistinguishable from the literal `null` case in the log below.
const label = Number.isNaN(invalidMaxPages) ? 'NaN' : JSON.stringify(invalidMaxPages);
if (invalidRequests === 100 && fetchedInvalid.length === 2000) {
pass(`workday.fetch() falls back to DEFAULT_MAX_PAGES for invalid max_pages=${label}`);
} else {
fail(`workday fetch invalid max_pages=${label}: requests=${invalidRequests}, total=${fetchedInvalid.length} (expected 100/2000)`);
}
}
// fetch() pagination — a failure that persists across every retry attempt
// on a later page returns the jobs gathered so far instead of discarding
// everything (sequential, not Promise.all), retries MAX_RETRIES+1=4 times
// on that page before giving up, and the failure itself is visible
// (console.error), not silent. The truncation ("raise max_pages") warning
// must NOT also fire — that knob does nothing for a rate-limited tenant.
let flakyWorkdayRequests = 0;
const { result: flakyWorkdayJobs, errors: flakyWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
flakyWorkdayRequests++;
const body = JSON.parse(opts.body);
const page = body.offset / 20; // PAGE_SIZE in providers/workday.mjs
if (page === 2) { const err = new Error('HTTP 503'); err.status = 503; throw err; }
return { total: 80, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job p${page}-${i}`, externalPath: `/job/board/p${page}-${i}` })) };
})));
if (flakyWorkdayRequests === 6 && flakyWorkdayJobs.length === 40) {
pass('workday.fetch() retries a failing page 4x then returns partial results');
} else {
fail(`workday fetch partial failure: requests=${flakyWorkdayRequests}, total=${flakyWorkdayJobs.length} (expected 6/40)`);
}
if (flakyWarnings.some(w => /truncated at \d+ of \d+ pages after 4 attempts/.test(w))) {
pass('workday.fetch() warns (console.error) when a page fetch fails after exhausting retries, with the attempt count');
} else {
fail(`workday fetch page failure: expected a fetch-failed warning, got ${JSON.stringify(flakyWarnings)}`);
}
// The failure message must show scale — which page out of how many were
// planned, and how many jobs came back out of the tenant's real total —
// not just "page 3, 40 jobs" with no sense of how much was actually lost.
// Same "truncated at ... (N of M jobs)" shape as the cap-hit warning below,
// so the two read consistently.
if (flakyWarnings.some(w => /truncated at 3 of 4 pages after 4 attempts \(40 of 80 jobs\): HTTP 503/.test(w))) {
pass('workday.fetch() fetch-failure warning reports scale (page X of Y, N of total jobs)');
} else {
fail(`workday fetch-failure warning missing scale context: ${JSON.stringify(flakyWarnings)}`);
}
if (!flakyWarnings.some(w => /raise max_pages/.test(w))) {
pass('workday.fetch() does NOT fire the "raise max_pages" warning on a fetch-error stop');
} else {
fail(`workday fetch-error stop should not also warn about max_pages: ${JSON.stringify(flakyWarnings)}`);
}
// fetch() retry — a 429 that succeeds on a later attempt is transparent to
// the caller (no jobs lost, no error surfaced) and respects Retry-After.
let retrySleepCalls = [];
let retryAttempts = 0;
const retryEntry = { name: 'RetryCo', careers_url: 'https://retryco.wd5.myworkdayjobs.com/careers' };
const retryJobs = await workday.fetch(retryEntry, mkWorkdayCtx(async () => {
retryAttempts++;
if (retryAttempts === 1) { const err = new Error('HTTP 429'); err.status = 429; err.retryAfter = '1'; throw err; }
return { total: 1, jobPostings: [{ title: 'Recovered Job', externalPath: '/job/board/recovered' }] };
}, { sleep: async (ms) => { retrySleepCalls.push(ms); } }));
if (retryAttempts === 2 && retryJobs.length === 1 && retryJobs[0].title === 'Recovered Job') {
pass('workday.fetch() retries a 429 and recovers transparently');
} else {
fail(`workday 429 retry: attempts=${retryAttempts}, jobs=${JSON.stringify(retryJobs)}`);
}
if (retrySleepCalls[0] === 1000) {
pass('workday.fetch() honors Retry-After header for backoff delay');
} else {
fail(`workday retry-after: expected first backoff delay 1000ms, got ${JSON.stringify(retrySleepCalls)}`);
}
// fetch() retry — a hostile or misconfigured Retry-After (e.g. 86400s = a
// full day) must not be honored verbatim: it's clamped to
// RETRY_MAX_DELAY_MS * 4 (32s) so a single bad header can't stall a
// tenant's fetch indefinitely, defeating the point of a bounded backoff.
let hostileRetrySleepCalls = [];
let hostileRetryAttempts = 0;
const hostileRetryEntry = { name: 'HostileRetryCo', careers_url: 'https://hostileretryco.wd5.myworkdayjobs.com/careers' };
await workday.fetch(hostileRetryEntry, mkWorkdayCtx(async () => {
hostileRetryAttempts++;
if (hostileRetryAttempts === 1) { const err = new Error('HTTP 429'); err.status = 429; err.retryAfter = '86400'; throw err; }
return { total: 0, jobPostings: [] };
}, { sleep: async (ms) => { hostileRetrySleepCalls.push(ms); } }));
if (hostileRetrySleepCalls[0] === 32_000) {
pass('workday.fetch() clamps an oversized Retry-After to RETRY_MAX_DELAY_MS * 4');
} else {
fail(`workday retry-after clamp: expected 32000ms, got ${JSON.stringify(hostileRetrySleepCalls)}`);
}
// fetch() retry — a non-retryable 4xx (e.g. malformed request) breaks
// immediately, without wasting retry attempts.
let non429Attempts = 0;
const { result: non429Jobs } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
non429Attempts++;
const body = JSON.parse(opts.body);
if (body.offset === 0) return { total: 40, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job ${i}`, externalPath: `/job/board/${i}` })) };
const err = new Error('HTTP 400: bad request'); err.status = 400; throw err;
})));
if (non429Attempts === 2 && non429Jobs.length === 20) {
pass('workday.fetch() does not retry a non-retryable 4xx error');
} else {
fail(`workday non-retryable 4xx: attempts=${non429Attempts}, jobs=${non429Jobs.length} (expected 2/20)`);
}
// fetch() early-stop — once a page's postings are all clearly past
// ctx.sinceMs, pagination stops without hitting max_pages, and the
// "raise max_pages" warning does NOT fire (this isn't a cap hit).
const SINCE_DAYS = 3; // mirrors scan-ats-full.mjs's --since default
const nowMs = Date.now();
const sinceMs = nowMs - SINCE_DAYS * 86_400_000;
let earlyStopRequests = 0;
const { result: earlyStopJobs, errors: earlyStopWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
earlyStopRequests++;
const body = JSON.parse(opts.body);
const page = body.offset / 20;
if (page === 0) {
// Page 0: fresh postings, well within the window.
return { total: 1_000_000, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Fresh ${i}`, externalPath: `/job/board/fresh-${i}`, postedOn: 'Posted Today' })) };
}
// Every later page: clearly stale (well past sinceMs - margin) — if
// early-stop didn't work, this mock would be asked for 100 pages.
return { total: 1_000_000, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Stale ${i}`, externalPath: `/job/board/stale-${i}`, postedOn: 'Posted 20 Days Ago' })) };
}, { sinceMs })));
if (earlyStopRequests === 2 && earlyStopJobs.length === 40) {
pass('workday.fetch() stops paginating once a page is past --since (early-stop)');
} else {
fail(`workday early-stop: requests=${earlyStopRequests}, jobs=${earlyStopJobs.length} (expected 2/40)`);
}
if (!earlyStopWarnings.some(w => /truncated at/.test(w))) {
pass('workday.fetch() does NOT warn about max_pages when it stopped early on --since');
} else {
fail(`workday early-stop should not warn about max_pages: ${JSON.stringify(earlyStopWarnings)}`);
}
// fetch() early-stop — a wide --since window (>= 30 days) never triggers
// early-stop off the unbounded "30+ Days Ago" bucket; pagination still
// proceeds until max_pages/total, as before. includeUndated: true isolates
// this from the no-date-skip optimization tested separately below — every
// posting here is undated ("30+"), which would otherwise short-circuit
// after page 1 regardless of --since width.
const WIDE_SINCE_DAYS = 90; // >= 30 — past the "30+ Days Ago" bucket's ambiguity threshold
const wideSinceMs = nowMs - WIDE_SINCE_DAYS * 86_400_000;
let wideRequests = 0;
const wideJobs = await workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
wideRequests++;
const body = JSON.parse(opts.body);
if (body.offset === 0) {
return { total: 40, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Old ${i}`, externalPath: `/job/board/old-${i}`, postedOn: 'Posted 30+ Days Ago' })) };
}
return { total: 40, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Old2 ${i}`, externalPath: `/job/board/old2-${i}`, postedOn: 'Posted 30+ Days Ago' })) };
}, { sinceMs: wideSinceMs, includeUndated: true }));
if (wideRequests === 2 && wideJobs.length === 40) {
pass('workday.fetch() never early-stops off the unbounded "30+ Days Ago" bucket');
} else {
fail(`workday wide-since: requests=${wideRequests}, jobs=${wideJobs.length} (expected 2/40)`);
}
// fetch() cap-hit warning — reverse-scan context (ctx.sinceMs set, as
// scan-ats-full.mjs always does) where entries are synthesized from an
// external dataset, not portals.yml: there's no portal entry to edit, and
// — per the "no fixed cap can guarantee full coverage" conclusion — no
// fix to advise at all, so the message is just the short fact, with
// neither "raise max_pages" nor a portals.yml edit suggested.
// includeUndated: true forces this past the no-date-skip short-circuit
// (tested separately below) so the fetch actually reaches the cap.
const noDateSinceMs = nowMs - SINCE_DAYS * 86_400_000;
const { errors: noDateWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async () => ({
total: 1_000_000,
jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `NoDate ${i}`, externalPath: `/job/board/nodate-${i}` })), // no postedOn
}), { sinceMs: noDateSinceMs, includeUndated: true })));
if (noDateWarnings.some(w => /truncated at \d+ pages/.test(w))) {
pass('workday.fetch() cap-hit warning fires in reverse-scan context (tenant has no dates, includeUndated on)');
} else {
fail(`workday no-date cap warning missing: ${JSON.stringify(noDateWarnings)}`);
}
if (!noDateWarnings.some(w => /raise max_pages|portal entry|portals\.yml/.test(w))) {
pass('workday.fetch() reverse-scan cap warning gives no inactionable advice (no portal entry to edit)');
} else {
fail(`workday reverse-scan cap warning should not suggest editing a portal entry: ${JSON.stringify(noDateWarnings)}`);
}
// fetch() no-date-skip — the default case (includeUndated NOT set, as
// scan-ats-full.mjs leaves it by default): a tenant whose first page has
// zero dated postings stops right there instead of grinding through up to
// maxPages requests whose results would all be dropped as 'undated'
// downstream anyway. Only 1 request should fire, not maxPages (100).
const skipSinceMs = nowMs - SINCE_DAYS * 86_400_000;
let skipRequests = 0;
const { result: skipJobs, errors: skipWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async () => {
skipRequests++;
return {
total: 1_000_000,
jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `NoDate ${i}`, externalPath: `/job/board/skip-${i}` })), // no postedOn
};
}, { sinceMs: skipSinceMs }))); // includeUndated intentionally omitted — the default, falsy case
if (skipRequests === 1 && skipJobs.length === 20) {
pass('workday.fetch() skips pagination after page 1 when includeUndated is off and the tenant has no dated postings');
} else {
fail(`workday no-date-skip: requests=${skipRequests}, jobs=${skipJobs.length} (expected 1/20)`);
}
// A full-directory scan hits this on a large fraction of tenants — a
// console.error per occurrence would just be the same line thousands of
// times, so the signal is a tag on the returned array instead (aggregated
// by scan-ats-full.mjs into one summary line at the end of the run).
if (skipJobs.workdayNoDateSkip === true) {
pass('workday.fetch() tags the returned jobs array for no-date-skip aggregation');
} else {
fail(`workday no-date-skip tag missing: jobs.workdayNoDateSkip = ${JSON.stringify(skipJobs.workdayNoDateSkip)}`);
}
if (skipWarnings.length === 0) {
pass('workday.fetch() does not console.error per-company on a no-date-skip (aggregated instead)');
} else {
fail(`workday no-date-skip should not log anything directly: ${JSON.stringify(skipWarnings)}`);
}
// fetch() no-date-skip — does NOT trigger when includeUndated is true (the
// "hit the cap while genuinely undated" scenario above already covers that
// the fetch continues in that case); this just re-confirms the gate.
let noSkipWithIncludeUndatedRequests = 0;
const noSkipJobs = await workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
noSkipWithIncludeUndatedRequests++;
const body = JSON.parse(opts.body);
if (body.offset === 0) return { total: 40, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `ND ${i}`, externalPath: `/job/board/nd-${i}` })) };
return { total: 40, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `ND2 ${i}`, externalPath: `/job/board/nd2-${i}` })) };
}, { sinceMs: skipSinceMs, includeUndated: true }));
if (noSkipWithIncludeUndatedRequests === 2 && noSkipJobs.length === 40) {
pass('workday.fetch() does not no-date-skip when includeUndated is true');
} else {
fail(`workday no-date-skip gate: requests=${noSkipWithIncludeUndatedRequests}, jobs=${noSkipJobs.length} (expected 2/40)`);
}
// fetch() cap-hit warning — reverse-scan context, tenant genuinely has
// more within --since than the cap allows (total far above the window,
// e.g. cvshealth-scale): short line, no suspect-cap tag.
const datedCapSinceMs = nowMs - SINCE_DAYS * 86_400_000;
const { errors: datedCapWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async () => ({
total: 1_000_000,
jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Fresh ${i}`, externalPath: `/job/board/fresh-${i}`, postedOn: 'Posted Today' })),
}), { sinceMs: datedCapSinceMs })));
if (datedCapWarnings.some(w => /truncated at \d+ pages \(2000 of 1000000 jobs\)/.test(w))) {
pass('workday.fetch() cap-hit warning reports the short "truncated at N pages" form');
} else {
fail(`workday dated cap-hit warning mismatch: ${JSON.stringify(datedCapWarnings)}`);
}
if (!datedCapWarnings.some(w => /Workday-capped/.test(w))) {
pass('workday.fetch() cap-hit warning omits the suspected-Workday-cap tag when total is far above the window');
} else {
fail(`workday cap-hit warning should not suspect a Workday-side cap here (total=1,000,000, window=2,000): ${JSON.stringify(datedCapWarnings)}`);
}
// fetch() cap-hit warning — Workday's own CXS backend has been observed
// reporting `total` as exactly maxPages*PAGE_SIZE even when the real count
// is far higher (verified live: dickssportinggoods reported total=2000 but
// its public careers page listed 7,120 openings, and offset=2000/4000
// requests returned the same first posting as offset=0 instead of new
// results). This exact-match case must carry a distinct, short tag.
const suspectCapSinceMs = nowMs - SINCE_DAYS * 86_400_000;
const { errors: suspectCapWarnings } = await captureConsoleErrors(() =>
workday.fetch(entry, mkWorkdayCtx(async () => ({
total: 2000, // === DEFAULT_MAX_PAGES (100) * PAGE_SIZE (20)
jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Suspect ${i}`, externalPath: `/job/board/suspect-${i}`, postedOn: 'Posted Today' })),
}), { sinceMs: suspectCapSinceMs })));
if (suspectCapWarnings.some(w => /\(total may be Workday-capped, not real\)/.test(w))) {
pass('workday.fetch() cap-hit warning flags a suspected Workday-side total cap when total === maxPages*PAGE_SIZE');
} else {
fail(`workday suspected-cap tag missing: ${JSON.stringify(suspectCapWarnings)}`);
}
// Verify POST method was used
let capturedMethod = null;
await workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => { capturedMethod = opts?.method; return { total: 0, jobPostings: [] }; }));
if (capturedMethod === 'POST') pass('workday.fetch() uses POST method');
else fail(`workday.fetch() method is ${JSON.stringify(capturedMethod)}, expected POST`);
// Fallback: no total field — paginate sequentially until short page
let fallbackRequests = 0;
const fallbackJobs = await workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
fallbackRequests++;
const body = JSON.parse(opts.body);
if (body.offset === 0) return { jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `FB P1-${i}`, externalPath: `/job/board/fb-${i}` })) };
return { jobPostings: Array.from({ length: 5 }, (_, i) => ({ title: `FB P2-${i}`, externalPath: `/job/board/fb2-${i}` })) };
}));
if (fallbackRequests === 2 && fallbackJobs.length === 25) {
pass('workday.fetch() fallback (no total): paginates sequentially, stops on short page (20+5=25)');
} else {
fail(`fallback pagination: requests=${fallbackRequests}, jobs=${fallbackJobs.length} (expected 2/25)`);
}
// fetch() honors ctx.maxPages — verify-portals' liveness probe sets maxPages:1.
// It must stop after the first page and NOT request page 2, which would trip
// the probe's second-request sentinel; fetchPageWithRetry treats that abort as
// transient and retries it MAX_RETRIES times (4 requests) plus a truncation
// warning. Even though total=173 implies 9 pages, the cap must win at 1.
let probeRequests = 0;
const probeWarnings = [];
const probeErr = console.error;
console.error = (m) => probeWarnings.push(m);
let probeJobs;
try {
probeJobs = await workday.fetch(entry, mkWorkdayCtx(async (_url, opts) => {
probeRequests++;
// Simulate the probe sentinel: any request past the first throws.
if (JSON.parse(opts.body).offset > 0) throw new Error('probe budget: no second page');
return { total: 173, jobPostings: Array.from({ length: 20 }, (_, i) => ({ title: `Job ${i}`, externalPath: `/job/board/${i}` })) };
}, { maxPages: 1 }));
} finally {
console.error = probeErr;
}
if (probeRequests === 1 && probeJobs.length === 20) {
pass('workday.fetch() honors ctx.maxPages=1 — one request, no second-page retry storm');
} else {
fail(`workday probe cap: requests=${probeRequests} (expected 1), jobs=${probeJobs?.length} (expected 20)`);
}
if (!probeWarnings.some((w) => /truncated/.test(String(w)))) {
pass('workday.fetch() stays silent (no truncation warning) under the liveness probe cap');
} else {
fail(`workday probe should emit no warning, got: ${JSON.stringify(probeWarnings)}`);
}
} catch (e) {
fail(`workday provider tests crashed: ${e.message}`);
}