@fiado/type-kit 3.627.0 → 3.629.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_test_/unit/collection/collectionCallbackRequests.test.ts +96 -0
- package/_test_/unit/gsfConnector/gsfMessages.test.ts +20 -0
- package/_test_/unit/loanCollectorAssignment/collectorAssignmentRequests.test.ts +65 -0
- package/_test_/unit/loanConfig/parameterWriteRequests.test.ts +62 -0
- package/_test_/unit/loanCredit/dtos/loanCreditInputLimits.test.ts +168 -0
- package/_test_/unit/loanCredit/signLoanCreditRequest.test.ts +61 -2
- package/_test_/unit/loanOfferings/offeringsInputRules.test.ts +254 -0
- package/_test_/unit/loanOfferings/promotionRequests.test.ts +1 -1
- package/_test_/unit/loanRetailAudit/auditInputLimits.test.ts +114 -0
- package/_test_/unit/loanScoring/evaluateSciRequest.test.ts +49 -0
- package/bin/collection/constants/CollectionResultLimits.d.ts +16 -0
- package/bin/collection/constants/CollectionResultLimits.js +19 -0
- package/bin/collection/dtos/ApplyCollectionResultRequest.d.ts +5 -0
- package/bin/collection/dtos/ApplyCollectionResultRequest.js +22 -0
- package/bin/collection/dtos/ResolvePendingChargesRequest.d.ts +2 -0
- package/bin/collection/dtos/ResolvePendingChargesRequest.js +9 -0
- package/bin/collection/index.d.ts +2 -0
- package/bin/collection/index.js +3 -0
- package/bin/collection/validators/IsCentPrecision.d.ts +11 -0
- package/bin/collection/validators/IsCentPrecision.js +32 -0
- package/bin/gsfConnector/dtos/GsfCreditTerms.d.ts +7 -2
- package/bin/gsfConnector/dtos/GsfCreditTerms.js +9 -0
- package/bin/loanCollectorAssignment/constants/CollectorAssignmentLimits.d.ts +12 -0
- package/bin/loanCollectorAssignment/constants/CollectorAssignmentLimits.js +15 -0
- package/bin/loanCollectorAssignment/dtos/requests/AutoAssignRequest.d.ts +1 -0
- package/bin/loanCollectorAssignment/dtos/requests/AutoAssignRequest.js +8 -0
- package/bin/loanCollectorAssignment/dtos/requests/ListCollectorAssignmentsRequest.d.ts +14 -0
- package/bin/loanCollectorAssignment/dtos/requests/ListCollectorAssignmentsRequest.js +62 -0
- package/bin/loanCollectorAssignment/dtos/requests/ReassignRequest.d.ts +1 -0
- package/bin/loanCollectorAssignment/dtos/requests/ReassignRequest.js +4 -0
- package/bin/loanCollectorAssignment/dtos/requests/RedistributeRequest.d.ts +1 -0
- package/bin/loanCollectorAssignment/dtos/requests/RedistributeRequest.js +4 -0
- package/bin/loanCollectorAssignment/dtos/responses/ListCollectorAssignmentsResponse.d.ts +9 -0
- package/bin/loanCollectorAssignment/dtos/responses/ListCollectorAssignmentsResponse.js +2 -0
- package/bin/loanCollectorAssignment/index.d.ts +3 -0
- package/bin/loanCollectorAssignment/index.js +4 -0
- package/bin/loanConfig/constants/ParameterRequestLimits.d.ts +8 -0
- package/bin/loanConfig/constants/ParameterRequestLimits.js +11 -0
- package/bin/loanConfig/dtos/requests/BulkGetParametersRequest.d.ts +1 -1
- package/bin/loanConfig/dtos/requests/BulkGetParametersRequest.js +3 -1
- package/bin/loanConfig/dtos/requests/BulkUpdateParametersRequest.d.ts +5 -2
- package/bin/loanConfig/dtos/requests/BulkUpdateParametersRequest.js +8 -4
- package/bin/loanConfig/dtos/requests/UpdateParameterRequest.d.ts +6 -3
- package/bin/loanConfig/dtos/requests/UpdateParameterRequest.js +10 -5
- package/bin/loanConfig/index.d.ts +1 -0
- package/bin/loanConfig/index.js +1 -0
- package/bin/loanCredit/constants/LoanInputLimits.d.ts +24 -0
- package/bin/loanCredit/constants/LoanInputLimits.js +27 -0
- package/bin/loanCredit/dtos/LoanBeneficiary.d.ts +1 -0
- package/bin/loanCredit/dtos/LoanBeneficiary.js +9 -0
- package/bin/loanCredit/dtos/LoanContractDetails.d.ts +18 -1
- package/bin/loanCredit/dtos/LoanContractDetails.js +2 -1
- package/bin/loanCredit/dtos/LoanDeviceNotifyItem.d.ts +5 -0
- package/bin/loanCredit/dtos/LoanDeviceNotifyItem.js +12 -3
- package/bin/loanCredit/dtos/RestructureTerms.d.ts +1 -1
- package/bin/loanCredit/dtos/RestructureTerms.js +2 -0
- package/bin/loanCredit/dtos/SignatureContext.d.ts +5 -1
- package/bin/loanCredit/dtos/requests/ActivationCheckRequest.d.ts +1 -1
- package/bin/loanCredit/dtos/requests/ActivationCheckRequest.js +4 -0
- package/bin/loanCredit/dtos/requests/BackofficeApproveRestructureRequest.js +2 -0
- package/bin/loanCredit/dtos/requests/BackofficeProposeRestructureRequest.js +2 -0
- package/bin/loanCredit/dtos/requests/BackofficeRejectRestructureRequest.js +2 -0
- package/bin/loanCredit/dtos/requests/BackofficeWriteOffCreditRequest.js +2 -0
- package/bin/loanCredit/dtos/requests/CreateCustomerPaymentRequest.d.ts +5 -0
- package/bin/loanCredit/dtos/requests/CreateCustomerPaymentRequest.js +6 -0
- package/bin/loanCredit/dtos/requests/CreateSellerPaymentRequest.d.ts +5 -0
- package/bin/loanCredit/dtos/requests/CreateSellerPaymentRequest.js +6 -0
- package/bin/loanCredit/dtos/requests/OriginateLoanCreditRequest.d.ts +1 -0
- package/bin/loanCredit/dtos/requests/OriginateLoanCreditRequest.js +7 -1
- package/bin/loanCredit/dtos/requests/SegmentFactsBatchRequest.d.ts +1 -1
- package/bin/loanCredit/dtos/requests/SegmentFactsBatchRequest.js +3 -1
- package/bin/loanCredit/dtos/requests/SignLoanCreditRequest.d.ts +2 -0
- package/bin/loanCredit/dtos/requests/SignLoanCreditRequest.js +3 -0
- package/bin/loanCredit/dtos/requests/UpsertLoanBorrowerRequest.d.ts +1 -0
- package/bin/loanCredit/dtos/requests/UpsertLoanBorrowerRequest.js +2 -0
- package/bin/loanCredit/index.d.ts +2 -0
- package/bin/loanCredit/index.js +2 -0
- package/bin/loanCredit/validators/IsContractBirthDate.d.ts +11 -0
- package/bin/loanCredit/validators/IsContractBirthDate.js +41 -0
- package/bin/loanOfferings/constants/offeringsInputLimits.d.ts +35 -0
- package/bin/loanOfferings/constants/offeringsInputLimits.js +38 -0
- package/bin/loanOfferings/dtos/requests/ChangeCreditPlanStatusRequest.d.ts +2 -0
- package/bin/loanOfferings/dtos/requests/ChangeCreditPlanStatusRequest.js +8 -0
- package/bin/loanOfferings/dtos/requests/ChangePromotionStatusRequest.js +2 -0
- package/bin/loanOfferings/dtos/requests/ComputeAmortizationScheduleRequest.d.ts +4 -1
- package/bin/loanOfferings/dtos/requests/ComputeAmortizationScheduleRequest.js +3 -1
- package/bin/loanOfferings/dtos/requests/ConsumePromotionRequest.d.ts +1 -0
- package/bin/loanOfferings/dtos/requests/ConsumePromotionRequest.js +3 -0
- package/bin/loanOfferings/dtos/requests/CreateCreditPlanRequest.d.ts +3 -0
- package/bin/loanOfferings/dtos/requests/CreateCreditPlanRequest.js +11 -2
- package/bin/loanOfferings/dtos/requests/CreateFinancierRequest.d.ts +5 -0
- package/bin/loanOfferings/dtos/requests/CreateFinancierRequest.js +11 -0
- package/bin/loanOfferings/dtos/requests/CreatePromotionRequest.d.ts +2 -2
- package/bin/loanOfferings/dtos/requests/CreatePromotionRequest.js +9 -0
- package/bin/loanOfferings/dtos/requests/CreateSegmentRequest.d.ts +1 -1
- package/bin/loanOfferings/dtos/requests/CreateSegmentRequest.js +3 -0
- package/bin/loanOfferings/dtos/requests/CreateStorePolicyRequest.d.ts +1 -1
- package/bin/loanOfferings/dtos/requests/CreateStorePolicyRequest.js +3 -0
- package/bin/loanOfferings/dtos/requests/ReleasePromotionRequest.d.ts +8 -0
- package/bin/loanOfferings/dtos/requests/ReleasePromotionRequest.js +30 -0
- package/bin/loanOfferings/dtos/requests/ReservePromotionBonusRequest.d.ts +1 -0
- package/bin/loanOfferings/dtos/requests/ReservePromotionBonusRequest.js +3 -0
- package/bin/loanOfferings/dtos/requests/SettlePromotionBonusRequest.d.ts +1 -0
- package/bin/loanOfferings/dtos/requests/SettlePromotionBonusRequest.js +3 -0
- package/bin/loanOfferings/dtos/requests/SimulateCreditPlanRequest.d.ts +1 -0
- package/bin/loanOfferings/dtos/requests/SimulateCreditPlanRequest.js +3 -0
- package/bin/loanOfferings/dtos/requests/UpdateCohortRequest.d.ts +3 -1
- package/bin/loanOfferings/dtos/requests/UpdateCohortRequest.js +5 -2
- package/bin/loanOfferings/dtos/requests/UpdateCreditPlanRequest.d.ts +8 -0
- package/bin/loanOfferings/dtos/requests/UpdateCreditPlanRequest.js +30 -12
- package/bin/loanOfferings/dtos/requests/UpdateFinancierRequest.d.ts +6 -0
- package/bin/loanOfferings/dtos/requests/UpdateFinancierRequest.js +13 -2
- package/bin/loanOfferings/dtos/requests/UpdatePromotionRequest.d.ts +7 -1
- package/bin/loanOfferings/dtos/requests/UpdatePromotionRequest.js +25 -5
- package/bin/loanOfferings/dtos/requests/UpdateSegmentRequest.d.ts +5 -2
- package/bin/loanOfferings/dtos/requests/UpdateSegmentRequest.js +12 -5
- package/bin/loanOfferings/dtos/requests/UpdateStorePolicyRequest.d.ts +18 -9
- package/bin/loanOfferings/dtos/requests/UpdateStorePolicyRequest.js +12 -4
- package/bin/loanOfferings/dtos/responses/ReleasePromotionResponse.d.ts +11 -0
- package/bin/loanOfferings/dtos/responses/ReleasePromotionResponse.js +2 -0
- package/bin/loanOfferings/index.d.ts +5 -0
- package/bin/loanOfferings/index.js +6 -0
- package/bin/loanOfferings/validators/IsOptionalNonNull.d.ts +9 -0
- package/bin/loanOfferings/validators/IsOptionalNonNull.js +14 -0
- package/bin/loanOfferings/validators/MaxJsonBytes.d.ts +5 -0
- package/bin/loanOfferings/validators/MaxJsonBytes.js +28 -0
- package/bin/loanRetailAudit/constants/auditInputLimits.d.ts +16 -0
- package/bin/loanRetailAudit/constants/auditInputLimits.js +19 -0
- package/bin/loanRetailAudit/dtos/AuditTrailChange.js +4 -0
- package/bin/loanRetailAudit/dtos/AuditTrailContextEntry.js +3 -0
- package/bin/loanRetailAudit/dtos/requests/CreatePiiAccessLogRequest.d.ts +7 -1
- package/bin/loanRetailAudit/dtos/requests/CreatePiiAccessLogRequest.js +8 -1
- package/bin/loanRetailAudit/events/AuditTrailRecordedV1.d.ts +1 -0
- package/bin/loanRetailAudit/events/AuditTrailRecordedV1.js +5 -1
- package/bin/loanRetailAudit/index.d.ts +2 -0
- package/bin/loanRetailAudit/index.js +3 -0
- package/bin/loanRetailAudit/validators/IsIsoDateTime.d.ts +13 -0
- package/bin/loanRetailAudit/validators/IsIsoDateTime.js +29 -0
- package/bin/loanScoring/dtos/ScoringOffer.d.ts +8 -6
- package/bin/loanScoring/dtos/requests/EvaluateSciRequest.d.ts +14 -3
- package/bin/loanScoring/dtos/requests/EvaluateSciRequest.js +15 -0
- package/package.json +1 -1
- package/src/collection/constants/CollectionResultLimits.ts +18 -0
- package/src/collection/dtos/ApplyCollectionResultRequest.ts +15 -8
- package/src/collection/dtos/ResolvePendingChargesRequest.ts +7 -4
- package/src/collection/index.ts +4 -0
- package/src/collection/validators/IsCentPrecision.ts +33 -0
- package/src/gsfConnector/dtos/GsfCreditTerms.ts +15 -3
- package/src/loanCollectorAssignment/constants/CollectorAssignmentLimits.ts +16 -0
- package/src/loanCollectorAssignment/dtos/requests/AutoAssignRequest.ts +13 -1
- package/src/loanCollectorAssignment/dtos/requests/ListCollectorAssignmentsRequest.ts +52 -0
- package/src/loanCollectorAssignment/dtos/requests/ReassignRequest.ts +8 -1
- package/src/loanCollectorAssignment/dtos/requests/RedistributeRequest.ts +8 -1
- package/src/loanCollectorAssignment/dtos/responses/ListCollectorAssignmentsResponse.ts +10 -0
- package/src/loanCollectorAssignment/index.ts +5 -0
- package/src/loanConfig/constants/ParameterRequestLimits.ts +11 -0
- package/src/loanConfig/dtos/requests/BulkGetParametersRequest.ts +4 -2
- package/src/loanConfig/dtos/requests/BulkUpdateParametersRequest.ts +26 -6
- package/src/loanConfig/dtos/requests/UpdateParameterRequest.ts +15 -7
- package/src/loanConfig/index.ts +1 -0
- package/src/loanCredit/constants/LoanInputLimits.ts +29 -0
- package/src/loanCredit/dtos/LoanBeneficiary.ts +11 -1
- package/src/loanCredit/dtos/LoanContractDetails.ts +26 -3
- package/src/loanCredit/dtos/LoanDeviceNotifyItem.ts +17 -3
- package/src/loanCredit/dtos/RestructureTerms.ts +4 -2
- package/src/loanCredit/dtos/SignatureContext.ts +5 -1
- package/src/loanCredit/dtos/requests/ActivationCheckRequest.ts +12 -2
- package/src/loanCredit/dtos/requests/BackofficeApproveRestructureRequest.ts +3 -1
- package/src/loanCredit/dtos/requests/BackofficeProposeRestructureRequest.ts +3 -1
- package/src/loanCredit/dtos/requests/BackofficeRejectRestructureRequest.ts +3 -1
- package/src/loanCredit/dtos/requests/BackofficeWriteOffCreditRequest.ts +3 -1
- package/src/loanCredit/dtos/requests/CreateCustomerPaymentRequest.ts +10 -1
- package/src/loanCredit/dtos/requests/CreateSellerPaymentRequest.ts +10 -1
- package/src/loanCredit/dtos/requests/OriginateLoanCreditRequest.ts +10 -1
- package/src/loanCredit/dtos/requests/SegmentFactsBatchRequest.ts +4 -2
- package/src/loanCredit/dtos/requests/SignLoanCreditRequest.ts +6 -1
- package/src/loanCredit/dtos/requests/UpsertLoanBorrowerRequest.ts +4 -1
- package/src/loanCredit/index.ts +2 -0
- package/src/loanCredit/validators/IsContractBirthDate.ts +41 -0
- package/src/loanOfferings/constants/offeringsInputLimits.ts +47 -0
- package/src/loanOfferings/dtos/requests/ChangeCreditPlanStatusRequest.ts +9 -1
- package/src/loanOfferings/dtos/requests/ChangePromotionStatusRequest.ts +3 -1
- package/src/loanOfferings/dtos/requests/ComputeAmortizationScheduleRequest.ts +7 -2
- package/src/loanOfferings/dtos/requests/ConsumePromotionRequest.ts +5 -1
- package/src/loanOfferings/dtos/requests/CreateCreditPlanRequest.ts +24 -2
- package/src/loanOfferings/dtos/requests/CreateFinancierRequest.ts +25 -0
- package/src/loanOfferings/dtos/requests/CreatePromotionRequest.ts +17 -2
- package/src/loanOfferings/dtos/requests/CreateSegmentRequest.ts +4 -1
- package/src/loanOfferings/dtos/requests/CreateStorePolicyRequest.ts +6 -1
- package/src/loanOfferings/dtos/requests/ReleasePromotionRequest.ts +16 -0
- package/src/loanOfferings/dtos/requests/ReservePromotionBonusRequest.ts +5 -1
- package/src/loanOfferings/dtos/requests/SettlePromotionBonusRequest.ts +5 -1
- package/src/loanOfferings/dtos/requests/SimulateCreditPlanRequest.ts +5 -1
- package/src/loanOfferings/dtos/requests/UpdateCohortRequest.ts +6 -3
- package/src/loanOfferings/dtos/requests/UpdateCreditPlanRequest.ts +43 -12
- package/src/loanOfferings/dtos/requests/UpdateFinancierRequest.ts +26 -2
- package/src/loanOfferings/dtos/requests/UpdatePromotionRequest.ts +34 -6
- package/src/loanOfferings/dtos/requests/UpdateSegmentRequest.ts +14 -7
- package/src/loanOfferings/dtos/requests/UpdateStorePolicyRequest.ts +28 -13
- package/src/loanOfferings/dtos/responses/ReleasePromotionResponse.ts +11 -0
- package/src/loanOfferings/index.ts +7 -0
- package/src/loanOfferings/validators/IsOptionalNonNull.ts +12 -0
- package/src/loanOfferings/validators/MaxJsonBytes.ts +28 -0
- package/src/loanRetailAudit/constants/auditInputLimits.ts +20 -0
- package/src/loanRetailAudit/dtos/AuditTrailChange.ts +5 -1
- package/src/loanRetailAudit/dtos/AuditTrailContextEntry.ts +4 -1
- package/src/loanRetailAudit/dtos/requests/CreatePiiAccessLogRequest.ts +20 -3
- package/src/loanRetailAudit/events/AuditTrailRecordedV1.ts +7 -2
- package/src/loanRetailAudit/index.ts +4 -0
- package/src/loanRetailAudit/validators/IsIsoDateTime.ts +33 -0
- package/src/loanScoring/dtos/ScoringOffer.ts +8 -6
- package/src/loanScoring/dtos/requests/EvaluateSciRequest.ts +26 -4
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ClassConstructor, plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
ChangeCreditPlanStatusRequest,
|
|
6
|
+
ChangePromotionStatusRequest,
|
|
7
|
+
ComputeAmortizationScheduleRequest,
|
|
8
|
+
ConsumePromotionRequest,
|
|
9
|
+
CreateCreditPlanRequest,
|
|
10
|
+
CreateFinancierRequest,
|
|
11
|
+
CreatePromotionRequest,
|
|
12
|
+
CreateSegmentRequest,
|
|
13
|
+
CreateStorePolicyRequest,
|
|
14
|
+
ReleasePromotionRequest,
|
|
15
|
+
ReservePromotionBonusRequest,
|
|
16
|
+
SettlePromotionBonusRequest,
|
|
17
|
+
SimulateCreditPlanRequest,
|
|
18
|
+
UpdateCohortRequest,
|
|
19
|
+
UpdateCreditPlanRequest,
|
|
20
|
+
UpdateFinancierRequest,
|
|
21
|
+
UpdatePromotionRequest,
|
|
22
|
+
UpdateSegmentRequest,
|
|
23
|
+
UpdateStorePolicyRequest,
|
|
24
|
+
} from '../../../src/loanOfferings/index';
|
|
25
|
+
|
|
26
|
+
/** Propiedades que fallan, validando como el lambda (whitelist sin rechazar extras). */
|
|
27
|
+
const errorProperties = async <T extends object>(dtoClass: ClassConstructor<T>, body: object): Promise<string[]> =>
|
|
28
|
+
(await validate(plainToInstance(dtoClass, body), { whitelist: true, forbidNonWhitelisted: false }))
|
|
29
|
+
.map((error) => error.property)
|
|
30
|
+
.sort();
|
|
31
|
+
|
|
32
|
+
const ids = (count: number, prefix = 'ID'): string[] => Array.from({ length: count }, (_, i) => `${prefix}-${i}`);
|
|
33
|
+
|
|
34
|
+
describe('PUT parciales: null en un campo no anulable da 400 (L-100)', () => {
|
|
35
|
+
const cases: Array<[string, ClassConstructor<object>, string[]]> = [
|
|
36
|
+
[
|
|
37
|
+
'UpdateCreditPlanRequest',
|
|
38
|
+
UpdateCreditPlanRequest,
|
|
39
|
+
[
|
|
40
|
+
'name',
|
|
41
|
+
'description',
|
|
42
|
+
'saleTypes',
|
|
43
|
+
'minDownPaymentPct',
|
|
44
|
+
'termWeeksOptions',
|
|
45
|
+
'maxFinancedAmountCents',
|
|
46
|
+
'tnaAnnual',
|
|
47
|
+
'openingCommissionPct',
|
|
48
|
+
'paymentShift',
|
|
49
|
+
'prepaymentMinimum',
|
|
50
|
+
],
|
|
51
|
+
],
|
|
52
|
+
['UpdatePromotionRequest', UpdatePromotionRequest, ['name', 'description', 'effect', 'segmentId', 'targetLevels']],
|
|
53
|
+
['UpdateStorePolicyRequest', UpdateStorePolicyRequest, ['name', 'hardening', 'easing', 'suspended']],
|
|
54
|
+
['UpdateSegmentRequest', UpdateSegmentRequest, ['name', 'colorSlot', 'cohortIds', 'filterIds', 'memberIds']],
|
|
55
|
+
['UpdateCohortRequest', UpdateCohortRequest, ['name', 'conditions']],
|
|
56
|
+
['UpdateFinancierRequest', UpdateFinancierRequest, ['name', 'label']],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
it.each(cases)('%s rechaza null en cada campo no anulable', async (_name, dtoClass, fields) => {
|
|
60
|
+
for (const field of fields) {
|
|
61
|
+
expect(await errorProperties(dtoClass, { [field]: null })).toEqual([field]);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it.each(cases)('%s acepta el parche vacío: ausente deja el valor como está', async (_name, dtoClass) => {
|
|
66
|
+
expect(await errorProperties(dtoClass, {})).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const nullable: Array<[string, ClassConstructor<object>, string[]]> = [
|
|
70
|
+
['UpdateCreditPlanRequest', UpdateCreditPlanRequest, ['eligibleTiers', 'maxDownPaymentPct', 'minSciScore', 'maxSciScore', 'validFrom', 'validUntil']],
|
|
71
|
+
[
|
|
72
|
+
'UpdatePromotionRequest',
|
|
73
|
+
UpdatePromotionRequest,
|
|
74
|
+
['productSkus', 'productCategories', 'tiers', 'retailerIds', 'storeIds', 'validFrom', 'validUntil', 'budgetMaxCents'],
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'UpdateStorePolicyRequest',
|
|
78
|
+
UpdateStorePolicyRequest,
|
|
79
|
+
['description', 'allowedSkus', 'storeIds', 'targetLevels', 'segmentIds', 'targetSkus', 'validFrom', 'validUntil'],
|
|
80
|
+
],
|
|
81
|
+
['UpdateSegmentRequest', UpdateSegmentRequest, ['description']],
|
|
82
|
+
['UpdateCohortRequest', UpdateCohortRequest, ['description']],
|
|
83
|
+
['UpdateFinancierRequest', UpdateFinancierRequest, ['externalRedirectUrl', 'eligibleTiers', 'config']],
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
it.each(nullable)('%s sigue aceptando null donde borra el valor', async (_name, dtoClass, fields) => {
|
|
87
|
+
expect(await errorProperties(dtoClass, Object.fromEntries(fields.map((field) => [field, null])))).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Plan de crédito (L-102, L-103, L-107, L-123)', () => {
|
|
92
|
+
const plan = {
|
|
93
|
+
code: 'PLAN-ORO',
|
|
94
|
+
name: 'Oro',
|
|
95
|
+
description: 'Plan para clientes de nivel oro',
|
|
96
|
+
level: 'GOLD',
|
|
97
|
+
saleTypes: ['NEW'],
|
|
98
|
+
minDownPaymentPct: 0.1,
|
|
99
|
+
termWeeksOptions: [12, 24],
|
|
100
|
+
maxFinancedAmountCents: 2_000_000,
|
|
101
|
+
tnaAnnual: 2.3,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
it('acepta un plan válido', async () => {
|
|
105
|
+
expect(await errorProperties(CreateCreditPlanRequest, plan)).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('vigencia en ISO-8601; null sigue siendo «sin fecha»', async () => {
|
|
109
|
+
expect(await errorProperties(CreateCreditPlanRequest, { ...plan, validFrom: '2026-11-03', validUntil: null })).toEqual([]);
|
|
110
|
+
expect(await errorProperties(CreateCreditPlanRequest, { ...plan, validFrom: '03/11/2026', validUntil: '' })).toEqual([
|
|
111
|
+
'validFrom',
|
|
112
|
+
'validUntil',
|
|
113
|
+
]);
|
|
114
|
+
expect(await errorProperties(UpdateCreditPlanRequest, { validUntil: 'mañana' })).toEqual(['validUntil']);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('plazos de 1 a 104 semanas', async () => {
|
|
118
|
+
expect(await errorProperties(CreateCreditPlanRequest, { ...plan, termWeeksOptions: [104] })).toEqual([]);
|
|
119
|
+
expect(await errorProperties(CreateCreditPlanRequest, { ...plan, termWeeksOptions: [12, 120] })).toEqual(['termWeeksOptions']);
|
|
120
|
+
expect(await errorProperties(UpdateCreditPlanRequest, { termWeeksOptions: [105] })).toEqual(['termWeeksOptions']);
|
|
121
|
+
const tooMany = Array.from({ length: 105 }, (_, i) => (i % 104) + 1);
|
|
122
|
+
expect(await errorProperties(UpdateCreditPlanRequest, { termWeeksOptions: tooMany })).toEqual(['termWeeksOptions']);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('code, nombre y descripción no vacíos y con tope', async () => {
|
|
126
|
+
const body = { ...plan, code: '', name: 'x'.repeat(121), description: 'x'.repeat(501) };
|
|
127
|
+
expect(await errorProperties(CreateCreditPlanRequest, body)).toEqual(['code', 'description', 'name']);
|
|
128
|
+
expect(await errorProperties(CreateCreditPlanRequest, { ...plan, code: 'C'.repeat(65) })).toEqual(['code']);
|
|
129
|
+
expect(await errorProperties(UpdateCreditPlanRequest, { name: '', description: '' })).toEqual(['description', 'name']);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('el motivo del PUT y del cambio de estado se declara y se acota a 280', async () => {
|
|
133
|
+
const dto = plainToInstance(UpdateCreditPlanRequest, { tnaAnnual: 2.3, reason: 'Ajuste de tasa' });
|
|
134
|
+
expect(await validate(dto, { whitelist: true })).toEqual([]);
|
|
135
|
+
expect(dto.reason).toBe('Ajuste de tasa');
|
|
136
|
+
expect(await errorProperties(UpdateCreditPlanRequest, { reason: 'x'.repeat(281) })).toEqual(['reason']);
|
|
137
|
+
expect(await errorProperties(ChangeCreditPlanStatusRequest, { status: 'ACTIVE', reason: 'Fin de campaña' })).toEqual([]);
|
|
138
|
+
expect(await errorProperties(ChangeCreditPlanStatusRequest, { status: 'ACTIVE', reason: 'x'.repeat(281) })).toEqual(['reason']);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('Amortización: plazo 104 y TNA con techo técnico interino (L-103)', () => {
|
|
143
|
+
const body = { financedAmountCents: 400_000, tnaAnnual: 2.3, termWeeks: 52, ivaOnInterest: 0.16 };
|
|
144
|
+
|
|
145
|
+
it('acepta la TNA de una reestructura por encima de 2.60', async () => {
|
|
146
|
+
expect(await errorProperties(ComputeAmortizationScheduleRequest, { ...body, tnaAnnual: 3.0 })).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('rechaza una TNA absurda', async () => {
|
|
150
|
+
expect(await errorProperties(ComputeAmortizationScheduleRequest, { ...body, tnaAnnual: 5.01 })).toEqual(['tnaAnnual']);
|
|
151
|
+
expect(await errorProperties(ComputeAmortizationScheduleRequest, { ...body, termWeeks: 105 })).toEqual(['termWeeks']);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('Promociones (L-104, L-105, L-107, L-123, L-052)', () => {
|
|
156
|
+
const promotion = {
|
|
157
|
+
name: 'Enganche 5 puntos menos',
|
|
158
|
+
description: 'Baja el enganche mínimo cinco puntos',
|
|
159
|
+
effect: { downPaymentDeltaPct: -0.05 },
|
|
160
|
+
segmentId: 'SEG-1',
|
|
161
|
+
targetLevels: ['SILVER'],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
it('nombre y descripción no vacíos, 120 y 500', async () => {
|
|
165
|
+
expect(await errorProperties(CreatePromotionRequest, promotion)).toEqual([]);
|
|
166
|
+
const body = { ...promotion, name: 'x'.repeat(121), description: '' };
|
|
167
|
+
expect(await errorProperties(CreatePromotionRequest, body)).toEqual(['description', 'name']);
|
|
168
|
+
expect(await errorProperties(UpdatePromotionRequest, { description: 'x'.repeat(501) })).toEqual(['description']);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('tiendas hasta 1 000 y SKUs hasta 2 000, sin vacíos', async () => {
|
|
172
|
+
expect(await errorProperties(CreatePromotionRequest, { ...promotion, storeIds: ids(1000), productSkus: ids(2000) })).toEqual([]);
|
|
173
|
+
expect(await errorProperties(CreatePromotionRequest, { ...promotion, storeIds: ids(1001) })).toEqual(['storeIds']);
|
|
174
|
+
expect(await errorProperties(UpdatePromotionRequest, { productSkus: ids(2001) })).toEqual(['productSkus']);
|
|
175
|
+
expect(await errorProperties(UpdatePromotionRequest, { storeIds: ['ST-1', ''], productSkus: [''] })).toEqual([
|
|
176
|
+
'productSkus',
|
|
177
|
+
'storeIds',
|
|
178
|
+
]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('el PUT declara el motivo, y la pausa lo acota a 280', async () => {
|
|
182
|
+
expect(await errorProperties(UpdatePromotionRequest, { reason: 'Corrige el nombre' })).toEqual([]);
|
|
183
|
+
expect(await errorProperties(UpdatePromotionRequest, { reason: 'x'.repeat(281) })).toEqual(['reason']);
|
|
184
|
+
expect(await errorProperties(ChangePromotionStatusRequest, { status: 'PAUSED', reason: 'x'.repeat(281) })).toEqual(['reason']);
|
|
185
|
+
expect(await errorProperties(ChangePromotionStatusRequest, { status: 'PAUSED', reason: null })).toEqual([]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('creditId y bonusId no vacíos: son la llave de idempotencia', async () => {
|
|
189
|
+
const creditId = '01KXPB422AFGZHQKHVM7E5YDBR';
|
|
190
|
+
expect(await errorProperties(ConsumePromotionRequest, { creditId })).toEqual([]);
|
|
191
|
+
expect(await errorProperties(ConsumePromotionRequest, { creditId: '' })).toEqual(['creditId']);
|
|
192
|
+
expect(await errorProperties(ReservePromotionBonusRequest, { creditId: '' })).toEqual(['creditId']);
|
|
193
|
+
expect(await errorProperties(SettlePromotionBonusRequest, { bonusId: '', status: 'SETTLED' })).toEqual(['bonusId']);
|
|
194
|
+
expect(await errorProperties(ConsumePromotionRequest, { creditId: 'x'.repeat(129) })).toEqual(['creditId']);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('release recibe el crédito que se canceló, con las mismas reglas', async () => {
|
|
198
|
+
expect(await errorProperties(ReleasePromotionRequest, { creditId: '01KXPB422AFGZHQKHVM7E5YDBR' })).toEqual([]);
|
|
199
|
+
expect(await errorProperties(ReleasePromotionRequest, {})).toEqual(['creditId']);
|
|
200
|
+
expect(await errorProperties(ReleasePromotionRequest, { creditId: '' })).toEqual(['creditId']);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('Topes de listas (L-105, L-117, L-135)', () => {
|
|
205
|
+
it('miembros manuales del segmento hasta 5 000, sin vacíos', async () => {
|
|
206
|
+
expect(await errorProperties(CreateSegmentRequest, { name: 'VIP', memberIds: ids(5000) })).toEqual([]);
|
|
207
|
+
expect(await errorProperties(CreateSegmentRequest, { name: 'VIP', memberIds: ids(5001) })).toEqual(['memberIds']);
|
|
208
|
+
expect(await errorProperties(UpdateSegmentRequest, { memberIds: ['RC-1', ''] })).toEqual(['memberIds']);
|
|
209
|
+
expect(await errorProperties(UpdateSegmentRequest, { memberIds: [] })).toEqual([]);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('tiendas de la política hasta 1 000, sin vacíos', async () => {
|
|
213
|
+
expect(await errorProperties(CreateStorePolicyRequest, { name: 'Centro', storeIds: ids(1000) })).toEqual([]);
|
|
214
|
+
expect(await errorProperties(CreateStorePolicyRequest, { name: 'Centro', storeIds: ids(1001) })).toEqual(['storeIds']);
|
|
215
|
+
expect(await errorProperties(UpdateStorePolicyRequest, { storeIds: [''] })).toEqual(['storeIds']);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('segmentos del cliente en la cotización hasta 50, sin vacíos', async () => {
|
|
219
|
+
const quote = { productPriceCents: 500_000, downPaymentPct: 0.2, termWeeks: 24 };
|
|
220
|
+
expect(await errorProperties(SimulateCreditPlanRequest, { ...quote, customerSegmentIds: ids(50) })).toEqual([]);
|
|
221
|
+
expect(await errorProperties(SimulateCreditPlanRequest, { ...quote, customerSegmentIds: ids(51) })).toEqual(['customerSegmentIds']);
|
|
222
|
+
expect(await errorProperties(SimulateCreditPlanRequest, { ...quote, customerSegmentIds: [''] })).toEqual(['customerSegmentIds']);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('Financieras (L-106, L-123)', () => {
|
|
227
|
+
const financier = { code: 'PCF', name: 'Pago Confiado', type: 'INTERNAL', label: 'Sin trámites adicionales' };
|
|
228
|
+
|
|
229
|
+
it('acepta una financiera válida', async () => {
|
|
230
|
+
expect(await errorProperties(CreateFinancierRequest, financier)).toEqual([]);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('la URL de redirección es https, con protocolo y hasta 2048', async () => {
|
|
234
|
+
const url = (externalRedirectUrl: unknown) => ({ ...financier, externalRedirectUrl });
|
|
235
|
+
expect(await errorProperties(CreateFinancierRequest, url('https://financiera.mx/solicitud?ref=sk'))).toEqual([]);
|
|
236
|
+
expect(await errorProperties(CreateFinancierRequest, url(null))).toEqual([]);
|
|
237
|
+
for (const bad of ['http://financiera.mx', 'financiera.mx', '', `https://financiera.mx/${'a'.repeat(2048)}`]) {
|
|
238
|
+
expect(await errorProperties(CreateFinancierRequest, url(bad))).toEqual(['externalRedirectUrl']);
|
|
239
|
+
}
|
|
240
|
+
expect(await errorProperties(UpdateFinancierRequest, { externalRedirectUrl: 'javascript:alert(1)' })).toEqual(['externalRedirectUrl']);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('config (deprecado) acepta hasta 4 KB serializado', async () => {
|
|
244
|
+
expect(await errorProperties(CreateFinancierRequest, { ...financier, config: { note: 'x'.repeat(4000) } })).toEqual([]);
|
|
245
|
+
expect(await errorProperties(UpdateFinancierRequest, { config: { note: 'x'.repeat(4096) } })).toEqual(['config']);
|
|
246
|
+
expect(await errorProperties(UpdateFinancierRequest, { config: { note: 'ñ'.repeat(2045) } })).toEqual(['config']);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('code, nombre y etiqueta no vacíos y con tope', async () => {
|
|
250
|
+
const body = { ...financier, code: 'C'.repeat(65), name: '', label: 'x'.repeat(121) };
|
|
251
|
+
expect(await errorProperties(CreateFinancierRequest, body)).toEqual(['code', 'label', 'name']);
|
|
252
|
+
expect(await errorProperties(UpdateFinancierRequest, { name: '', label: '' })).toEqual(['label', 'name']);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -20,7 +20,7 @@ const errorProperties = async (
|
|
|
20
20
|
raw: Record<string, unknown>,
|
|
21
21
|
dtoClass: typeof CreatePromotionRequest | typeof UpdatePromotionRequest = CreatePromotionRequest,
|
|
22
22
|
): Promise<string[]> => {
|
|
23
|
-
const instance = plainToInstance(dtoClass, raw, { excludeExtraneousValues: true });
|
|
23
|
+
const instance = plainToInstance<object, Record<string, unknown>>(dtoClass, raw, { excludeExtraneousValues: true });
|
|
24
24
|
return (await validate(instance as object)).map((error) => error.property);
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ClassConstructor, plainToInstance } from 'class-transformer';
|
|
3
|
+
import { ValidationError, validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
AUDIT_TRAIL_CHANGES_MAX_ITEMS,
|
|
6
|
+
AUDIT_TRAIL_CONTEXT_MAX_ITEMS,
|
|
7
|
+
AuditTrailActionEnum,
|
|
8
|
+
AuditTrailRecordedV1,
|
|
9
|
+
CreatePiiAccessLogRequest,
|
|
10
|
+
isIsoDateTime,
|
|
11
|
+
PII_ACCESS_FIELDS_MAX_ITEMS,
|
|
12
|
+
PiiAccessActionEnum,
|
|
13
|
+
PiiResourceTypeEnum,
|
|
14
|
+
RetailAuditEntityTypeEnum,
|
|
15
|
+
} from '../../../src/loanRetailAudit/index';
|
|
16
|
+
|
|
17
|
+
const errorPaths = async <T extends object>(dtoClass: ClassConstructor<T>, body: object): Promise<string[]> => {
|
|
18
|
+
const pathsOf = (errors: ValidationError[], parent: string): string[] =>
|
|
19
|
+
errors.flatMap((error) => {
|
|
20
|
+
const path = parent ? `${parent}.${error.property}` : error.property;
|
|
21
|
+
return [...(error.constraints ? [path] : []), ...pathsOf(error.children ?? [], path)];
|
|
22
|
+
});
|
|
23
|
+
return pathsOf(await validate(plainToInstance(dtoClass, body), { whitelist: true }), '').sort();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const EVENT = {
|
|
27
|
+
eventId: '01JABCDEF0123456789XYZABCD',
|
|
28
|
+
entityType: RetailAuditEntityTypeEnum.SALE,
|
|
29
|
+
entityId: 'sale-1',
|
|
30
|
+
action: AuditTrailActionEnum.SALE_CANCEL,
|
|
31
|
+
changes: [{ field: 'status', oldValue: 'COMPLETED', newValue: 'CANCELLED' }],
|
|
32
|
+
context: [{ key: 'saleType', value: 'CASH' }],
|
|
33
|
+
performedBy: 'user-1',
|
|
34
|
+
sourceLambda: 'retail-wizard-business',
|
|
35
|
+
occurredAt: '2026-10-19T12:00:00.000Z',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const PII = {
|
|
39
|
+
accessId: '01JABCDEF0123456789XYZABCE',
|
|
40
|
+
occurredAt: '2026-10-19T12:00:00.000Z',
|
|
41
|
+
correlationId: 'corr-1',
|
|
42
|
+
actorId: 'sub-1',
|
|
43
|
+
sourceLambda: 'retail-customer-business',
|
|
44
|
+
operationName: 'privateListCustomers',
|
|
45
|
+
action: PiiAccessActionEnum.READ,
|
|
46
|
+
resourceType: PiiResourceTypeEnum.RETAIL_CUSTOMER,
|
|
47
|
+
resourceId: '(list:tenant)',
|
|
48
|
+
fields: ['fullName', 'phone'],
|
|
49
|
+
resourceIds: ['01KXPB422AFGZHQKHVM7E5YDBR', 'acc-0123456789abcdef0123456789abcdef'],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe('occurredAt: fecha-hora ISO completa con zona (L-188)', () => {
|
|
53
|
+
it('acepta lo que produce toISOString() y un offset', () => {
|
|
54
|
+
expect(isIsoDateTime(new Date().toISOString())).toBe(true);
|
|
55
|
+
expect(isIsoDateTime('2026-11-03T04:00:00-06:00')).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('rechaza semana, día del año, año suelto, fecha sola, sin zona y fechas que no existen', () => {
|
|
59
|
+
for (const value of ['2026-W44-1', '2026-305', '2026', '2026-11-03', '2026-11-03T10:00:00', '2026-02-30T00:00:00Z', '2026-11-03T24:00:00Z', 20261103]) {
|
|
60
|
+
expect(isIsoDateTime(value)).toBe(false);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('lo aplican el evento de auditoría y el acceso a PII', async () => {
|
|
65
|
+
expect(await errorPaths(AuditTrailRecordedV1, EVENT)).toEqual([]);
|
|
66
|
+
expect(await errorPaths(AuditTrailRecordedV1, { ...EVENT, occurredAt: '2026-W44-1' })).toEqual(['occurredAt']);
|
|
67
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, PII)).toEqual([]);
|
|
68
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, occurredAt: '2026-10-19' })).toEqual(['occurredAt']);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Topes del evento de auditoría (L-183)', () => {
|
|
73
|
+
it('cambios hasta 100 y contexto hasta 50', async () => {
|
|
74
|
+
const changes = Array.from({ length: AUDIT_TRAIL_CHANGES_MAX_ITEMS + 1 }, () => EVENT.changes[0]);
|
|
75
|
+
const context = Array.from({ length: AUDIT_TRAIL_CONTEXT_MAX_ITEMS + 1 }, () => EVENT.context[0]);
|
|
76
|
+
expect(await errorPaths(AuditTrailRecordedV1, { ...EVENT, changes, context })).toEqual(['changes', 'context']);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('nombres hasta 128 y valores hasta 4 KB', async () => {
|
|
80
|
+
const body = {
|
|
81
|
+
...EVENT,
|
|
82
|
+
changes: [{ field: 'f'.repeat(129), oldValue: 'o'.repeat(4097), newValue: 'n'.repeat(4096) }],
|
|
83
|
+
context: [{ key: 'k'.repeat(129), value: 'v'.repeat(4097) }],
|
|
84
|
+
};
|
|
85
|
+
expect(await errorPaths(AuditTrailRecordedV1, body)).toEqual([
|
|
86
|
+
'changes.0.field',
|
|
87
|
+
'changes.0.oldValue',
|
|
88
|
+
'context.0.key',
|
|
89
|
+
'context.0.value',
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('Acceso a PII: campos e ids sin vacíos y con tope (L-183, L-187)', () => {
|
|
95
|
+
it('rechaza nombres de campo o ids vacíos', async () => {
|
|
96
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, fields: ['fullName', ''] })).toEqual(['fields']);
|
|
97
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, resourceIds: ['01KXPB422AFGZHQKHVM7E5YDBR', ''] })).toEqual(['resourceIds']);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('acota los campos a 50 y cada id a 128', async () => {
|
|
101
|
+
const fields = Array.from({ length: PII_ACCESS_FIELDS_MAX_ITEMS + 1 }, (_, i) => `field${i}`);
|
|
102
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, fields })).toEqual(['fields']);
|
|
103
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, resourceIds: ['r'.repeat(129)] })).toEqual(['resourceIds']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('acepta un listado grande: la cantidad de ids todavía no tiene tope', async () => {
|
|
107
|
+
const resourceIds = Array.from({ length: 5000 }, (_, i) => `01KXPB422AFGZHQKHVM7E${String(i).padStart(5, '0')}`);
|
|
108
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, resourceIds })).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('un listado vacío sigue siendo válido', async () => {
|
|
112
|
+
expect(await errorPaths(CreatePiiAccessLogRequest, { ...PII, resourceIds: [] })).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import { EvaluateSciRequest } from '../../../src/loanScoring/index';
|
|
5
|
+
|
|
6
|
+
const errorProperties = async (body: object): Promise<string[]> =>
|
|
7
|
+
(await validate(plainToInstance(EvaluateSciRequest, body), { whitelist: true })).map((error) => error.property).sort();
|
|
8
|
+
|
|
9
|
+
/** Como lo manda el wizard: ids ULID, CURP en mayúsculas y celular de 10 dígitos. */
|
|
10
|
+
const BODY = {
|
|
11
|
+
borrowerId: '01KXPB422AFGZHQKHVM7E5YDBR',
|
|
12
|
+
retailCustomerId: '01KXPB422AFGZHQKHVM7E5YDBS',
|
|
13
|
+
curp: 'LOHM900215MMCPRR09',
|
|
14
|
+
cellphone: '5512345678',
|
|
15
|
+
antiFraudPassed: true,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('EvaluateSciRequest', () => {
|
|
19
|
+
it('acepta el request del wizard, con y sin llave de idempotencia (L-173)', async () => {
|
|
20
|
+
expect(await errorProperties(BODY)).toEqual([]);
|
|
21
|
+
expect(await errorProperties({ ...BODY, idempotencyKey: 'wizard-session-01KXPB' })).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('la llave de idempotencia no va vacía ni pasa de 128', async () => {
|
|
25
|
+
expect(await errorProperties({ ...BODY, idempotencyKey: '' })).toEqual(['idempotencyKey']);
|
|
26
|
+
expect(await errorProperties({ ...BODY, idempotencyKey: 'k'.repeat(129) })).toEqual(['idempotencyKey']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('acepta la CURP de prueba del mock de Círculo (empieza con X)', async () => {
|
|
30
|
+
expect(await errorProperties({ ...BODY, curp: 'XOAC900101HDFRRL05' })).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('rechaza celular con +52 o separadores, y borrowerId que no es ULID (L-177)', async () => {
|
|
34
|
+
const body = { ...BODY, cellphone: '+525512345678', borrowerId: 'SMOKE-B-LSB' };
|
|
35
|
+
expect(await errorProperties(body)).toEqual(['borrowerId', 'cellphone']);
|
|
36
|
+
expect(await errorProperties({ ...BODY, cellphone: '55 1234 5678' })).toEqual(['cellphone']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('el CURP solo tiene tope de largo: retail-customer no valida su formato', async () => {
|
|
40
|
+
expect(await errorProperties({ ...BODY, curp: 'lohm900215mmcprr09' })).toEqual([]);
|
|
41
|
+
expect(await errorProperties({ ...BODY, curp: 'LOHM900215MMCPRR0' })).toEqual([]);
|
|
42
|
+
expect(await errorProperties({ ...BODY, curp: 'C'.repeat(65) })).toEqual(['curp']);
|
|
43
|
+
expect(await errorProperties({ ...BODY, curp: '' })).toEqual(['curp']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('retailCustomerId hasta 64', async () => {
|
|
47
|
+
expect(await errorProperties({ ...BODY, retailCustomerId: 'r'.repeat(65) })).toEqual(['retailCustomerId']);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Largo máximo de los ids que el motor manda en `apply-result` (`attemptId`, `intentId`, `chargeId`,
|
|
3
|
+
* `holdTxId`). Los reales miden menos de 100: `attemptId` = `<intentId>#<proveedor>#<mecanismo>#<ciclo>`.
|
|
4
|
+
* Con 256 cabe la llave compuesta del dominio sin acercarse a los 2 KB de la pk de DynamoDB.
|
|
5
|
+
*/
|
|
6
|
+
export declare const COLLECTION_RESULT_ID_MAX_LENGTH = 256;
|
|
7
|
+
/** Largo máximo de `domain`, `directoryId` y `tenantId` en `resolve-charges`. */
|
|
8
|
+
export declare const COLLECTION_RESOLVE_FIELD_MAX_LENGTH = 128;
|
|
9
|
+
/**
|
|
10
|
+
* Techo técnico de un monto del motor, en PESOS (o en la moneda de la wallet): 1 000 millones.
|
|
11
|
+
*
|
|
12
|
+
* No es política de negocio: un equipo de SureKeep cuesta decenas de miles de pesos. Hasta este techo un
|
|
13
|
+
* `double` representa el monto con resolución de ~0.00001 centavos, así que `Math.round(monto * 100)`
|
|
14
|
+
* da el centavo exacto y el resultado queda muy por debajo de `Number.MAX_SAFE_INTEGER`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const COLLECTION_AMOUNT_MAX = 1000000000;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLLECTION_AMOUNT_MAX = exports.COLLECTION_RESOLVE_FIELD_MAX_LENGTH = exports.COLLECTION_RESULT_ID_MAX_LENGTH = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Largo máximo de los ids que el motor manda en `apply-result` (`attemptId`, `intentId`, `chargeId`,
|
|
6
|
+
* `holdTxId`). Los reales miden menos de 100: `attemptId` = `<intentId>#<proveedor>#<mecanismo>#<ciclo>`.
|
|
7
|
+
* Con 256 cabe la llave compuesta del dominio sin acercarse a los 2 KB de la pk de DynamoDB.
|
|
8
|
+
*/
|
|
9
|
+
exports.COLLECTION_RESULT_ID_MAX_LENGTH = 256;
|
|
10
|
+
/** Largo máximo de `domain`, `directoryId` y `tenantId` en `resolve-charges`. */
|
|
11
|
+
exports.COLLECTION_RESOLVE_FIELD_MAX_LENGTH = 128;
|
|
12
|
+
/**
|
|
13
|
+
* Techo técnico de un monto del motor, en PESOS (o en la moneda de la wallet): 1 000 millones.
|
|
14
|
+
*
|
|
15
|
+
* No es política de negocio: un equipo de SureKeep cuesta decenas de miles de pesos. Hasta este techo un
|
|
16
|
+
* `double` representa el monto con resolución de ~0.00001 centavos, así que `Math.round(monto * 100)`
|
|
17
|
+
* da el centavo exacto y el resultado queda muy por debajo de `Number.MAX_SAFE_INTEGER`.
|
|
18
|
+
*/
|
|
19
|
+
exports.COLLECTION_AMOUNT_MAX = 1000000000;
|
|
@@ -8,6 +8,9 @@ import { CurrencyId } from '../../currency/enums/CurrencyId';
|
|
|
8
8
|
* respuesta se lee como no entregada y el mismo resultado vuelve más tarde, así que aplicarlo tiene
|
|
9
9
|
* que ser idempotente: el dominio deduplica por `attemptId`, que identifica ESE intento y no el cobro
|
|
10
10
|
* entero.
|
|
11
|
+
*
|
|
12
|
+
* Los ids no vacíos y con tope evitan que un `attemptId` vacío colapse la deduplicación (L-005). Los
|
|
13
|
+
* montos van en pesos, con centavos, sin negativos y bajo un techo técnico (L-022).
|
|
11
14
|
*/
|
|
12
15
|
export declare class ApplyCollectionResultRequest {
|
|
13
16
|
attemptId: string;
|
|
@@ -16,7 +19,9 @@ export declare class ApplyCollectionResultRequest {
|
|
|
16
19
|
domain: string;
|
|
17
20
|
tenantId: string;
|
|
18
21
|
result: CollectionResultStatus;
|
|
22
|
+
/** Pesos cobrados (débito directo). */
|
|
19
23
|
amountCollected: number;
|
|
24
|
+
/** Pesos retenidos (preautorización). */
|
|
20
25
|
amountReserved: number;
|
|
21
26
|
mechanism: MechanismType;
|
|
22
27
|
holdTxId?: string;
|
|
@@ -11,6 +11,8 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ApplyCollectionResultRequest = void 0;
|
|
13
13
|
const class_validator_1 = require("class-validator");
|
|
14
|
+
const CollectionResultLimits_1 = require("../constants/CollectionResultLimits");
|
|
15
|
+
const IsCentPrecision_1 = require("../validators/IsCentPrecision");
|
|
14
16
|
const CollectionResultStatus_1 = require("../enums/CollectionResultStatus");
|
|
15
17
|
const MechanismType_1 = require("../enums/MechanismType");
|
|
16
18
|
const CurrencyId_1 = require("../../currency/enums/CurrencyId");
|
|
@@ -21,20 +23,29 @@ const CurrencyId_1 = require("../../currency/enums/CurrencyId");
|
|
|
21
23
|
* respuesta se lee como no entregada y el mismo resultado vuelve más tarde, así que aplicarlo tiene
|
|
22
24
|
* que ser idempotente: el dominio deduplica por `attemptId`, que identifica ESE intento y no el cobro
|
|
23
25
|
* entero.
|
|
26
|
+
*
|
|
27
|
+
* Los ids no vacíos y con tope evitan que un `attemptId` vacío colapse la deduplicación (L-005). Los
|
|
28
|
+
* montos van en pesos, con centavos, sin negativos y bajo un techo técnico (L-022).
|
|
24
29
|
*/
|
|
25
30
|
class ApplyCollectionResultRequest {
|
|
26
31
|
}
|
|
27
32
|
exports.ApplyCollectionResultRequest = ApplyCollectionResultRequest;
|
|
28
33
|
__decorate([
|
|
29
34
|
(0, class_validator_1.IsString)(),
|
|
35
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
36
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESULT_ID_MAX_LENGTH),
|
|
30
37
|
__metadata("design:type", String)
|
|
31
38
|
], ApplyCollectionResultRequest.prototype, "attemptId", void 0);
|
|
32
39
|
__decorate([
|
|
33
40
|
(0, class_validator_1.IsString)(),
|
|
41
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
42
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESULT_ID_MAX_LENGTH),
|
|
34
43
|
__metadata("design:type", String)
|
|
35
44
|
], ApplyCollectionResultRequest.prototype, "intentId", void 0);
|
|
36
45
|
__decorate([
|
|
37
46
|
(0, class_validator_1.IsString)(),
|
|
47
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
48
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESULT_ID_MAX_LENGTH),
|
|
38
49
|
__metadata("design:type", String)
|
|
39
50
|
], ApplyCollectionResultRequest.prototype, "chargeId", void 0);
|
|
40
51
|
__decorate([
|
|
@@ -51,10 +62,16 @@ __decorate([
|
|
|
51
62
|
], ApplyCollectionResultRequest.prototype, "result", void 0);
|
|
52
63
|
__decorate([
|
|
53
64
|
(0, class_validator_1.IsNumber)(),
|
|
65
|
+
(0, IsCentPrecision_1.IsCentPrecision)(),
|
|
66
|
+
(0, class_validator_1.Min)(0),
|
|
67
|
+
(0, class_validator_1.Max)(CollectionResultLimits_1.COLLECTION_AMOUNT_MAX),
|
|
54
68
|
__metadata("design:type", Number)
|
|
55
69
|
], ApplyCollectionResultRequest.prototype, "amountCollected", void 0);
|
|
56
70
|
__decorate([
|
|
57
71
|
(0, class_validator_1.IsNumber)(),
|
|
72
|
+
(0, IsCentPrecision_1.IsCentPrecision)(),
|
|
73
|
+
(0, class_validator_1.Min)(0),
|
|
74
|
+
(0, class_validator_1.Max)(CollectionResultLimits_1.COLLECTION_AMOUNT_MAX),
|
|
58
75
|
__metadata("design:type", Number)
|
|
59
76
|
], ApplyCollectionResultRequest.prototype, "amountReserved", void 0);
|
|
60
77
|
__decorate([
|
|
@@ -64,11 +81,16 @@ __decorate([
|
|
|
64
81
|
__decorate([
|
|
65
82
|
(0, class_validator_1.IsOptional)(),
|
|
66
83
|
(0, class_validator_1.IsString)(),
|
|
84
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
85
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESULT_ID_MAX_LENGTH),
|
|
67
86
|
__metadata("design:type", String)
|
|
68
87
|
], ApplyCollectionResultRequest.prototype, "holdTxId", void 0);
|
|
69
88
|
__decorate([
|
|
70
89
|
(0, class_validator_1.IsOptional)(),
|
|
71
90
|
(0, class_validator_1.IsNumber)(),
|
|
91
|
+
(0, IsCentPrecision_1.IsCentPrecision)(),
|
|
92
|
+
(0, class_validator_1.Min)(0),
|
|
93
|
+
(0, class_validator_1.Max)(CollectionResultLimits_1.COLLECTION_AMOUNT_MAX),
|
|
72
94
|
__metadata("design:type", Number)
|
|
73
95
|
], ApplyCollectionResultRequest.prototype, "walletAmount", void 0);
|
|
74
96
|
__decorate([
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* No lleva importe: el monto es justo lo que se está preguntando. Puede haber cambiado desde que el
|
|
5
5
|
* cargo se dio de alta —por mora, o porque el cliente pagó por otra vía— y sólo el dominio, que es
|
|
6
6
|
* quien aplica sus propias reglas, sabe cuánto queda vivo.
|
|
7
|
+
*
|
|
8
|
+
* Los tres campos son llave de búsqueda: vacíos o sin tope terminaban en 500 en el dominio (L-064).
|
|
7
9
|
*/
|
|
8
10
|
export declare class ResolvePendingChargesRequest {
|
|
9
11
|
domain: string;
|
|
@@ -11,25 +11,34 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ResolvePendingChargesRequest = void 0;
|
|
13
13
|
const class_validator_1 = require("class-validator");
|
|
14
|
+
const CollectionResultLimits_1 = require("../constants/CollectionResultLimits");
|
|
14
15
|
/**
|
|
15
16
|
* El motor pregunta al dominio qué debe un titular ahora mismo.
|
|
16
17
|
*
|
|
17
18
|
* No lleva importe: el monto es justo lo que se está preguntando. Puede haber cambiado desde que el
|
|
18
19
|
* cargo se dio de alta —por mora, o porque el cliente pagó por otra vía— y sólo el dominio, que es
|
|
19
20
|
* quien aplica sus propias reglas, sabe cuánto queda vivo.
|
|
21
|
+
*
|
|
22
|
+
* Los tres campos son llave de búsqueda: vacíos o sin tope terminaban en 500 en el dominio (L-064).
|
|
20
23
|
*/
|
|
21
24
|
class ResolvePendingChargesRequest {
|
|
22
25
|
}
|
|
23
26
|
exports.ResolvePendingChargesRequest = ResolvePendingChargesRequest;
|
|
24
27
|
__decorate([
|
|
25
28
|
(0, class_validator_1.IsString)(),
|
|
29
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
30
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESOLVE_FIELD_MAX_LENGTH),
|
|
26
31
|
__metadata("design:type", String)
|
|
27
32
|
], ResolvePendingChargesRequest.prototype, "domain", void 0);
|
|
28
33
|
__decorate([
|
|
29
34
|
(0, class_validator_1.IsString)(),
|
|
35
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
36
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESOLVE_FIELD_MAX_LENGTH),
|
|
30
37
|
__metadata("design:type", String)
|
|
31
38
|
], ResolvePendingChargesRequest.prototype, "directoryId", void 0);
|
|
32
39
|
__decorate([
|
|
33
40
|
(0, class_validator_1.IsString)(),
|
|
41
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
42
|
+
(0, class_validator_1.MaxLength)(CollectionResultLimits_1.COLLECTION_RESOLVE_FIELD_MAX_LENGTH),
|
|
34
43
|
__metadata("design:type", String)
|
|
35
44
|
], ResolvePendingChargesRequest.prototype, "tenantId", void 0);
|
|
@@ -11,6 +11,8 @@ export * from './enums/RetryIntentStatus';
|
|
|
11
11
|
export * from './enums/AckIntentStatus';
|
|
12
12
|
export * from './enums/RedriveOutboxStatus';
|
|
13
13
|
export * from './enums/DisbursementType';
|
|
14
|
+
export * from './constants/CollectionResultLimits';
|
|
15
|
+
export * from './validators/IsCentPrecision';
|
|
14
16
|
export * from './dtos/PendingChargeDto';
|
|
15
17
|
export * from './dtos/CollectRequest';
|
|
16
18
|
export * from './dtos/CollectResponse';
|
package/bin/collection/index.js
CHANGED
|
@@ -28,6 +28,9 @@ __exportStar(require("./enums/RetryIntentStatus"), exports);
|
|
|
28
28
|
__exportStar(require("./enums/AckIntentStatus"), exports);
|
|
29
29
|
__exportStar(require("./enums/RedriveOutboxStatus"), exports);
|
|
30
30
|
__exportStar(require("./enums/DisbursementType"), exports);
|
|
31
|
+
// Constantes y validadores
|
|
32
|
+
__exportStar(require("./constants/CollectionResultLimits"), exports);
|
|
33
|
+
__exportStar(require("./validators/IsCentPrecision"), exports);
|
|
31
34
|
// DTOs
|
|
32
35
|
__exportStar(require("./dtos/PendingChargeDto"), exports);
|
|
33
36
|
__exportStar(require("./dtos/CollectRequest"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ValidationOptions } from 'class-validator';
|
|
2
|
+
/** `true` si el número no tiene fracción de centavo, salvo ruido de punto flotante. */
|
|
3
|
+
export declare const hasCentPrecision: (value: unknown) => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Monto con a lo más dos decimales (centavos).
|
|
6
|
+
*
|
|
7
|
+
* No se usa `@IsNumber({ maxDecimalPlaces: 2 })` porque cuenta los dígitos del `toString()`: rechaza
|
|
8
|
+
* `648.2299999999999`, que es `648.23` con ruido de una resta. El motor de cobro deja ese ruido al
|
|
9
|
+
* restar lo cobrado de lo pendiente, y rechazar un resultado con dinero ya cobrado lo manda a la DLQ.
|
|
10
|
+
*/
|
|
11
|
+
export declare const IsCentPrecision: (validationOptions?: ValidationOptions) => PropertyDecorator;
|