- Extract EpfoService's pure stub + validation logic into a dependency-free module (EpfoService/src/stub.ts); index.ts now uses it in its stub branches so the tested logic IS the production stub behaviour. - epfo.test.ts (App integration): the deterministic stub contract (OTP 000000 → matched, UAN/OTP validation, session expiry) and the Next proxy routes' verify_bank_epf gate — 401 unauthenticated, 403 for the MPO, Accounts passes through to a mocked upstream, body validated before the upstream call. No EPFO_LIVE, no running service. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42 lines
2 KiB
TypeScript
42 lines
2 KiB
TypeScript
/**
|
|
* Pure, dependency-free EPFO stub + validation logic (no express/playwright), so
|
|
* the deterministic contract the PPMS app relies on can be unit-tested without
|
|
* launching the service. `index.ts` uses these in its stub branches, so the
|
|
* tested logic IS the production stub behaviour.
|
|
*
|
|
* Deterministic stub contract (EPFO_LIVE unset):
|
|
* /otp validates the UAN and opens a session.
|
|
* /verify validates session + UAN + OTP; matched iff OTP === STUB_MATCH_OTP.
|
|
*/
|
|
|
|
export const STUB_MATCH_OTP = "000000";
|
|
|
|
export const isUan = (s: unknown): s is string => typeof s === "string" && /^\d{12}$/.test(s);
|
|
export const isOtp = (s: unknown): s is string => typeof s === "string" && /^\d{4,8}$/.test(s);
|
|
|
|
export const mobileHint = (m?: string) => (m && m.length >= 4 ? `••••••${m.slice(-4)}` : "••••••••");
|
|
|
|
export interface StubResult {
|
|
ok: boolean;
|
|
status: number;
|
|
body: Record<string, unknown>;
|
|
}
|
|
|
|
/** Stub of POST /otp — validate the UAN and (caller-supplied) open a session. */
|
|
export function stubOtp(uan: unknown, sessionId: string): StubResult {
|
|
if (!isUan(uan)) return { ok: false, status: 400, body: { error: "A 12-digit UAN is required" } };
|
|
return { ok: true, status: 200, body: { sessionId, mobileHint: mobileHint(), stub: true } };
|
|
}
|
|
|
|
/** Stub of POST /verify — validate the session/UAN/OTP and return the match. */
|
|
export function stubVerify(session: { uan: string } | undefined, uan: unknown, otp: unknown): StubResult {
|
|
if (!session) return { ok: false, status: 410, body: { error: "Session expired — request a new OTP" } };
|
|
if (!isUan(uan) || session.uan !== uan) return { ok: false, status: 400, body: { error: "UAN mismatch" } };
|
|
if (!isOtp(otp)) return { ok: false, status: 400, body: { error: "A valid OTP is required" } };
|
|
const matched = otp === STUB_MATCH_OTP;
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: { matched, name: matched ? "EPFO Member (stub)" : null, status: matched ? "ACTIVE" : null, stub: true },
|
|
};
|
|
}
|