parity(api,apps): delivery-mode gaps — master OTP, non-blocking SMS, faToEn, v2 cart cap

D20: OTP_MASTER_CODE login bypass read only from the environment, disabled when
unset, audited on every use, with a boot banner while armed. SMS no longer
aborts the boot in production — it warns and declines silently, so no message
can stop an operation.

D19: port Utility::faTOen onto admin-entered mobiles (before the uniqueness
lookup, which could previously be bypassed with Persian digits), and the
create-only welcome SMS, byte-identical to the reference and fire-and-forget.

user-app-v2 was missing the cart stock ceiling entirely: capQty/maxQty, the
un-orderable guard and the qty clamp are ported, leaving v2's own design intact.
The two cart stores are now byte-identical. isVariantOrderable and the
orderable-first default selection ported too.

jest 45/45; typecheck clean on api, admin-panel, user-app, user-app-v2.

[ref: henza_server/app/Http/Controllers/Adm/AdminController.php:333,348,364,378-387]
[ref: henza_server/app/Models/Utility.php:26]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-09-22 18:35:33 +03:30
co-authored by Claude Opus 5
parent 2194b3d4d9
commit 460cf276b6
128 changed files with 756 additions and 181 deletions
+27 -15
View File
@@ -2,9 +2,11 @@ import { Logger } from '@nestjs/common';
import { assertSmsConfigured, smsConfig, smsConfigured } from './sms.service';
/**
* Covers the boot-time contract the owner specified:
* Covers the boot-time contract as REVISED by D20:
* * while no key is set, development behaviour is unchanged;
* * in production a missing key is a clear, explicit boot failure.
* * a missing key NEVER stops the boot, in any environment — it warns, and
* sending silently declines. (D14's "fail to boot in production" rule was
* removed by the owner for the delivery.)
*/
describe('SMS configuration', () => {
const saved = { ...process.env };
@@ -71,32 +73,39 @@ describe('SMS configuration', () => {
});
});
describe('assertSmsConfigured', () => {
it('only warns outside production when keys are missing', () => {
/**
* D20 REVERSED the original boot contract. SMS must never stop the server
* from starting, in any environment — an earlier revision of these tests
* asserted the opposite (a throw in production) and is intentionally gone.
*/
describe('assertSmsConfigured (D20 — never throws)', () => {
it('warns outside production when keys are missing', () => {
process.env.NODE_ENV = 'development';
expect(() => assertSmsConfigured(logger)).not.toThrow();
expect(logger.warn).toHaveBeenCalled();
});
it('THROWS in production when keys are missing, naming them', () => {
it('warns in production when keys are missing, naming them, and still boots', () => {
process.env.NODE_ENV = 'production';
expect(() => assertSmsConfigured(logger)).toThrow(/SMS_TREZ_USERNAME/);
expect(() => assertSmsConfigured(logger)).toThrow(/SMS_TREZ_PASSWORD/);
expect(() => assertSmsConfigured(logger)).toThrow(/SMS_TREZ_SENDER_LINE/);
expect(() => assertSmsConfigured(logger)).not.toThrow();
const warned = (logger.warn as jest.Mock).mock.calls.flat().join(' ');
expect(warned).toMatch(/SMS_TREZ_USERNAME/);
expect(warned).toMatch(/SMS_TREZ_PASSWORD/);
expect(warned).toMatch(/SMS_TREZ_SENDER_LINE/);
});
it('passes in production once everything is set', () => {
it('passes silently in production once everything is set', () => {
process.env.NODE_ENV = 'production';
configureFully();
expect(() => assertSmsConfigured(logger)).not.toThrow();
expect(logger.warn).not.toHaveBeenCalled();
});
it('THROWS in production when SMS is explicitly disabled', () => {
// Disabling SMS in production means nobody can log in; that must be a
// deliberate, visible failure rather than a silent one.
it('allows SMS to be explicitly disabled in production', () => {
process.env.NODE_ENV = 'production';
process.env.SMS_PROVIDER = 'none';
expect(() => assertSmsConfigured(logger)).toThrow(/Refusing to start in production/);
expect(() => assertSmsConfigured(logger)).not.toThrow();
expect(logger.warn).toHaveBeenCalled();
});
it('allows SMS to be disabled outside production', () => {
@@ -105,9 +114,12 @@ describe('SMS configuration', () => {
expect(() => assertSmsConfigured(logger)).not.toThrow();
});
it('rejects an unknown provider name outright', () => {
it('warns about an unknown provider name instead of refusing to boot', () => {
process.env.SMS_PROVIDER = 'kavenegar';
expect(() => assertSmsConfigured(logger)).toThrow(/Unknown SMS_PROVIDER/);
expect(() => assertSmsConfigured(logger)).not.toThrow();
expect((logger.warn as jest.Mock).mock.calls.flat().join(' ')).toMatch(
/Unknown SMS_PROVIDER/,
);
});
});
});
+18 -22
View File
@@ -68,30 +68,30 @@ export function smsConfigured(): boolean {
}
/**
* Called from main.ts before the app listens. In production a missing key is
* fatal: without it no customer can log in, and a silent no-op gives no clue
* why. Outside production it is only a warning, so local development works
* with no credentials at all.
* Called from main.ts before the app listens. Reports the SMS configuration
* and NEVER throws.
*
* D20 reversed the earlier rule (D14) that a missing credential should abort
* the boot in production. The server now always starts: a missing or unknown
* provider is a warning, `send()` declines silently and logs, and no message —
* including the welcome SMS — can stop an operation. The trade-off is
* deliberate and accepted: with SMS unconfigured, customers cannot receive a
* login code, which is why `OTP_MASTER_CODE` exists for the delivery.
*/
export function assertSmsConfigured(logger: Logger = new Logger('SmsConfig')): void {
const c = smsConfig();
if (c.provider === 'none') {
const message = 'SMS_PROVIDER=none — no SMS will be sent.';
if (process.env.NODE_ENV === 'production') {
throw new Error(
`${message} Refusing to start in production: customers could not log in. ` +
'Set SMS_PROVIDER=trez and the SMS_TREZ_* variables.',
);
}
logger.warn(message);
logger.warn('SMS_PROVIDER=none — no SMS will be sent.');
return;
}
if (c.provider !== 'trez') {
throw new Error(
`Unknown SMS_PROVIDER "${c.provider}". Supported values: "trez", "none".`,
logger.warn(
`Unknown SMS_PROVIDER "${c.provider}" (supported: "trez", "none"). ` +
'No SMS will be sent.',
);
return;
}
const missing = (
@@ -106,14 +106,10 @@ export function assertSmsConfigured(logger: Logger = new Logger('SmsConfig')): v
if (missing.length === 0) return;
const message = `SMS provider "trez" is selected but these are not set: ${missing.join(', ')}.`;
if (process.env.NODE_ENV === 'production') {
throw new Error(
`${message} Refusing to start in production: no customer could receive a login code. ` +
'Set them, or set SMS_PROVIDER=none to acknowledge that SMS is disabled.',
);
}
logger.warn(`${message} Running in development mode — messages are logged, not sent.`);
logger.warn(
`SMS provider "trez" is selected but these are not set: ${missing.join(', ')}. ` +
'Messages are logged, not sent. Customers cannot receive a login code.',
);
}
@Injectable()