forked from Shared/esg
Implement normalized error handling with Persian translations across all exception types, replace legacy NestJS exceptions with AppException, and add unit, integration, and smoke tests. - Add error catalog with gateway-owned codes and Persian messages - Introduce AppException wrapping normalized error envelopes - Add translateError helper for automatic messageFa population - Remove claims module and update provider error normalization - Add unit tests for error contracts and helper functions - Add integration tests for admin and inquiry endpoints - Add smoke tests for real provider connectivity - Add test support utilities (auth mocks, assertions, app factory)
146 lines
4.8 KiB
TypeScript
146 lines
4.8 KiB
TypeScript
import { maskPayload } from '../../src/common/helpers/mask-payload.helper';
|
||
import { buildValidationErrorResponse } from '../../src/common/helpers/validation-error.helper';
|
||
import {
|
||
getShahkarProviderError,
|
||
isShahkarSuccess,
|
||
normalizeShahkarFields,
|
||
parseShahkarInqueryResult,
|
||
} from '../../src/common/helpers/shahkar-response.helper';
|
||
import { getSayahProviderError } from '../../src/common/helpers/sayah-response.helper';
|
||
import { getCentInsurProviderError } from '../../src/common/helpers/centinsur-response.helper';
|
||
import { InquiryType } from '../../src/common/enums/inquiry-type.enum';
|
||
import { ClientType } from '../../src/common/enums/client-type.enum';
|
||
import { Role } from '../../src/common/enums/role.enum';
|
||
import { UserMapper } from '../../src/users/mappers/user.mapper';
|
||
import {
|
||
createClientConfig,
|
||
resolveClientConfig,
|
||
} from '../support/client-config.factory';
|
||
|
||
describe('unit helpers and mappers', () => {
|
||
it('masks sensitive payload fields recursively without mutating safe fields', () => {
|
||
expect(
|
||
maskPayload({
|
||
nationalCode: '0012345678',
|
||
nested: { password: 'secret', safe: 'visible' },
|
||
amount: 100,
|
||
}),
|
||
).toEqual({
|
||
nationalCode: '***MASKED***',
|
||
nested: { password: '***MASKED***', safe: 'visible' },
|
||
amount: 100,
|
||
});
|
||
});
|
||
|
||
it('maps Shahkar provider no-match responses to a stable internal code', () => {
|
||
const fields = {
|
||
response: '600',
|
||
result: 'NotIdentifiedException',
|
||
comment: '',
|
||
requestId: 'provider-tracking',
|
||
};
|
||
|
||
expect(isShahkarSuccess(fields)).toBe(false);
|
||
expect(getShahkarProviderError(fields)).toMatchObject({
|
||
code: 'INQUIRY_NO_MATCH',
|
||
providerTrackingCode: 'provider-tracking',
|
||
});
|
||
});
|
||
|
||
it('parses SOAP Shahkar response fields', () => {
|
||
const fields = normalizeShahkarFields(parseShahkarInqueryResult(`
|
||
<ShahkarInqueryResult>
|
||
<Response>200</Response>
|
||
<Result>OK: matched</Result>
|
||
</ShahkarInqueryResult>
|
||
`) as Record<string, unknown>);
|
||
|
||
expect(fields).toMatchObject({ response: '200', result: 'OK: matched' });
|
||
expect(isShahkarSuccess(fields)).toBe(true);
|
||
});
|
||
|
||
it('maps Sayah and CentInsur business errors', () => {
|
||
expect(
|
||
getSayahProviderError({
|
||
ReturnValue: false,
|
||
HasError: false,
|
||
Errors: [{ Code: 'SHEBA', Message: 'Mismatch' }],
|
||
}),
|
||
).toMatchObject({ code: 'SHEBA_MISMATCH' });
|
||
|
||
expect(
|
||
getCentInsurProviderError(
|
||
{ IsSucceed: true, Result: { Result: false, ErrorMessage: null } },
|
||
InquiryType.REAL_ESTATE,
|
||
),
|
||
).toMatchObject({
|
||
code: 'INQUIRY_NO_MATCH',
|
||
message: 'No property ownership found for the provided national code and postal code',
|
||
});
|
||
});
|
||
|
||
it('maps user documents without leaking secrets', () => {
|
||
const dto = UserMapper.toResponseDto({
|
||
_id: { toString: () => 'user-1' },
|
||
fullName: 'Admin User',
|
||
username: 'admin',
|
||
email: 'admin@example.com',
|
||
role: Role.SUPER_ADMIN,
|
||
clientType: ClientType.INTERNAL,
|
||
appName: 'gateway',
|
||
clientName: 'Gateway',
|
||
isActive: true,
|
||
isBlocked: false,
|
||
allowedInquiries: [],
|
||
requestLimitPerMinute: 10,
|
||
requestLimitPerDay: 100,
|
||
totalRequests: 3,
|
||
createdAt: new Date('2026-01-01T00:00:00Z'),
|
||
updatedAt: new Date('2026-01-02T00:00:00Z'),
|
||
password: 'should-not-leak',
|
||
} as never);
|
||
|
||
expect(dto).toMatchObject({ id: 'user-1', username: 'admin' });
|
||
expect(dto).not.toHaveProperty('password');
|
||
});
|
||
|
||
it('resolves client-specific credentials and network configuration', () => {
|
||
const vpnClient = createClientConfig({
|
||
clientId: 'vpn-client',
|
||
requiresVpn: true,
|
||
providerBaseUrl: 'https://vpn-only.provider.test',
|
||
credentials: { username: 'vpn-user', password: 'vpn-password' },
|
||
});
|
||
|
||
expect(resolveClientConfig([createClientConfig(), vpnClient], 'vpn-client')).toMatchObject({
|
||
providerBaseUrl: 'https://vpn-only.provider.test',
|
||
credentials: { username: 'vpn-user', password: 'vpn-password' },
|
||
requiresVpn: true,
|
||
});
|
||
});
|
||
|
||
it('returns one prioritized validation error instead of duplicate constraints', () => {
|
||
const { normalizedError } = buildValidationErrorResponse([
|
||
{
|
||
property: 'nationalCode',
|
||
constraints: {
|
||
isLength: 'nationalCode must be longer than or equal to 10 characters',
|
||
matches: 'nationalCode must be exactly 10 digits',
|
||
},
|
||
},
|
||
] as never);
|
||
|
||
expect(normalizedError).toMatchObject({
|
||
code: 'VALIDATION_ERROR',
|
||
message: 'nationalCode must be exactly 10 digits',
|
||
messageFa: 'کد ملی باید دقیقا ۱۰ رقم باشد',
|
||
details: [
|
||
{
|
||
field: 'nationalCode',
|
||
constraints: ['nationalCode must be exactly 10 digits'],
|
||
},
|
||
],
|
||
});
|
||
});
|
||
});
|