HR-ATS-Portal/frontend/candidates-table.test.mjs

140 lines
4.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* Candidates table mapper — score, stage, recruiter, rejected.
*
* node candidates-table.test.mjs
*/
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import esbuild from 'esbuild'
const outDir = mkdtempSync(join(tmpdir(), 'tf-cand-'))
const outFile = join(outDir, 'candidates.mjs')
await esbuild.build({
entryPoints: ['src/api/candidates.js'],
outfile: outFile,
bundle: true,
format: 'esm',
platform: 'node',
target: 'node20',
logLevel: 'error',
define: { 'import.meta.env': JSON.stringify({ VITE_API_BASE: '' }) },
})
const api = await import(pathToFileURL(outFile).href)
const { toApplicationListView } = api
let failed = 0
function ok(name, cond, extra) {
if (cond) {
console.log(`ok ${name}`)
if (extra) console.log(` ${extra}`)
} else {
failed += 1
console.log(`FAIL ${name}`)
if (extra) console.log(` ${extra}`)
}
}
const scored = toApplicationListView({
inbox_id: 41,
user_id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
name: 'Ada Lovelace',
email: 'ada@example.com',
job_title: 'Backend Engineer',
recruiter: 'Sam Recruiter',
application_status: 'PENDING',
ai_score: 88,
recommendation: 'Strong Match',
created_at: '2026-09-03T10:00:00Z',
source: 'Email',
is_active: true,
})
ok('row is application-keyed', scored.id === 'inbox:41', `id=${scored.id}`)
ok('keeps userId for profile navigation', scored.userId === 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
ok('job title comes through', scored.jobTitle === 'Backend Engineer')
ok('ATS score is numeric', scored.aiScore === 88)
ok('band is Strong Match', scored.recommendation === 'Strong Match')
ok('PENDING maps to Shortlist', scored.stage === 'Shortlist', `stage=${scored.stage}`)
ok('recruiter name is kept', scored.recruiter === 'Sam Recruiter')
const rejected = toApplicationListView({
inbox_id: 42,
user_id: 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
name: 'Rejected Candidate',
email: 'r@example.com',
application_status: 'REJECTED',
ai_score: 40,
created_at: '2026-09-01T10:00:00Z',
})
ok('REJECTED maps to Rejected stage', rejected.stage === 'Rejected', `stage=${rejected.stage}`)
ok('CLOSED shows as CLOSED', toApplicationListView({
inbox_id: 43, application_status: 'CLOSED', name: 'Closed',
}).stage === 'CLOSED')
const formRow = toApplicationListView({
form_data_id: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
name: 'Form Applicant',
source: 'Form',
job_title: 'Brand Manager',
})
ok('form row key', formRow.id === 'form:ffffffff-ffff-ffff-ffff-ffffffffffff')
ok('form with no pipeline status has no stage', formRow.stage == null)
const hired = toApplicationListView({
inbox_id: 44,
application_status: 'HIRED',
name: 'Hired Person',
ai_score: 91,
})
ok('HIRED maps to Hired', hired.stage === 'Hired')
const unscored = toApplicationListView({
manual_upload_candidate_id: 'cccccccc-cccc-cccc-cccc-cccccccccccc',
user_id: 'dddddddd-dddd-dddd-dddd-dddddddddddd',
name: 'No Score Yet',
email: 'ns@example.com',
assigned_job_post: { title: 'Brand Manager' },
recruiter: null,
application_status: 'SCREENING',
})
ok('manual row key', unscored.id === 'manual:cccccccc-cccc-cccc-cccc-cccccccccccc')
ok('job falls back to assigned post title', unscored.jobTitle === 'Brand Manager')
ok('missing score stays null, not 0', unscored.aiScore === null)
ok('unscored has no invented band', unscored.recommendation === null)
ok('SCREENING maps to Screening', unscored.stage === 'Screening')
ok('missing recruiter is null so the table can say Unassigned', unscored.recruiter === null)
const weak = toApplicationListView({
inbox_id: 45,
name: 'Weak',
ai_score: 50,
})
ok('score without band derives Weak Match', weak.recommendation === 'Weak Match')
const potential = toApplicationListView({
inbox_id: 46,
name: 'Mid',
ai_score: 70,
})
ok('6581 derives Potential Match', potential.recommendation === 'Potential Match')
const enumStatus = toApplicationListView({
inbox_id: 47,
name: 'Enum',
application_status: { value: 'OFFER' },
})
ok('enum-shaped status still maps', enumStatus.stage === 'Offer')
rmSync(outDir, { recursive: true, force: true })
if (failed) {
console.log(`\n${failed} check(s) failed`)
process.exit(1)
}
console.log('\nAll candidates-table mapper checks passed')