kyc exception at client creation time and some tds work
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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 {
|
||||
deriveKycExceptionFields,
|
||||
KycExceptionInput,
|
||||
KYC_EXCEPTION_AADHAAR,
|
||||
KYC_EXCEPTION_PAN
|
||||
} from './kyc-exception.util';
|
||||
|
||||
const TODAY = '05 August 2026';
|
||||
|
||||
/** Individual client, nothing previously recorded — the client-creation case. */
|
||||
const newIndividual = (panVerified: boolean, aadhaarVerified: boolean): KycExceptionInput => ({
|
||||
panVerified,
|
||||
aadhaarVerified,
|
||||
aadhaarApplicable: true,
|
||||
hadException: false,
|
||||
previousReason: null,
|
||||
todayFormatted: TODAY
|
||||
});
|
||||
|
||||
/** Individual client that already carries an open exception. */
|
||||
const existingException = (
|
||||
panVerified: boolean,
|
||||
aadhaarVerified: boolean,
|
||||
previousReason: string
|
||||
): KycExceptionInput => ({
|
||||
panVerified,
|
||||
aadhaarVerified,
|
||||
aadhaarApplicable: true,
|
||||
hadException: true,
|
||||
previousReason,
|
||||
todayFormatted: TODAY
|
||||
});
|
||||
|
||||
describe('deriveKycExceptionFields', () => {
|
||||
describe('at client creation', () => {
|
||||
it('marks both PAN and Aadhaar when neither is entered or verified', () => {
|
||||
expect(deriveKycExceptionFields(newIndividual(false, false))).toEqual({
|
||||
isException: true,
|
||||
exceptionReason: `${KYC_EXCEPTION_PAN}, ${KYC_EXCEPTION_AADHAAR}`,
|
||||
exceptionResolved: false,
|
||||
exceptionResolvedOn: null
|
||||
});
|
||||
});
|
||||
|
||||
it('marks only PAN when Aadhaar verified but PAN did not', () => {
|
||||
expect(deriveKycExceptionFields(newIndividual(false, true))).toEqual({
|
||||
isException: true,
|
||||
exceptionReason: KYC_EXCEPTION_PAN,
|
||||
exceptionResolved: false,
|
||||
exceptionResolvedOn: null
|
||||
});
|
||||
});
|
||||
|
||||
it('marks only Aadhaar when PAN verified but Aadhaar did not', () => {
|
||||
expect(deriveKycExceptionFields(newIndividual(true, false))).toEqual({
|
||||
isException: true,
|
||||
exceptionReason: KYC_EXCEPTION_AADHAAR,
|
||||
exceptionResolved: false,
|
||||
exceptionResolvedOn: null
|
||||
});
|
||||
});
|
||||
|
||||
it('raises nothing when both verify, and does not mark the client resolved', () => {
|
||||
// A clean client never had an exception, so it must not appear in the Resolved tab either.
|
||||
expect(deriveKycExceptionFields(newIndividual(true, true))).toEqual({
|
||||
isException: false,
|
||||
exceptionReason: null,
|
||||
exceptionResolved: false,
|
||||
exceptionResolvedOn: null
|
||||
});
|
||||
});
|
||||
|
||||
it('does not raise an Aadhaar exception for a non-individual client', () => {
|
||||
// Companies submit a dummy masked Aadhaar; checking it would mean a permanent exception.
|
||||
expect(
|
||||
deriveKycExceptionFields({
|
||||
panVerified: true,
|
||||
aadhaarVerified: false,
|
||||
aadhaarApplicable: false,
|
||||
hadException: false,
|
||||
previousReason: null,
|
||||
todayFormatted: TODAY
|
||||
}).isException
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolving', () => {
|
||||
it('closes the exception once both checks pass, keeping the reason for the audit trail', () => {
|
||||
const both = `${KYC_EXCEPTION_PAN}, ${KYC_EXCEPTION_AADHAAR}`;
|
||||
expect(deriveKycExceptionFields(existingException(true, true, both))).toEqual({
|
||||
isException: false,
|
||||
exceptionReason: both,
|
||||
exceptionResolved: true,
|
||||
exceptionResolvedOn: TODAY
|
||||
});
|
||||
});
|
||||
|
||||
it('narrows to the outstanding item when only one of two verifies', () => {
|
||||
const both = `${KYC_EXCEPTION_PAN}, ${KYC_EXCEPTION_AADHAAR}`;
|
||||
// PAN verified, Aadhaar still outstanding — stays open, listing Aadhaar alone.
|
||||
expect(deriveKycExceptionFields(existingException(true, false, both))).toEqual({
|
||||
isException: true,
|
||||
exceptionReason: KYC_EXCEPTION_AADHAAR,
|
||||
exceptionResolved: false,
|
||||
exceptionResolvedOn: null
|
||||
});
|
||||
});
|
||||
|
||||
it('narrows the other way round too', () => {
|
||||
const both = `${KYC_EXCEPTION_PAN}, ${KYC_EXCEPTION_AADHAAR}`;
|
||||
expect(deriveKycExceptionFields(existingException(false, true, both)).exceptionReason).toBe(KYC_EXCEPTION_PAN);
|
||||
});
|
||||
});
|
||||
|
||||
describe('re-opening', () => {
|
||||
it('clears a prior resolution so a new exception returns to Pending', () => {
|
||||
// Regression: without exceptionResolved=false the record still matched the Resolved query
|
||||
// and a brand-new exception appeared as already closed.
|
||||
const result = deriveKycExceptionFields({
|
||||
panVerified: false,
|
||||
aadhaarVerified: true,
|
||||
aadhaarApplicable: true,
|
||||
hadException: false,
|
||||
previousReason: 'PAN Card Verification Failed',
|
||||
todayFormatted: TODAY
|
||||
});
|
||||
expect(result.exceptionResolved).toBe(false);
|
||||
expect(result.exceptionResolvedOn).toBeNull();
|
||||
expect(result.isException).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('officer-entered reasons', () => {
|
||||
it('keeps a manual reason alongside the automatic ones', () => {
|
||||
const result = deriveKycExceptionFields({
|
||||
...newIndividual(true, true),
|
||||
manualReasons: ['Address proof pending']
|
||||
});
|
||||
expect(result).toEqual({
|
||||
isException: true,
|
||||
exceptionReason: 'Address proof pending',
|
||||
exceptionResolved: false,
|
||||
exceptionResolvedOn: null
|
||||
});
|
||||
});
|
||||
|
||||
it('drops a stale automatic reason left in the form once the check passes', () => {
|
||||
// The form still holds 'PAN Card Verification Failed' from a previous load; PAN has since
|
||||
// verified, so it must not survive into the payload.
|
||||
const result = deriveKycExceptionFields({
|
||||
...newIndividual(true, true),
|
||||
manualReasons: [KYC_EXCEPTION_PAN]
|
||||
});
|
||||
expect(result.isException).toBe(false);
|
||||
expect(result.exceptionReason).toBeNull();
|
||||
});
|
||||
|
||||
it('does not duplicate a manual reason that matches an outstanding automatic one', () => {
|
||||
const result = deriveKycExceptionFields({
|
||||
...newIndividual(false, true),
|
||||
manualReasons: [KYC_EXCEPTION_PAN]
|
||||
});
|
||||
expect(result.exceptionReason).toBe(KYC_EXCEPTION_PAN);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user