- Create TPIN: current MPIN -> OTP -> encrypted TPIN via /tpin/create/*, from Services and from the transfer screen on TPIN_NOT_SET - IMPS initiate now sends encryptedTpin instead of encryptedMpin; handles INVALID_TPIN and TPIN_NOT_SET - Share one RSA-OAEP encryption and key-retry path for MPIN and TPIN - Forgot MPIN on sign-in and Reset MPIN in the Security Center - Fund transfer history list and detail, reachable from Home - Brand logo on sign-in and palette updates Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
import {
|
|
MPIN_LENGTH,
|
|
isAcceptableNewMpin,
|
|
isSequentialDigits,
|
|
isRepeatedDigits,
|
|
isValidAccountNumber,
|
|
isValidIfsc,
|
|
isValidMmid,
|
|
isValidMpin,
|
|
isValidOtp,
|
|
isValidPhone,
|
|
} from './validation';
|
|
|
|
describe('isValidMpin', () => {
|
|
it('is fixed at four digits', () => {
|
|
expect(MPIN_LENGTH).toBe(4);
|
|
expect(isValidMpin('4271')).toBe(true);
|
|
});
|
|
|
|
// The backend would accept six, but this app standardises on four, so a
|
|
// six-digit entry must not slip past the client either.
|
|
it('rejects any other length', () => {
|
|
expect(isValidMpin('427')).toBe(false);
|
|
expect(isValidMpin('42719')).toBe(false);
|
|
expect(isValidMpin('427193')).toBe(false);
|
|
expect(isValidMpin('')).toBe(false);
|
|
});
|
|
|
|
// The server refuses this too, but only after decryption — catching it here
|
|
// saves a round trip and a confusing VALIDATION_ERROR.
|
|
it('rejects an all-same-digit MPIN', () => {
|
|
expect(isValidMpin('1111')).toBe(false);
|
|
expect(isValidMpin('0000')).toBe(false);
|
|
});
|
|
|
|
it('rejects non-digits', () => {
|
|
expect(isValidMpin('42a1')).toBe(false);
|
|
expect(isValidMpin('4 27')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isRepeatedDigits', () => {
|
|
it('is false for an empty string rather than vacuously true', () => {
|
|
expect(isRepeatedDigits('')).toBe(false);
|
|
});
|
|
|
|
it('detects a repeated digit of any length', () => {
|
|
expect(isRepeatedDigits('7')).toBe(true);
|
|
expect(isRepeatedDigits('7777')).toBe(true);
|
|
expect(isRepeatedDigits('7778')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isValidPhone', () => {
|
|
it('accepts exactly ten digits', () => {
|
|
expect(isValidPhone('8871115905')).toBe(true);
|
|
expect(isValidPhone('887111590')).toBe(false);
|
|
expect(isValidPhone('88711159051')).toBe(false);
|
|
});
|
|
|
|
it('ignores formatting characters', () => {
|
|
expect(isValidPhone('887-111-5905')).toBe(true);
|
|
expect(isValidPhone('+91 88711 15905')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isValidOtp', () => {
|
|
it('requires exactly six digits', () => {
|
|
expect(isValidOtp('892220')).toBe(true);
|
|
expect(isValidOtp('89222')).toBe(false);
|
|
expect(isValidOtp('8922201')).toBe(false);
|
|
});
|
|
});
|
|
|
|
// The bounds below are the API's, not any single bank's formatting. An IFSC is
|
|
// 11 characters in practice, but the endpoint documents 6 to 15.
|
|
describe('payee routing fields', () => {
|
|
it('accepts account numbers from 5 to 20 digits', () => {
|
|
expect(isValidAccountNumber('50100')).toBe(true);
|
|
expect(isValidAccountNumber('50100999888777')).toBe(true);
|
|
expect(isValidAccountNumber('5010')).toBe(false);
|
|
expect(isValidAccountNumber('1'.repeat(21))).toBe(false);
|
|
expect(isValidAccountNumber('50100abc')).toBe(false);
|
|
});
|
|
|
|
it('accepts IFSC codes from 6 to 15 alphanumerics', () => {
|
|
expect(isValidIfsc('HDFC0001234')).toBe(true);
|
|
expect(isValidIfsc('SIM000')).toBe(true);
|
|
expect(isValidIfsc('SIM00')).toBe(false);
|
|
expect(isValidIfsc('HDFC-0001234')).toBe(false);
|
|
});
|
|
|
|
it('requires an MMID of exactly seven digits', () => {
|
|
expect(isValidMmid('1072730')).toBe(true);
|
|
expect(isValidMmid('107273')).toBe(false);
|
|
expect(isValidMmid('10727301')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isSequentialDigits', () => {
|
|
it('flags straight runs up and down', () => {
|
|
expect(isSequentialDigits('1234')).toBe(true);
|
|
expect(isSequentialDigits('6789')).toBe(true);
|
|
expect(isSequentialDigits('4321')).toBe(true);
|
|
expect(isSequentialDigits('987654')).toBe(true);
|
|
});
|
|
|
|
it('passes anything that is not one steady step', () => {
|
|
expect(isSequentialDigits('1243')).toBe(false);
|
|
expect(isSequentialDigits('1357')).toBe(false);
|
|
expect(isSequentialDigits('1111')).toBe(false);
|
|
expect(isSequentialDigits('9012')).toBe(false);
|
|
expect(isSequentialDigits('')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isAcceptableNewMpin', () => {
|
|
it('refuses runs and repeats when choosing an MPIN', () => {
|
|
expect(isAcceptableNewMpin('1234')).toBe(false);
|
|
expect(isAcceptableNewMpin('4321')).toBe(false);
|
|
expect(isAcceptableNewMpin('1111')).toBe(false);
|
|
expect(isAcceptableNewMpin('4271')).toBe(true);
|
|
});
|
|
|
|
it('leaves sign-in able to enter an old sequential MPIN', () => {
|
|
expect(isValidMpin('1234')).toBe(true);
|
|
});
|
|
});
|