137 lines
5.0 KiB
TypeScript
137 lines
5.0 KiB
TypeScript
/**
|
|
* Copyright since 2025 Mifos Initiative
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import {
|
|
deriveKycDocumentRenewal,
|
|
kycDocumentNeedsAttention,
|
|
KYC_DOCUMENT_EXPIRY_WARNING_DAYS,
|
|
KYC_DOCUMENT_RENEWAL_YEARS
|
|
} from './kyc-document-renewal.util';
|
|
|
|
const TODAY = new Date(2026, 7, 7); // 07 Aug 2026
|
|
|
|
const at = (y: number, m: number, d: number) => new Date(y, m - 1, d);
|
|
|
|
const check = (issueDate: Date | null, expiryDate: Date | null = null, today: Date = TODAY) =>
|
|
deriveKycDocumentRenewal({ issueDate, expiryDate, today });
|
|
|
|
describe('deriveKycDocumentRenewal', () => {
|
|
describe('the five-year rule', () => {
|
|
it('says nothing about a document with neither date captured', () => {
|
|
const result = check(null, null);
|
|
expect(result.state).toBe('UNKNOWN');
|
|
expect(result.dueOn).toBeNull();
|
|
expect(result.daysRemaining).toBeNull();
|
|
});
|
|
|
|
it('leaves a recently issued document alone', () => {
|
|
const result = check(at(2025, 1, 15));
|
|
expect(result.state).toBe('OK');
|
|
expect(result.reason).toBe('AGE');
|
|
});
|
|
|
|
it('falls due exactly five years after issue', () => {
|
|
const result = check(at(2021, 8, 7));
|
|
expect(result.dueOn).toEqual(at(2026, 8, 7));
|
|
expect(result.daysRemaining).toBe(0);
|
|
// Due today is not yet past — the branch still has the day to act.
|
|
expect(result.state).toBe('OK');
|
|
});
|
|
|
|
it('becomes DUE the day after the fifth anniversary', () => {
|
|
const result = check(at(2021, 8, 6));
|
|
expect(result.state).toBe('DUE');
|
|
expect(result.reason).toBe('AGE');
|
|
});
|
|
|
|
it('is DUE, not EXPIRED, once past five years — the document is still valid, just stale', () => {
|
|
const result = check(at(2020, 3, 10));
|
|
expect(result.state).toBe('DUE');
|
|
expect(result.reason).toBe('AGE');
|
|
expect(result.daysRemaining).toBeLessThan(0);
|
|
});
|
|
|
|
it('does not warn ahead of the five-year mark — a document one day short is entirely fine', () => {
|
|
// Due tomorrow on age alone, and still OK: only expiry gets an advance warning.
|
|
const result = check(at(2021, 8, 8));
|
|
expect(result.daysRemaining).toBe(1);
|
|
expect(result.state).toBe('OK');
|
|
});
|
|
});
|
|
|
|
describe('an explicit expiry date', () => {
|
|
it('warns seven days before expiry', () => {
|
|
const result = check(at(2025, 6, 1), at(2026, 8, 14));
|
|
expect(result.reason).toBe('EXPIRY');
|
|
expect(result.daysRemaining).toBe(7);
|
|
expect(result.state).toBe('EXPIRING_SOON');
|
|
});
|
|
|
|
it('is still OK eight days out', () => {
|
|
expect(check(at(2025, 6, 1), at(2026, 8, 15)).state).toBe('OK');
|
|
});
|
|
|
|
it('warns on the day of expiry itself', () => {
|
|
const result = check(at(2025, 6, 1), at(2026, 8, 7));
|
|
expect(result.daysRemaining).toBe(0);
|
|
expect(result.state).toBe('EXPIRING_SOON');
|
|
});
|
|
|
|
it('marks a passed expiry as EXPIRED — no longer a valid OVD', () => {
|
|
const result = check(at(2024, 1, 1), at(2026, 1, 1));
|
|
expect(result.state).toBe('EXPIRED');
|
|
expect(result.reason).toBe('EXPIRY');
|
|
});
|
|
|
|
it('governs when it falls before the five-year mark', () => {
|
|
// Issued last year, so the age clock is years away — expiry is what matters.
|
|
const result = check(at(2025, 6, 1), at(2026, 10, 1));
|
|
expect(result.reason).toBe('EXPIRY');
|
|
expect(result.dueOn).toEqual(at(2026, 10, 1));
|
|
});
|
|
|
|
it('yields to the five-year rule when it falls later', () => {
|
|
// A passport issued in 2019 expiring in 2029 is stale long before it expires.
|
|
const result = check(at(2019, 5, 20), at(2029, 5, 20));
|
|
expect(result.reason).toBe('AGE');
|
|
expect(result.dueOn).toEqual(at(2024, 5, 20));
|
|
expect(result.state).toBe('DUE');
|
|
});
|
|
|
|
it('is judged on expiry alone when no issue date was captured', () => {
|
|
const result = check(null, at(2027, 1, 1));
|
|
expect(result.reason).toBe('EXPIRY');
|
|
expect(result.state).toBe('OK');
|
|
});
|
|
});
|
|
|
|
describe('leap-year issue dates', () => {
|
|
it('pins a 29 February issue to 28 February, not 1 March', () => {
|
|
// 2020-02-29 + 5 years has no real anniversary; rolling into March would push the
|
|
// document into the wrong month.
|
|
const result = check(at(2020, 2, 29));
|
|
expect(result.dueOn).toEqual(at(2025, 2, 28));
|
|
});
|
|
});
|
|
|
|
describe('kycDocumentNeedsAttention', () => {
|
|
it('flags only the states staff must act on', () => {
|
|
expect(kycDocumentNeedsAttention('UNKNOWN')).toBe(false);
|
|
expect(kycDocumentNeedsAttention('OK')).toBe(false);
|
|
expect(kycDocumentNeedsAttention('EXPIRING_SOON')).toBe(true);
|
|
expect(kycDocumentNeedsAttention('EXPIRED')).toBe(true);
|
|
expect(kycDocumentNeedsAttention('DUE')).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('uses a five-year age window and a seven-day expiry warning', () => {
|
|
expect(KYC_DOCUMENT_RENEWAL_YEARS).toBe(5);
|
|
expect(KYC_DOCUMENT_EXPIRY_WARNING_DAYS).toBe(7);
|
|
});
|
|
});
|