mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
fix(otp): fall back to email for foreign phone numbers
The SMS gateway is domestic-only, but OTP sends fanned out to any phone on the account - a foreign number meant a code queued into the void while the response claimed success. isDomesticPhone (+2519/+2517 E.164) now gates SMS: dual-channel sends with a foreign phone go email-only (the phone stays on the row so verify still matches it), and a phone-only foreign target still tries SMS as the only route. The staff-triggered reset exposes phoneIsDomestic so the backoffice disables the SMS channel with an explanation, and the API refuses the channel directly for foreign numbers. EDRFREIGHT-186
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { OtpService, normalizeOtpTarget } from './otp.service';
|
||||
import { OtpService, isDomesticPhone, normalizeOtpTarget } from './otp.service';
|
||||
|
||||
describe('normalizeOtpTarget', () => {
|
||||
it('canonicalises Ethiopian forms to one E.164 key', () => {
|
||||
@@ -28,6 +28,18 @@ describe('normalizeOtpTarget', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDomesticPhone', () => {
|
||||
it.each(['+251986680099', '0986680099', '0712345678', '251986680099'])(
|
||||
'accepts Ethiopian mobile form %s',
|
||||
(phone) => expect(isDomesticPhone(phone)).toBe(true),
|
||||
);
|
||||
|
||||
it.each(['+14155550123', '+447911123456', '+2519866', '12345'])(
|
||||
'rejects non-domestic or malformed %s',
|
||||
(phone) => expect(isDomesticPhone(phone)).toBe(false),
|
||||
);
|
||||
});
|
||||
|
||||
interface FakeRow {
|
||||
id: string;
|
||||
phone?: string;
|
||||
@@ -184,6 +196,26 @@ describe('OtpService — dual-channel send', () => {
|
||||
expect(email.sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips SMS for a foreign number when email is available', async () => {
|
||||
const { service, sms, email, rows } = makeService();
|
||||
await service.sendOtp({ phone: '+14155550123', email: 'user@example.com' });
|
||||
|
||||
// The gateway is domestic-only — email is the delivery route, but the
|
||||
// foreign phone stays on the row so verify still matches either channel.
|
||||
expect(sms.sendSms).not.toHaveBeenCalled();
|
||||
expect(email.sendEmail).toHaveBeenCalledTimes(1);
|
||||
await expect(
|
||||
service.verifyOtpForAction({ phone: '+14155550123' }, rows()[0]!.otp),
|
||||
).resolves.toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('still attempts SMS for a foreign number when it is the only channel', async () => {
|
||||
const { service, sms } = makeService();
|
||||
await service.sendOtp({ phone: '+14155550123' });
|
||||
|
||||
expect(sms.sendSms).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('still succeeds when one transport throws', async () => {
|
||||
const { service, rows } = makeService({
|
||||
sms: async () => {
|
||||
|
||||
Reference in New Issue
Block a user