From 8722122da68ce79f8862c9a1e290f958eedf3c06 Mon Sep 17 00:00:00 2001 From: Vijay Kumar Date: Wed, 8 Sep 2021 18:19:47 +0530 Subject: [PATCH 1/5] Added mojaloop simulator sim API --- .../mojaloop_simulator_sim_1.4/api_spec.yaml | 1078 +++++++++++++++++ .../mojaloop_simulator_sim_1.4/mockRef.json | 79 ++ .../response_map.json | 37 + 3 files changed, 1194 insertions(+) create mode 100644 spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml create mode 100644 spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json create mode 100644 spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json diff --git a/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml b/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml new file mode 100644 index 00000000..045c9c2f --- /dev/null +++ b/spec_files/api_definitions/mojaloop_simulator_sim_1.4/api_spec.yaml @@ -0,0 +1,1078 @@ +openapi: 3.0.1 +info: + title: Mojaloop SDK Inbound Scheme Adapter API + description: Mojaloop SDK Inbound Scheme Adapter API - To be implemented by DFSP backend + license: + name: Open API for FSP Interoperability (FSPIOP) + url: http://www.majaloop.io + version: 1.0.0 + +paths: + /: + get: + summary: Health check endpoint. + operationId: healthCheck + responses: + 200: + description: Returns empty body if the service is running. + /participants/{idType}/{idValue}: + get: + summary: Asks for the FSPID of the scheme participant that can handle transfers for the specified identifier type and value + tags: + - Participants + operationId: ParticipantsGetByTypeAndID + parameters: + - name: idType + in: path + required: true + schema: + $ref: '#/components/schemas/idType' + - name: idValue + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + responses: + 200: + description: Response containing details of the requested party + content: + application/json: + schema: + $ref: '#/components/schemas/participantsResponse' + 404: + description: The party specified by the provided identifier type and value is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /participants/{idType}/{idValue}/{subIdValue}: + get: + summary: Asks for the FSPID of the scheme participant that can handle transfers for the specified identifier type, value and subId value + tags: + - Participants + operationId: ParticipantsGetByTypeIDAndSubId + parameters: + - name: idType + in: path + required: true + schema: + $ref: '#/components/schemas/idType' + - name: idValue + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + - name: subIdValue + in: path + required: true + schema: + $ref: '#/components/schemas/subIdValue' + responses: + 200: + description: Response containing details of the requested party + content: + application/json: + schema: + $ref: '#/components/schemas/participantsResponse' + 404: + description: The party specified by the provided identifier type and value/subId is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /parties/{idType}/{idValue}: + get: + summary: Requests information relating to a transfer party identified by the specified identifier type and value + tags: + - Parties + operationId: PartiesGetByTypeAndID + parameters: + - name: idType + in: path + required: true + schema: + $ref: '#/components/schemas/idType' + - name: idValue + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + responses: + 200: + description: Response containing details of the requested party + content: + application/json: + schema: + $ref: '#/components/schemas/transferParty' + 404: + description: The party specified by the provided identifier type and value is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /parties/{idType}/{idValue}/{subIdValue}: + get: + summary: Requests information relating to a transfer party identified by the specified identifier type, value and subId value + tags: + - Parties + operationId: PartiesGetByTypeIdAndSubId + parameters: + - name: idType + in: path + required: true + schema: + $ref: '#/components/schemas/idType' + - name: idValue + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + - name: subIdValue + in: path + required: true + schema: + $ref: '#/components/schemas/subIdValue' + responses: + 200: + description: Response containing details of the requested party + content: + application/json: + schema: + $ref: '#/components/schemas/transferParty' + 404: + description: The party specified by the provided identifier type and value is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /quoterequests: + post: + summary: Requests a quote for the specified transfer + tags: + - Quotes + operationId: QuoteRequest + requestBody: + description: Request for a transfer quotation + content: + application/json: + schema: + $ref: '#/components/schemas/quoteRequest' + responses: + 200: + description: A response to the transfer quotation request + content: + application/json: + schema: + $ref: '#/components/schemas/quoteResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /transactionrequests: + post: + summary: transaction request that supports pull based transfers + tags: + - TransactionRequest + operationId: TransactionRequest + requestBody: + description: Request for Transaction Request + content: + application/json: + schema: + $ref: '#/components/schemas/transactionRequest' + responses: + 200: + description: A response to the transfer transaction request + content: + application/json: + schema: + $ref: '#/components/schemas/transactionRequestResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /transfers: + post: + summary: Transfers funds from an external account to an internal account + tags: + - Transfers + operationId: TransfersPost + requestBody: + description: An incoming transfer request + content: + application/json: + schema: + $ref: '#/components/schemas/transferRequest' + responses: + 200: + description: The transfer was accepted + content: + application/json: + schema: + $ref: '#/components/schemas/transferResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /transfers/{transferId}: + put: + summary: Receive notification for a specific transfer + description: The HTTP request `PUT /transfers/{transferId}` is used to receive notification for transfer being fulfiled when the FSP is a Payee + parameters: + - name: transferId + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + tags: + - Transfers + operationId: TransfersPut + requestBody: + description: An incoming notification for fulfiled transfer + content: + application/json: + schema: + $ref: '#/components/schemas/fulfilNotification' + responses: + 200: + description: The notification was accepted + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + + /otp/{requestToPayId}: + get: + summary: Requests OTP + tags: + - OTP + operationId: OtpGet + parameters: + - name: requestToPayId + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + responses: + 200: + description: Response containing details of the OTP + content: + application/json: + schema: + $ref: '#/components/schemas/otpDetails' + 404: + description: The party specified by the provided identifier type and value is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /bulkQuotes: + post: + summary: Requests a bulk quote + tags: + - BulkQuotes + operationId: BulkQuotesPost + requestBody: + description: Incoming request for a bulk quotation + content: + application/json: + schema: + $ref: '#/components/schemas/bulkQuoteRequest' + responses: + 200: + description: A response to the bulk quote request + content: + application/json: + schema: + $ref: '#/components/schemas/bulkQuoteResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /bulkQuotes/{idValue}: + get: + summary: Requests information relating to a bulk quote identified by the specified identifier value + tags: + - BulkQuotes + operationId: BulkQuotesGet + parameters: + - name: idValue + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + responses: + 200: + description: Response containing details of the requested bulk quote + content: + application/json: + schema: + $ref: '#/components/schemas/bulkQuoteResponse' + 404: + description: The bulk quote specified by the provided identifier value is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /bulkTransfers: + post: + summary: Execute bulk transfer of funds from an external account to internal accounts + tags: + - BulkTransfers + operationId: BulkTransfersPost + requestBody: + description: An incoming bulk transfer request + content: + application/json: + schema: + $ref: '#/components/schemas/bulkTransferRequest' + responses: + 200: + description: The bulk transfer was accepted + content: + application/json: + schema: + $ref: '#/components/schemas/bulkTransferResponse' + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + + /bulkTransfers/{idValue}: + get: + summary: Requests information relating to a bulk transfer identified by the specified identifier value + tags: + - BulkTransfers + operationId: BulkTransfersGet + parameters: + - name: idValue + in: path + required: true + schema: + $ref: '#/components/schemas/idValue' + responses: + 200: + description: Response containing details of the requested bulk transfer + content: + application/json: + schema: + $ref: '#/components/schemas/bulkTransferResponse' + 404: + description: The bulk transfer specified by the provided identifier value is not known to the server + 400: + description: Malformed or missing required headers or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + 500: + description: An error occured processing the request + content: + application/json: + schema: + $ref: '#/components/schemas/errorResponse' + +components: + schemas: + quoteRequest: + type: object + description: A request for a quote for transfer from the DFSP backend + required: + - quoteId + - transactionId + - to + - from + - amountType + - amount + - currency + - transactionType + - initiator + - initiatorType + properties: + quoteId: + $ref: '#/components/schemas/quoteId' + transactionId: + $ref: '#/components/schemas/transactionId' + to: + $ref: '#/components/schemas/transferParty' + from: + $ref: '#/components/schemas/transferParty' + amountType: + $ref: '#/components/schemas/amountType' + amount: + $ref: '#/components/schemas/money' + currency: + $ref: '#/components/schemas/currency' + feesAmount: + $ref: '#/components/schemas/money' + feesCurrency: + $ref: '#/components/schemas/currency' + transactionType: + $ref: '#/components/schemas/transactionType' + initiator: + $ref: '#/components/schemas/initiator' + initiatorType: + $ref: '#/components/schemas/initiatorType' + geoCode: + $ref: '#/components/schemas/geoCode' + note: + type: string + minLength: 1 + maxLength: 128 + description: An optional note associated with the requested transfer + expiration: + $ref: '#/components/schemas/timestamp' + + transactionRequest: + type: object + description: A request for a pull based transfer + required: + - transactionRequestId + - to + - from + - amount + - currency + - transactionType + - initiator + - initiatorType + properties: + transactionRequestId: + $ref: '#/components/schemas/transactionRequestId' + to: + $ref: '#/components/schemas/transferParty' + from: + $ref: '#/components/schemas/transferParty' + amount: + $ref: '#/components/schemas/money' + currency: + $ref: '#/components/schemas/currency' + transactionType: + $ref: '#/components/schemas/transactionType' + initiator: + $ref: '#/components/schemas/initiator' + initiatorType: + $ref: '#/components/schemas/initiatorType' + geoCode: + $ref: '#/components/schemas/geoCode' + note: + type: string + minLength: 1 + maxLength: 128 + description: An optional note associated with the requested transfer + expiration: + $ref: '#/components/schemas/timestamp' + + timestamp: + type: string + description: An ISO-8601 formatted timestamp + pattern: ^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:(\.\d{3}))(?:Z|[+-][01]\d:[0-5]\d)$ + + dateOfBirth: + type: string + description: Date of birth in the form YYYY-MM-DD + pattern: ^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)$ + + initiator: + type: string + enum: + - PAYER + - PAYEE + + initiatorType: + type: string + enum: + - CONSUMER + - AGENT + - BUSINESS + - DEVICE + + quoteResponse: + type: object + description: A response to a request for a quote + required: + - quoteId + - transactionId + - transferAmount + - transferAmountCurrency + properties: + quoteId: + $ref: '#/components/schemas/quoteId' + transactionId: + $ref: '#/components/schemas/transactionId' + transferAmount: + $ref: '#/components/schemas/money' + transferAmountCurrency: + $ref: '#/components/schemas/currency' + payeeReceiveAmount: + $ref: '#/components/schemas/money' + payeeReceiveAmountCurrency: + $ref: '#/components/schemas/currency' + payeeFspFeeAmount: + $ref: '#/components/schemas/money' + payeeFspFeeAmountCurrency: + $ref: '#/components/schemas/currency' + payeeFspCommissionAmount: + $ref: '#/components/schemas/money' + payeeFspCommissionAmountCurrency: + $ref: '#/components/schemas/currency' + expiration: + $ref: '#/components/schemas/timestamp' + geoCode: + $ref: '#/components/schemas/geoCode' + extensionList: + $ref: '#/components/schemas/extensionList' + + transactionRequestResponse: + type: object + description: A response to a request for a quote + required: + - transactionId + - transactionRequestState + properties: + transactionId: + $ref: '#/components/schemas/transactionId' + transferAmount: + $ref: '#/components/schemas/transactionRequestState' + + participantsResponse: + type: object + properties: + fspId: + $ref: '#/components/schemas/fspId' + + fspId: + type: string + minLength: 1 + maxLength: 32 + + payerType: + type: string + enum: + - CONSUMER + - AGENT + - BUSINESS + - DEVICE + + amountType: + type: string + enum: + - SEND + - RECEIVE + + transactionType: + type: string + enum: + - TRANSFER + - DEPOSIT + - PAYMENT + + transactionRequestState: + type: string + enum: + - RECEIVED + - PENDING + - ACCEPTED + - REJECTED + + transferRequest: + type: object + required: + - transferId + - currency + - amount + properties: + transferId: + $ref: '#/components/schemas/transferId' + quote: + $ref: '#/components/schemas/quoteResponse' + from: + $ref: '#/components/schemas/transferParty' + to: + $ref: '#/components/schemas/transferParty' + amountType: + $ref: '#/components/schemas/amountType' + currency: + $ref: '#/components/schemas/currency' + amount: + $ref: '#/components/schemas/money' + transactionType: + $ref: '#/components/schemas/transactionType' + note: + maxLength: 128 + type: string + + otpDetails: + type: object + required: + - otpValue + properties: + otpValue: + type: string + description: OTP value + + idType: + type: string + enum: + - MSISDN + - ACCOUNT_NO + - EMAIL + - PERSONAL_ID + - BUSINESS + - DEVICE + - ACCOUNT_ID + - IBAN + - ALIAS + + idValue: + type: string + minLength: 1 + maxLength: 128 + + subIdValue: + type: string + minLength: 1 + maxLength: 128 + + money: + pattern: ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + type: string + + transferResponse: + type: object + required: + - homeTransactionId + properties: + homeTransactionId: + type: string + description: Transaction ID from the DFSP backend, used to reconcile transactions between the switch and DFSP backend systems + + currency: + maxLength: 3 + minLength: 3 + type: string + + transferId: + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + type: string + description: A Mojaloop API transfer identifier (UUID) + + quoteId: + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + type: string + description: A Mojaloop API quote identifier (UUID) + + transactionRequestId: + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + type: string + description: A Mojaloop API transaction request identifier (UUID) + + transactionId: + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + type: string + description: ID of the transaction, the ID is decided by the Payer FSP during the creation of the quote + + transferParty: + type: object + required: + - idType + - idValue + properties: + type: + $ref: '#/components/schemas/payerType' + idType: + $ref: '#/components/schemas/idType' + idValue: + type: string + description: The identifier string used to identify the sender + subIdValue: + type: string + description: The sub identifier string used to identify the sender + displayName: + type: string + description: Display name of the sender if known + firstName: + type: string + description: Party first name + middleName: + type: string + description: Party moddle name + lastName: + type: string + description: Party last name + dateOfBirth: + $ref: '#/components/schemas/dateOfBirth' + merchantClassificationCode: + type: string + description: Up to 4 digits specifying the senders merchant classification, if known and applicable + + bulkQuoteRequest: + type: object + description: A request for a bulk quote + required: + - bulkQuoteId + - from + - individualQuotes + properties: + bulkQuoteId: + $ref: '#/components/schemas/bulkQuoteId' + from: + $ref: '#/components/schemas/transferParty' + geoCode: + $ref: '#/components/schemas/geoCode' + expiration: + $ref: '#/components/schemas/timestamp' + individualQuotes: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/IndividualQuote' + + bulkQuoteResponse: + type: object + description: A response to a request for a bulk quote + required: + - bulkQuoteId + - individualQuoteResults + properties: + bulkQuoteId: + $ref: '#/components/schemas/bulkQuoteId' + expiration: + $ref: '#/components/schemas/timestamp' + individualQuoteResults: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/IndividualQuoteResult' + description: Fees for each individual transaction, if any of them are charged per + transaction. + + IndividualQuote: + type: object + description: Data model for individual quote in a bulk quote request + required: + - quoteId + - transactionId + - to + - amountType + - amount + - currency + - transactionType + - initiator + - initiatorType + properties: + quoteId: + $ref: '#/components/schemas/quoteId' + transactionId: + $ref: '#/components/schemas/transactionId' + to: + $ref: '#/components/schemas/transferParty' + amountType: + $ref: '#/components/schemas/amountType' + amount: + $ref: '#/components/schemas/money' + currency: + $ref: '#/components/schemas/currency' + feesAmount: + $ref: '#/components/schemas/money' + feesCurrency: + $ref: '#/components/schemas/currency' + transactionType: + $ref: '#/components/schemas/transactionType' + initiator: + $ref: '#/components/schemas/initiator' + initiatorType: + $ref: '#/components/schemas/initiatorType' + note: + type: string + minLength: 1 + maxLength: 128 + description: An optional note associated with the quote + + IndividualQuoteResult: + type: object + description: Data model for individual quote in a bulk quote response + properties: + quoteId: + $ref: '#/components/schemas/quoteId' + transferAmount: + $ref: '#/components/schemas/money' + transferAmountCurrency: + $ref: '#/components/schemas/currency' + payeeReceiveAmount: + $ref: '#/components/schemas/money' + payeeReceiveAmountCurrency: + $ref: '#/components/schemas/currency' + payeeFspFeeAmount: + $ref: '#/components/schemas/money' + payeeFspFeeAmountCurrency: + $ref: '#/components/schemas/currency' + payeeFspCommissionAmount: + $ref: '#/components/schemas/money' + payeeFspCommissionAmountCurrency: + $ref: '#/components/schemas/currency' + + bulkQuoteId: + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + type: string + description: A Mojaloop API bulk quote identifier (UUID) + + bulkTransferRequest: + type: object + required: + - bulkTransferId + - individualTransfers + properties: + bulkTransferId: + $ref: '#/components/schemas/bulkTransferId' + bulkQuote: + $ref: '#/components/schemas/bulkQuoteResponse' + from: + $ref: '#/components/schemas/transferParty' + individualTransfers: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/IndividualTransfer' + + IndividualTransfer: + type: object + description: Data model for individual transfer in a bulk transfer request + required: + - transferId + - amount + - currency + properties: + transferId: + $ref: '#/components/schemas/transferId' + to: + $ref: '#/components/schemas/transferParty' + amountType: + $ref: '#/components/schemas/amountType' + amount: + $ref: '#/components/schemas/money' + currency: + $ref: '#/components/schemas/currency' + feesAmount: + $ref: '#/components/schemas/money' + feesCurrency: + $ref: '#/components/schemas/currency' + transactionType: + $ref: '#/components/schemas/transactionType' + initiator: + $ref: '#/components/schemas/initiator' + initiatorType: + $ref: '#/components/schemas/initiatorType' + note: + type: string + minLength: 1 + maxLength: 128 + description: An optional note associated with the quote + + bulkTransferResponse: + type: object + required: + - homeTransactionId + properties: + bulkTransferId: + $ref: '#/components/schemas/bulkTransferId' + homeTransactionId: + type: string + description: Transaction ID from the DFSP backend, used to reconcile transactions between the switch and DFSP backend systems + from: + $ref: '#/components/schemas/transferParty' + individualTransfers: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: '#/components/schemas/IndividualTransfer' + + bulkTransferId: + pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + type: string + description: A Mojaloop API transfer identifier (UUID) + + geoCode: + type: object + description: Indicates the geographic location from where the transaction was initiated. + properties: + latitude: + $ref: '#/components/schemas/latitude' + longitude: + $ref: '#/components/schemas/longitude' + required: + - latitude + - longitude + + latitude: + type: string + pattern: ^(\+|-)?(?:90(?:(?:\.0{1,6})?)|(?:[0-9]|[1-8][0-9])(?:(?:\.[0-9]{1,6})?))$ + description: The API data type Latitude is a JSON String in a lexical format that is restricted by a regular expression for interoperability reasons. + + longitude: + type: string + pattern: ^(\+|-)?(?:180(?:(?:\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\.[0-9]{1,6})?))$ + description: The API data type Longitude is a JSON String in a lexical format that is restricted by a regular expression for interoperability reasons. + + errorResponse: + type: object + properties: + statusCode: + type: string + description: Error code as string + message: + type: string + description: Error message text + + extensionList: + type: array + items: + $ref: '#/components/schemas/extensionItem' + minItems: 0 + maxItems: 16 + + extensionItem: + type: object + properties: + key: + type: string + minLength: 1 + maxLength: 32 + value: + type: string + minLength: 1 + maxLength: 128 + + transferState: + type: string + enum: + - RECEIVED + - RESERVED + - COMMITTED + - ABORTED + description: > + Below are the allowed values for the enumeration + - RECEIVED DFSP has received the transfer. + - RESERVED DFSP has reserved the transfer. + - COMMITTED DFSP has successfully performed the transfer. + - ABORTED DFSP has aborted the transfer due a rejection or failure to perform the transfer. + + fulfilNotification: + title: TransfersIDPatchResponse + type: object + description: PUT /transfers/{transferId} object + properties: + completedTimestamp: + $ref: '#/components/schemas/timestamp' + transferState: + $ref: '#/components/schemas/transferState' + extensionList: + $ref: '#/components/schemas/extensionList' + required: + - completedTimestamp + - transferState + diff --git a/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json b/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json new file mode 100644 index 00000000..fe3a4780 --- /dev/null +++ b/spec_files/api_definitions/mojaloop_simulator_sim_1.4/mockRef.json @@ -0,0 +1,79 @@ +[ + { + "id": "firstName", + "pattern": "John|David|Michael|Chris|Mike|Mark|Paul|Daniel|James|Maria" + }, + { + "id": "middleName", + "pattern": "G|P|N|S" + }, + { + "id": "lastName", + "pattern": "Smith|Jones|Johnson|Lee|Brown|Williams|Rodriguez|Garcia|Gonzalez|Lopez" + }, + { + "id": "dateOfBirth", + "pattern": "^(19)\\d\\d[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|2[0-8])$" + }, + { + "id": "transferState", + "pattern": "COMMITTED|RESERVED|ABORTED|RECEIVED" + }, + { + "id": "fulfilment", + "pattern": "[A-Fa-f0-9]{64}" + }, + { + "id": "condition", + "pattern": "[A-Fa-f0-9]{64}" + }, + { + "id": "ilpPacket", + "pattern": "[A-Fa-f0-9]{256}" + }, + { + "id": "transferAmount.currency", + "pattern": "USD" + }, + { + "id": "transferAmount.amount", + "pattern": "123" + }, + { + "id": "payeeReceiveAmountCurrency", + "pattern": "USD" + }, + { + "id": "payeeReceiveAmount", + "pattern": "123" + }, + { + "id": "payeeFspFeeAmountCurrency", + "pattern": "USD" + }, + { + "id": "payeeFspFeeAmount", + "pattern": "2" + }, + { + "id": "payeeFspCommissionAmountCurrency", + "pattern": "USD" + }, + { + "id": "payeeFspCommissionAmount", + "pattern": "3" + }, + + { + "id": "errorInformation.errorCode", + "pattern": "600[1-9]" + }, + { + "id": "errorInformation.errorDescription", + "pattern": "This is a mock error description" + }, + { + "id": "Content-Length", + "pattern": "123" + } +] \ No newline at end of file diff --git a/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json b/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json new file mode 100644 index 00000000..d7eba90a --- /dev/null +++ b/spec_files/api_definitions/mojaloop_simulator_sim_1.4/response_map.json @@ -0,0 +1,37 @@ +{ + "/parties/{idType}/{idValue}": { + "get": { + "response": { + "bodyOverride": { + "idType": "{$request.params.idType}", + "idValue": "{$request.params.idValue}", + "merchantClassificationCode": null + } + } + } + }, + "/quoterequests": { + "post": { + "response": { + "bodyOverride": { + "quoteId": "{$request.body.quoteId}", + "transactionId": "{$request.body.transactionId}", + "transferAmount": "{$request.body.amount}", + "transferAmountCurrency": "{$request.body.currency}", + "expiration": "{$request.body.expiration}", + "geoCode": null, + "extensionList": null + } + } + } + }, + "/transfers": { + "post": { + "response": { + "bodyOverride": { + "homeTransactionId": "{$request.body.transferId}" + } + } + } + } +} \ No newline at end of file From e2d86e568b8a33625121ff9dfc5cd81af7d1b35b Mon Sep 17 00:00:00 2001 From: Vijay Kumar Date: Wed, 8 Sep 2021 18:29:12 +0530 Subject: [PATCH 2/5] Added custom.pushMessage functionality to inbound scripting --- src/lib/notificationEmitter.js | 7 +++++- .../vm-javascript-sandbox.js | 5 ++++ test/unit/lib/notificationEmitter.test.js | 8 ++++++ .../vm-javascript-sandbox.test.js | 25 +++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/lib/notificationEmitter.js b/src/lib/notificationEmitter.js index f4c18e62..db8303ae 100644 --- a/src/lib/notificationEmitter.js +++ b/src/lib/notificationEmitter.js @@ -38,6 +38,10 @@ const broadcastLog = (log, sessionID = null) => { broadcast(log, sessionID, 'newLog') } +const sendMessage = (message, sessionID = null) => { + broadcast(message, sessionID, 'pushMessage') +} + const broadcastOutboundLog = (log, sessionID = null) => { broadcast(log, sessionID, 'newOutboundLog') } @@ -50,5 +54,6 @@ const broadcastOutboundProgress = (status, sessionID = null) => { module.exports = { broadcastLog, broadcastOutboundLog, - broadcastOutboundProgress + broadcastOutboundProgress, + sendMessage } diff --git a/src/lib/scripting-engines/vm-javascript-sandbox.js b/src/lib/scripting-engines/vm-javascript-sandbox.js index b255368f..b3a54fc3 100644 --- a/src/lib/scripting-engines/vm-javascript-sandbox.js +++ b/src/lib/scripting-engines/vm-javascript-sandbox.js @@ -27,6 +27,7 @@ const axiosModule = require('axios').default const atob = require('atob') const WebSocketClientManager = require('../webSocketClient/WebSocketClientManager').WebSocketClientManager const InboundEventListener = require('../eventListenerClient/inboundEventListener').InboundEventListener +const notificationEmitter = require('../notificationEmitter') const JwsSigning = require('../jws/JwsSigning') const Config = require('../config') const httpAgentStore = require('../httpAgentStore') @@ -123,6 +124,10 @@ const customWrapperFn = (requestVariables) => { }, skipRequest: function () { requestVariables.SKIP_REQUEST = true + }, + pushMessage: function (message, sessionID = null) { + customLogger.logMessage('info', 'Sending Push Message to topic pushMessage' + (sessionID ? '/' + sessionID : ''), { additionalData: { message }, notification: false }) + notificationEmitter.sendMessage(message, sessionID) } } } diff --git a/test/unit/lib/notificationEmitter.test.js b/test/unit/lib/notificationEmitter.test.js index 225b804a..7572856a 100644 --- a/test/unit/lib/notificationEmitter.test.js +++ b/test/unit/lib/notificationEmitter.test.js @@ -51,4 +51,12 @@ describe('NotificationEmitter', () => { expect(() => NotificationEmitter.broadcastOutboundProgress({}, 'sessionID')).not.toThrowError() }) }) + describe('sendMessage', () => { + it('should not throw an error when sessionID is missing', () => { + expect(() => NotificationEmitter.sendMessage({})).not.toThrowError() + }) + it('should not throw an error', () => { + expect(() => NotificationEmitter.sendMessage({}, 'sessionID')).not.toThrowError() + }) + }) }) diff --git a/test/unit/lib/scripting-engines/vm-javascript-sandbox.test.js b/test/unit/lib/scripting-engines/vm-javascript-sandbox.test.js index 5f3d9537..28fd8faf 100644 --- a/test/unit/lib/scripting-engines/vm-javascript-sandbox.test.js +++ b/test/unit/lib/scripting-engines/vm-javascript-sandbox.test.js @@ -27,11 +27,14 @@ const uuid = require('uuid') const axios = require('axios').default const JwsSigning = require('../../../../src/lib/jws/JwsSigning') +const NotificationEmitter = require('../../../../src/lib/notificationEmitter') jest.mock('axios') axios.create.mockImplementation((config) => axios) jest.mock('../../../../src/lib/jws/JwsSigning') const Context = require('../../../../src/lib/scripting-engines/vm-javascript-sandbox') +const spyNotificationEmitterSendMessage = jest.spyOn(NotificationEmitter, 'sendMessage') + describe('Test Outbound Context', () => { describe('generateContextObj', () => { @@ -256,6 +259,28 @@ describe('Test Outbound Context', () => { }) + it('executeAsync should call custom.pushMessage function', async () => { + + const contextObj = await Context.generateContextObj({}) + + const args = { + script: [ + "await custom.pushMessage({})", + ], + data: { context: {...contextObj}, id: uuid.v4()}, + contextObj: contextObj + } + + let scriptResult + try { + scriptResult = await Context.executeAsync(args.script, args.data, args.contextObj) + } finally { + contextObj.ctx.dispose() + contextObj.ctx = null + } + expect(spyNotificationEmitterSendMessage).toBeCalled() + }) + it('executeAsync should call custom.setRequestTimeout function', async () => { const contextObj = await Context.generateContextObj({}) From a9f336debd00357e50b60e7742b0d60a7def0527 Mon Sep 17 00:00:00 2001 From: Vijay Kumar Date: Wed, 8 Sep 2021 18:36:55 +0530 Subject: [PATCH 3/5] Added documentation about the pushMessage custom function --- .../User-Guide-Mojaloop-Testing-Toolkit.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/documents/User-Guide-Mojaloop-Testing-Toolkit.md b/documents/User-Guide-Mojaloop-Testing-Toolkit.md index 11432ac9..47e9d24f 100644 --- a/documents/User-Guide-Mojaloop-Testing-Toolkit.md +++ b/documents/User-Guide-Mojaloop-Testing-Toolkit.md @@ -468,7 +468,25 @@ You can write scripts in two formats. - **custom.skipRequest** - function By using this function in the pre-request script, you can skip the current request including post-request-scripts and assertions. You can see the request and assertions as skipped in the UI and in the report as well. + + - _**custom.pushMessage(message, [sessionID])**_ + + By using this function in the scripts in rules for inbound requests, you can push a websocket message to the clients listening on the websocket server of TTK and on topic 'pushMessage'. There is an optional sessionId that we can pass as second argument to this function + + Parameters: + - **message** - The message object to emit to the clients. + + Type: [`Object`] + - **sessionID** - Optional sessionID to send message to targetted clients who are listening on the topic 'pushMessage/' + + Type: [`string`] + + Example usage: + ```javascript + await custom.pushMessage({ name: 'Sample Name' }) + await custom.pushMessage({ name: 'Sample Name' }, 'client1') + ``` ![Sample Pre Request and Post Request Scripts](/assets/images/test-case-editor-scripts.png) From eee206dc3ac6a7fdf8bbefb9cd6dcb22578420c1 Mon Sep 17 00:00:00 2001 From: Vijay Kumar Date: Wed, 8 Sep 2021 18:38:08 +0530 Subject: [PATCH 4/5] Bumped up the version and postponed the audits --- audit-resolve.json | 592 ++++++++++++++++++++++----------------------- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 298 insertions(+), 298 deletions(-) diff --git a/audit-resolve.json b/audit-resolve.json index 67ae9ed3..52fd37d9 100644 --- a/audit-resolve.json +++ b/audit-resolve.json @@ -126,79 +126,79 @@ }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1670|hapi-swagger>handlebars": { "decision": "fix", @@ -206,7 +206,7 @@ }, "1673|ml-testing-toolkit-shared-lib>lodash": { "decision": "postpone", - "madeAt": 1630669412636 + "madeAt": 1631106466423 }, "1674|jsdoc>underscore": { "decision": "fix", @@ -214,295 +214,295 @@ }, "1675|postman-collection>sanitize-html": { "decision": "postpone", - "madeAt": 1630669407488 + "madeAt": 1631106462461 }, "1676|postman-collection>sanitize-html": { "decision": "postpone", - "madeAt": 1630669407488 + "madeAt": 1631106462461 }, "1677|npm-run-all>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>@jest/core>@jest/reporters>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>@jest/core>@jest/reporters>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463003 }, "1677|jest>jest-cli>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-resolve-dependencies>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runner>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>@jest/core>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|jest>jest-cli>@jest/core>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1677|standard>eslint-plugin-import>read-pkg-up>read-pkg>normalize-package-data>hosted-git-info": { "decision": "postpone", - "madeAt": 1630669408228 + "madeAt": 1631106463004 }, "1681|json-ref-lite>property-expr": { "decision": "postpone", - "madeAt": 1630669413284 + "madeAt": 1631106467008 }, "1693|postman-collection>sanitize-html>postcss": { "decision": "postpone", - "madeAt": 1630669408856 + "madeAt": 1631106463678 }, "1748|ws": { "decision": "fix", @@ -914,7 +914,7 @@ }, "1748|socket.io>engine.io>ws": { "decision": "postpone", - "madeAt": 1630669409469 + "madeAt": 1631106464184 }, "1748|socket.io>socket.io-client>engine.io-client>ws": { "decision": "fix", @@ -970,811 +970,811 @@ }, "1755|nodemon>update-notifier>latest-version>package-json>got>cacheable-request>normalize-url": { "decision": "postpone", - "madeAt": 1630669410077 + "madeAt": 1631106464600 }, "1755|npm-check-updates>update-notifier>latest-version>package-json>got>cacheable-request>normalize-url": { "decision": "postpone", - "madeAt": 1630669410077 + "madeAt": 1631106464600 }, "1770|npm-check-updates>pacote>@npmcli/run-script>node-gyp>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1770|npm-check-updates>pacote>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1770|npm-check-updates>pacote>npm-registry-fetch>make-fetch-happen>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1770|npm-check-updates>pacote>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1771|npm-check-updates>pacote>@npmcli/run-script>node-gyp>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1771|npm-check-updates>pacote>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1771|npm-check-updates>pacote>npm-registry-fetch>make-fetch-happen>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1771|npm-check-updates>pacote>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1772|npm-check-updates>pacote>@npmcli/git": { "decision": "postpone", - "madeAt": 1630669411310 + "madeAt": 1631106465432 }, "1779|npm-check-updates>pacote>@npmcli/run-script>node-gyp>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1779|npm-check-updates>pacote>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1779|npm-check-updates>pacote>npm-registry-fetch>make-fetch-happen>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1779|npm-check-updates>pacote>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1780|npm-check-updates>pacote>@npmcli/run-script>node-gyp>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1780|npm-check-updates>pacote>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1780|npm-check-updates>pacote>npm-registry-fetch>make-fetch-happen>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1780|npm-check-updates>pacote>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1781|npm-check-updates>pacote>@npmcli/run-script>node-gyp>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1781|npm-check-updates>pacote>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1781|npm-check-updates>pacote>npm-registry-fetch>make-fetch-happen>cacache>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1781|npm-check-updates>pacote>tar": { "decision": "postpone", - "madeAt": 1630669410685 + "madeAt": 1631106465005 }, "1773|npm-run-all>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>@jest/reporters>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>@jest/reporters>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>jest-config>jest-jasmine2>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>jest-cli>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465937 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-resolve-dependencies>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-snapshot>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-resolve>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|standard>eslint-plugin-import>read-pkg-up>read-pkg>normalize-package-data>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>@jest/reporters>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>@jest/reporters>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411989 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>jest-jasmine2>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>jest-jasmine2>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>jest-jasmine2>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-resolve-dependencies>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-resolve-dependencies>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-snapshot>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>@jest/test-sequencer>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-jasmine2>jest-runtime>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runner>jest-runtime>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runner>jest-runtime>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-runtime>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-runtime>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>jest-config>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>@jest/core>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|jest>jest-cli>@jest/core>jest-resolve>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|proxyquire>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|standard>eslint-plugin-import>eslint-import-resolver-node>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|standard>eslint-plugin-import>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|standard>eslint-plugin-node>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 }, "1773|standard>eslint-plugin-react>resolve>path-parse": { "decision": "postpone", - "madeAt": 1630669411990 + "madeAt": 1631106465938 } }, "rules": {}, diff --git a/package-lock.json b/package-lock.json index 5c935a82..1c01b9ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ml-testing-toolkit", - "version": "13.2.0", + "version": "13.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ba0d970c..62dadf6c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ml-testing-toolkit", "description": "Testing Toolkit for Mojaloop implementations", - "version": "13.2.0", + "version": "13.3.0", "license": "Apache-2.0", "author": "Vijaya Kumar Guthi, ModusBox Inc. ", "contributors": [ From db9e1c87b2ed9bf5627f574b1f7de897c0a617d8 Mon Sep 17 00:00:00 2001 From: Vijay Kumar Date: Thu, 23 Sep 2021 16:04:28 +0530 Subject: [PATCH 5/5] Refactored test case execution to use worker threads. Still need to take care of many functionalitites like notifications, inboundEvent, websocket ...etc --- src/lib/test-outbound/outbound-initiator.js | 908 +++----------------- src/lib/test-outbound/testrunner-worker.js | 859 ++++++++++++++++++ 2 files changed, 982 insertions(+), 785 deletions(-) create mode 100644 src/lib/test-outbound/testrunner-worker.js diff --git a/src/lib/test-outbound/outbound-initiator.js b/src/lib/test-outbound/outbound-initiator.js index c0ebaa84..a69fdec3 100644 --- a/src/lib/test-outbound/outbound-initiator.js +++ b/src/lib/test-outbound/outbound-initiator.js @@ -47,6 +47,11 @@ const dbAdapter = require('../db/adapters/dbAdapter') const arrayStore = require('../arrayStore') const UniqueIdGenerator = require('../../lib/uniqueIdGenerator') const httpAgentStore = require('../httpAgentStore') +const path = require('path') + +const { + Worker, isMainThread, parentPort, workerData, MessageChannel +} = require('worker_threads'); var terminateTraceIds = {} @@ -66,805 +71,138 @@ const getTracing = (traceID, dfspId) => { } const OutboundSend = async (inputTemplate, traceID, dfspId) => { - const totalCounts = getTotalCounts(inputTemplate) - const globalConfig = { - broadcastOutboundProgressEnabled: true, - scriptExecution: true, - testsExecution: true, - totalProgress: { - testCasesTotal: totalCounts.totalTestCases, - testCasesProcessed: 0, - requestsTotal: totalCounts.totalRequests, - requestsProcessed: 0, - assertionsTotal: totalCounts.totalAssertions, - assertionsProcessed: 0, - assertionsPassed: 0, - assertionsFailed: 0 - } - } - - const startedTimeStamp = new Date() - const tracing = getTracing(traceID, dfspId) - - const variableData = { - environment: { ...inputTemplate.inputValues } - } - try { - for (const i in inputTemplate.test_cases) { - globalConfig.totalProgress.testCasesProcessed++ - await processTestCase(inputTemplate.test_cases[i], traceID, inputTemplate.inputValues, variableData, dfspId, globalConfig) - } - - const completedTimeStamp = new Date() - const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime() - // Send the total result to client - if (tracing.outboundID) { - const runtimeInformation = { - completedTimeISO: completedTimeStamp.toISOString(), - startedTime: startedTimeStamp.toUTCString(), - completedTime: completedTimeStamp.toUTCString(), - runDurationMs: runDurationMs, - avgResponseTime: 'NA', - totalAssertions: 0, - totalPassedAssertions: 0 - } - const totalResult = generateFinalReport(inputTemplate, runtimeInformation) - if (Config.getSystemConfig().HOSTING_ENABLED) { - const totalResultCopy = JSON.parse(JSON.stringify(totalResult)) - totalResultCopy.runtimeInformation.completedTimeISO = completedTimeStamp - dbAdapter.upsert('reports', totalResultCopy, { dfspId }) - } - notificationEmitter.broadcastOutboundProgress({ - status: 'FINISHED', - outboundID: tracing.outboundID, - totalResult - }, tracing.sessionID) - } - } catch (err) { - console.log(err) - notificationEmitter.broadcastOutboundProgress({ - status: 'TERMINATED', - outboundID: tracing.outboundID - }, tracing.sessionID) - } -} - -const OutboundSendLoop = async (inputTemplate, traceID, dfspId, iterations) => { - const totalCounts = getTotalCounts(inputTemplate) - - const globalConfig = { - broadcastOutboundProgressEnabled: false, - scriptExecution: true, - testsExecution: true, - totalProgress: { - testCasesTotal: totalCounts.totalTestCases, - testCasesProcessed: 0, - requestsTotal: totalCounts.totalRequests, - requestsProcessed: 0, - assertionsTotal: totalCounts.totalAssertions, - assertionsProcessed: 0, - assertionsPassed: 0, - assertionsFailed: 0 - } - } - const tracing = getTracing(traceID, dfspId) - - const environmentVariables = { ...inputTemplate.inputValues } - try { - const totalStartedTimeStamp = new Date() - const totalReport = { - iterations: [] - } - for (let itn = 0; itn < iterations; itn++) { - const startedTimeStamp = new Date() - // Deep copy the template - const tmpTemplate = JSON.parse(JSON.stringify(inputTemplate)) - // Execute all the test cases in the template - for (const i in tmpTemplate.test_cases) { - await processTestCase(tmpTemplate.test_cases[i], traceID, tmpTemplate.inputValues, environmentVariables, dfspId, globalConfig) - } - const completedTimeStamp = new Date() - const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime() - const runtimeInformation = { - iterationNumber: itn, - completedTimeISO: completedTimeStamp.toISOString(), - startedTime: startedTimeStamp.toUTCString(), - completedTime: completedTimeStamp.toUTCString(), - runDurationMs: runDurationMs, - totalAssertions: 0, - totalPassedAssertions: 0 - } - // TODO: This can be optimized by storing only results into the iterations array - totalReport.iterations.push(generateFinalReport(tmpTemplate, runtimeInformation)) - notificationEmitter.broadcastOutboundProgress({ - status: 'ITERATION_PROGRESS', - outboundID: tracing.outboundID, - iterationStatus: runtimeInformation - }, tracing.sessionID) - } - - const totalCompletedTimeStamp = new Date() - const totalRunDurationMs = totalCompletedTimeStamp.getTime() - totalStartedTimeStamp.getTime() - // Send the total result to client - if (tracing.outboundID) { - notificationEmitter.broadcastOutboundProgress({ - status: 'ITERATIONS_FINISHED', - outboundID: tracing.outboundID, - totalRunDurationMs, - totalReport - }, tracing.sessionID) - } - } catch (err) { - notificationEmitter.broadcastOutboundProgress({ - status: 'ITERATIONS_TERMINATED', - outboundID: tracing.outboundID, - errorMessage: err.message - }, tracing.sessionID) - } -} - -const terminateOutbound = (traceID) => { - terminateTraceIds[traceID] = true -} - -const processTestCase = async (testCase, traceID, inputValues, variableData, dfspId, globalConfig) => { - const tracing = getTracing(traceID) - - // Load the requests array into an object by the request id to access a particular object faster - const requestsObj = {} - // Store the request ids into a new array - const templateIDArr = [] - for (const i in testCase.requests) { - requestsObj[testCase.requests[i].id] = testCase.requests[i] - templateIDArr.push(testCase.requests[i].id) - } - // Sort the request ids array - templateIDArr.sort((a, b) => { - return a > b - }) const apiDefinitions = await openApiDefinitionsModel.getApiDefinitions() - // Iterate the request ID array - for (const i in templateIDArr) { - if (terminateTraceIds[traceID]) { - delete terminateTraceIds[traceID] - throw new Error('Terminated') - } - const request = requestsObj[templateIDArr[i]] - - const reqApiDefinition = apiDefinitions.find((item) => { - return ( - item.majorVersion === +request.apiVersion.majorVersion && - item.minorVersion === +request.apiVersion.minorVersion && - item.type === request.apiVersion.type - ) - }) - - if (request.delay) { - await new Promise(resolve => setTimeout(resolve, request.delay)) - } - - let convertedRequest = JSON.parse(JSON.stringify(request)) - - // Form the actual http request headers, body, path and method by replacing configurable parameters - // Replace the parameters - convertedRequest = replaceVariables(request, inputValues, request, requestsObj) - convertedRequest = replaceRequestVariables(convertedRequest) - - // Form the path from params and operationPath - convertedRequest.path = replacePathVariables(request.operationPath, convertedRequest.params) - - // Insert traceparent header if sessionID passed - if (tracing.sessionID) { - convertedRequest.headers = convertedRequest.headers || {} - convertedRequest.headers.traceparent = '00-' + traceID + '-0123456789abcdef0-00' - } - - const scriptsExecution = {} - let contextObj = null - if (globalConfig.scriptExecution) { - let context = postmanContext - if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') { - context = javascriptContext - } - contextObj = await context.generateContextObj(variableData.environment) - } - - // Send http request - try { - // Extra step to access request variables that consists of environment variables in scripts - const tmpRequest = replaceEnvironmentVariables(convertedRequest, variableData.environment) - if (globalConfig.scriptExecution) { - await executePreRequestScript(tmpRequest, scriptsExecution, contextObj, variableData) - } - - convertedRequest = replaceEnvironmentVariables(convertedRequest, variableData.environment) - convertedRequest = replaceRequestLevelEnvironmentVariables(convertedRequest, contextObj.requestVariables) - - let successCallbackUrl = null - let errorCallbackUrl = null - if (request.apiVersion.asynchronous === true) { - const cbMapRawdata = await readFileAsync(reqApiDefinition.callbackMapFile) - const reqCallbackMap = JSON.parse(cbMapRawdata) - if (reqCallbackMap[request.operationPath] && reqCallbackMap[request.operationPath][request.method]) { - const successCallback = reqCallbackMap[request.operationPath][request.method].successCallback - const errorCallback = reqCallbackMap[request.operationPath][request.method].errorCallback - successCallbackUrl = successCallback.method + ' ' + replaceVariables(successCallback.pathPattern, null, convertedRequest) - errorCallbackUrl = errorCallback.method + ' ' + replaceVariables(errorCallback.pathPattern, null, convertedRequest) - } - } - if (contextObj.requestVariables && contextObj.requestVariables.SKIP_REQUEST) { - await setSkippedResponse(convertedRequest, request, 'SKIPPED', tracing, testCase, scriptsExecution, globalConfig) - } else { - const resp = await sendRequest(convertedRequest.url, convertedRequest.method, convertedRequest.path, convertedRequest.queryParams, convertedRequest.headers, convertedRequest.body, successCallbackUrl, errorCallbackUrl, convertedRequest.ignoreCallbacks, dfspId, contextObj) - await setResponse(convertedRequest, resp, variableData, request, 'SUCCESS', tracing, testCase, scriptsExecution, contextObj, globalConfig) - } - } catch (err) { - let resp - try { - resp = JSON.parse(err.message) - } catch (parsingErr) { - resp = err.message - } - await setResponse(convertedRequest, resp, variableData, request, 'ERROR', tracing, testCase, scriptsExecution, contextObj, globalConfig) - } finally { - if (contextObj) { - contextObj.ctx.dispose() - contextObj.ctx = null - } - } - } - - // Return status report of this test case - return testCase - // Set a timeout if the response callback is not received in a particular time -} - -const setResponse = async (convertedRequest, resp, variableData, request, status, tracing, testCase, scriptsExecution, contextObj, globalConfig) => { - // Get the requestsHistory and callbacksHistory from the arrayStore - const requestsHistoryObj = arrayStore.get('requestsHistory') - const callbacksHistoryObj = arrayStore.get('callbacksHistory') - const backgroundData = { - requestsHistory: requestsHistoryObj, - callbacksHistory: callbacksHistoryObj - } - - if (globalConfig.scriptExecution) { - await executePostRequestScript(convertedRequest, resp, scriptsExecution, contextObj, variableData, backgroundData) - } - - let testResult = null - console.log('GVK', resp) - if (globalConfig.testsExecution) { - testResult = await handleTests(convertedRequest, resp.syncResponse, resp.callback, variableData.environment, backgroundData, contextObj.requestVariables) - } - request.appended = { - status: status, - testResult, - response: resp.syncResponse, - callback: resp.callback, - request: convertedRequest, - additionalInfo: { - curlRequest: resp.curlRequest - } - } - - // Update total progress counts - globalConfig.totalProgress.requestsProcessed++ - globalConfig.totalProgress.assertionsProcessed += request.tests && request.tests.assertions ? request.tests.assertions.length : 0 - globalConfig.totalProgress.assertionsPassed += testResult.passedCount - globalConfig.totalProgress.assertionsFailed += request.tests && request.tests.assertions ? (request.tests.assertions.length - testResult.passedCount) : 0 - - if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) { - notificationEmitter.broadcastOutboundProgress({ - outboundID: tracing.outboundID, - testCaseId: testCase.id, - testCaseName: testCase.name, - status: status, - requestId: request.id, - response: resp.syncResponse, - callback: resp.callback, - requestSent: convertedRequest, - additionalInfo: { - curlRequest: resp.curlRequest, - scriptsExecution: scriptsExecution - }, - testResult, - totalProgress: globalConfig.totalProgress - }, tracing.sessionID) - } -} - -const setSkippedResponse = async (convertedRequest, request, status, tracing, testCase, scriptsExecution, globalConfig) => { - let testResult = null - if (globalConfig.testsExecution) { - testResult = await setAllTestsSkipped(convertedRequest) - } - request.appended = { - status: status, - testResult, - response: null, - callback: null, - request: convertedRequest, - additionalInfo: { - curlRequest: null - } - } - - // Update total progress counts - globalConfig.totalProgress.requestsProcessed++ - globalConfig.totalProgress.assertionsProcessed += request.tests && request.tests.assertions && request.tests.assertions.length - globalConfig.totalProgress.assertionsPassed += testResult.passedCount - // globalConfig.totalProgress.assertionsFailed += 0 - - if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) { - notificationEmitter.broadcastOutboundProgress({ - outboundID: tracing.outboundID, - testCaseId: testCase.id, - testCaseName: testCase.name, - status: status, - requestId: request.id, - response: null, - callback: null, - requestSent: convertedRequest, - additionalInfo: { - curlRequest: null, - scriptsExecution: scriptsExecution - }, - testResult, - totalProgress: globalConfig.totalProgress - }, tracing.sessionID) - } -} - -const executePreRequestScript = async (convertedRequest, scriptsExecution, contextObj, variableData) => { - if (convertedRequest.scripts && convertedRequest.scripts.preRequest && convertedRequest.scripts.preRequest.exec.length > 0 && convertedRequest.scripts.preRequest.exec !== ['']) { - let context = postmanContext - if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') { - context = javascriptContext - } - const requestToPass = { - url: convertedRequest.url, - method: convertedRequest.method, - path: convertedRequest.path, - queryParams: convertedRequest.queryParams, - headers: convertedRequest.headers, - body: convertedRequest.body - } - scriptsExecution.preRequest = await context.executeAsync(convertedRequest.scripts.preRequest.exec, { context: { request: requestToPass }, id: uuid.v4() }, contextObj) - variableData.environment = scriptsExecution.preRequest.environment - } -} - -const executePostRequestScript = async (convertedRequest, resp, scriptsExecution, contextObj, variableData, backgroundData) => { - if (convertedRequest.scripts && convertedRequest.scripts.postRequest && convertedRequest.scripts.postRequest.exec.length > 0 && convertedRequest.scripts.postRequest.exec !== ['']) { - let response - if (_.isString(resp)) { - response = resp - } else if (resp.syncResponse) { - response = { code: resp.syncResponse.status, status: resp.syncResponse.statusText, body: resp.syncResponse.body || resp.syncResponse.data } - } - - let callback - if (resp.callback) { - callback = resp.callback - } - - // Pass the requestsHistory and callbacksHistory to postman sandbox - const collectionVariables = [] - collectionVariables.push( - { - type: 'any', - key: 'requestsHistory', - value: JSON.stringify(backgroundData.requestsHistory) - }, - { - type: 'any', - key: 'callbacksHistory', - value: JSON.stringify(backgroundData.callbacksHistory) - } - ) - let context = postmanContext - if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') { - context = javascriptContext - } - scriptsExecution.postRequest = await context.executeAsync(convertedRequest.scripts.postRequest.exec, { context: { response, callback, collectionVariables }, id: uuid.v4() }, contextObj) - variableData.environment = scriptsExecution.postRequest.environment - } -} - -const handleTests = async (request, response = null, callback = null, environment = {}, backgroundData = {}, requestVariables = {}) => { - try { - const results = {} - let passedCount = 0 - if (request.tests && request.tests.assertions.length > 0) { - for (const k in request.tests.assertions) { - const testCase = request.tests.assertions[k] - try { - let status = 'SKIPPED' - const expect = (args) => { // eslint-disable-line - status = 'SUCCESS' - return expectOriginal(args) - } - const testsString = testCase.exec.join('\n') - - eval(testsString) // eslint-disable-line - results[testCase.id] = { - status - } - passedCount++ - } catch (err) { - console.log(err) - results[testCase.id] = { - status: 'FAILED', - message: err.message - } - } - } - } - return { results, passedCount } - } catch (err) { - console.log(err) - return null - } -} - -const setAllTestsSkipped = async (request) => { - const results = {} - let passedCount = 0 - if (request.tests && request.tests.assertions.length > 0) { - for (const k in request.tests.assertions) { - const testCase = request.tests.assertions[k] - results[testCase.id] = { - status: 'SKIPPED' - } - passedCount++ - } - } - return { results, passedCount } -} - -const getUrlPrefix = (baseUrl) => { - let returnUrl = baseUrl - if (!returnUrl.startsWith('http:') && !returnUrl.startsWith('https:')) { - returnUrl = 'http://' + returnUrl - } - if (returnUrl.endsWith('/')) { - returnUrl = returnUrl.slice(0, returnUrl.length - 1) - } - return returnUrl -} - -const sendRequest = (baseUrl, method, path, queryParams, headers, body, successCallbackUrl, errorCallbackUrl, ignoreCallbacks, dfspId, contextObj = {}) => { - return new Promise((resolve, reject) => { - (async () => { - const httpAgentProps = {} - const user = dfspId ? { dfspId } : undefined - const userConfig = await Config.getUserConfig(user) - const uniqueId = UniqueIdGenerator.generateUniqueId() - let urlGenerated = userConfig.CALLBACK_ENDPOINT + path - if (baseUrl) { - urlGenerated = getUrlPrefix(baseUrl) + path - } - if (userConfig.OUTBOUND_MUTUAL_TLS_ENABLED) { - const tlsConfig = await ConnectionProvider.getTlsConfig() - if (!tlsConfig.dfsps[dfspId]) { - const errorMsg = 'Outbound TLS is enabled, but there is no TLS config found for DFSP ID: ' + dfspId - customLogger.logMessage('error', errorMsg, { user }) - reject(new Error(JSON.stringify({ errorCode: 4000, errorDescription: errorMsg }))) - } - httpAgentProps.httpsAgent = new https.Agent({ - cert: tlsConfig.dfsps[dfspId].hubClientCert, - key: tlsConfig.hubClientKey, - ca: [tlsConfig.dfsps[dfspId].dfspServerCaRootCert], - rejectUnauthorized: true + const user = dfspId ? { dfspId } : undefined + const testOutboundEventChannel = new MessageChannel(); + + + + const data = { action: 'OutboundSend', inputTemplate, traceID, dfspId, apiDefinitions, testOutboundEventPort: testOutboundEventChannel.port2 } + const worker = new Worker(path.resolve(__dirname, 'testrunner-worker.js'), { + workerData: data, + transferList: [ testOutboundEventChannel.port2 ] + }); + worker.on('message', () => { + return 'Completed' + }); + worker.on('error', (err) => { + console.log(err.stack) + return 'Error' + }); + worker.on('exit', (code) => { + if (code !== 0) + console.log(`Worker stopped with exit code ${code}`); + }); + + testOutboundEventChannel.port1.on('message', (message) => { + console.log('Message from worker through channel', message) + if (message.action === 'listenCallbacks') { + // Listen for success callback + MyEventEmitter.getEmitter('testOutbound', user).once(message.successCallbackUrl, (callbackHeaders, callbackBody) => { + MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(message.errorCallbackUrl) + testOutboundEventChannel.port1.postMessage({ + callbackType: 'successCallback', + callbackHeaders, + callbackBody }) - urlGenerated = urlGenerated.replace('http:', 'https:') - } else if (userConfig.CLIENT_MUTUAL_TLS_ENABLED) { - const urlObject = new URL(urlGenerated) - const cred = userConfig.CLIENT_TLS_CREDS.filter(item => item.HOST === urlObject.host) - if (Array.isArray(cred) && cred.length === 1) { - customLogger.logMessage('info', `Found the Client certificate for ${urlObject.host}`, { notification: false }) - httpAgentProps.httpsAgent = httpAgentStore.getHttpsAgent(urlObject.host, { - cert: cred[0].CERT, - key: cred[0].KEY, - rejectUnauthorized: false - }) - urlGenerated = urlGenerated.replace('http:', 'https:') - } else { - const errorMsg = `client mutual TLS is enabled, but there is no TLS config found for ${urlObject.host}` - customLogger.logMessage('error', errorMsg, { notification: false }) - } - } else { - if (urlGenerated.startsWith('https:')) { - httpAgentProps.httpsAgent = httpAgentStore.getHttpsAgent('generic', { - rejectUnauthorized: false - }) - } else { - httpAgentProps.httpAgent = httpAgentStore.getHttpAgent('generic') - } - } - - const reqOpts = { - method: method, - url: urlGenerated, - path: path, - params: queryParams, - headers: headers, - data: body, - timeout: (contextObj.requestVariables && contextObj.requestVariables.REQUEST_TIMEOUT) || userConfig.DEFAULT_REQUEST_TIMEOUT, - validateStatus: function (status) { - return status < 900 // Reject only if the status code is greater than or equal to 900 - }, - ...httpAgentProps - } - - if (contextObj.requestVariables && contextObj.requestVariables.TTK_JWS_SIGN_KEY) { - try { - await JwsSigning.signWithKey(reqOpts, contextObj.requestVariables.TTK_JWS_SIGN_KEY) - } catch (err) { - customLogger.logMessage('error', err.message, { additionalData: err }) - } - } else { - try { - await JwsSigning.sign(reqOpts) - customLogger.logOutboundRequest('info', 'JWS signed', { uniqueId, request: reqOpts }) - } catch (err) { - customLogger.logMessage('error', err.message, { additionalData: err }) - } - } - - var syncResponse = {} - var curlRequest = '' - var timer = null - if (successCallbackUrl && errorCallbackUrl && (ignoreCallbacks !== true)) { - timer = setTimeout(() => { - MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl) - MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl) - reject(new Error(JSON.stringify({ curlRequest: curlRequest, syncResponse: syncResponse, errorCode: 4001, errorMessage: 'Timeout for receiving callback' }))) - }, userConfig.CALLBACK_TIMEOUT) - // Listen for success callback - MyEventEmitter.getEmitter('testOutbound', user).once(successCallbackUrl, (callbackHeaders, callbackBody) => { - clearTimeout(timer) - MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl) - customLogger.logMessage('info', 'Received success callback ' + successCallbackUrl, { request: { headers: callbackHeaders, body: callbackBody }, notification: false }) - resolve({ curlRequest: curlRequest, syncResponse: syncResponse, callback: { url: successCallbackUrl, headers: callbackHeaders, body: callbackBody } }) - }) - // Listen for error callback - MyEventEmitter.getEmitter('testOutbound', user).once(errorCallbackUrl, (callbackHeaders, callbackBody) => { - clearTimeout(timer) - MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl) - customLogger.logMessage('info', 'Received error callback ' + errorCallbackUrl, { request: { headers: callbackHeaders, body: callbackBody }, notification: false }) - reject(new Error(JSON.stringify({ curlRequest: curlRequest, syncResponse: syncResponse, callback: { url: errorCallbackUrl, headers: callbackHeaders, body: callbackBody } }))) + }) + // Listen for error callback + MyEventEmitter.getEmitter('testOutbound', user).once(message.errorCallbackUrl, (callbackHeaders, callbackBody) => { + MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(message.successCallbackUrl) + testOutboundEventChannel.port1.postMessage({ + callbackType: 'errorCallback', + callbackHeaders, + callbackBody }) - } - - customLogger.logOutboundRequest('info', 'Sending request ' + reqOpts.method + ' ' + reqOpts.url, { additionalData: { request: reqOpts }, user, uniqueId, request: reqOpts }) - - axios(reqOpts).then((result) => { - syncResponse = { - status: result.status, - statusText: result.statusText, - body: result.data, - headers: result.headers - } - curlRequest = result.request ? result.request.toCurl() : '' - - if (result.status > 299) { - customLogger.logOutboundRequest('error', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: { response: result }, user, uniqueId, request: reqOpts }) - if (timer) { - clearTimeout(timer) - MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl) - MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl) - } - reject(new Error(JSON.stringify({ curlRequest: curlRequest, syncResponse }))) - } else { - customLogger.logOutboundRequest('info', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: { response: result }, user, uniqueId, request: reqOpts }) - } - - if (!successCallbackUrl || !errorCallbackUrl || ignoreCallbacks) { - resolve({ curlRequest: curlRequest, syncResponse: syncResponse }) - } - customLogger.logMessage('info', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: result.data, notification: false, user }) - }, (err) => { - syncResponse = { - status: 500, - statusText: err.message - } - customLogger.logOutboundRequest('error', 'Failed to send request ' + method + ' Error: ' + err.message, { additionalData: { errorStack: err.stack }, user, uniqueId, request: reqOpts }) - customLogger.logMessage('error', 'Failed to send request ' + method + ' Error: ' + err.message, { additionalData: { errorStack: err.stack }, notification: false, user }) - reject(new Error(JSON.stringify({ errorCode: 4000, syncResponse }))) }) - })() + } }) -} - -const setResultObject = (inputObject) => { - if (typeof inputObject === 'string') { - return inputObject - } else if (typeof inputObject === 'object') { - return JSON.stringify(inputObject) - } -} - -const replaceVariables = (inputObject, inputValues, request, requestsObj) => { - let resultObject = setResultObject(inputObject) - if (!resultObject) { - return inputObject - } - // Check the string for any inclusions like {$some_param} - const matchedArray = resultObject.match(/{\$([^}]+)}/g) - if (matchedArray) { - matchedArray.forEach(element => { - // Check for the function type of param, if its function we need to call a function in custom-functions and replace the returned value - const splitArr = element.split('.') - switch (splitArr[0]) { - case '{$function': { - resultObject = resultObject.replace(element, getFunctionResult(element, inputValues, request)) - break - } - case '{$prev': { - const temp = element.replace(/{\$prev.(.*)}/, '$1') - const tempArr = temp.split('.') - try { - var replacedValue = _.get(requestsObj[tempArr[0]].appended, temp.replace(tempArr[0] + '.', '')) - if (replacedValue) { - resultObject = resultObject.replace(element, replacedValue) - } - } catch (err) { - customLogger.logMessage('error', `${element} not found`, { notification: false }) - } - break - } - case '{$request': { - const temp = element.replace(/{\$request.(.*)}/, '$1') - const replacedValue = _.get(request, temp) - if (replacedValue && !replacedValue.startsWith('{$')) { - resultObject = resultObject.replace(element, replacedValue) - } - break - } - case '{$inputs': { - const temp = element.replace(/{\$inputs.(.*)}/, '$1') - if (inputValues[temp]) { - resultObject = resultObject.replace(element, inputValues[temp]) - } - break - } - default: - break - } - }) - } - - return (typeof inputObject === 'object') ? JSON.parse(resultObject) : resultObject -} - -const replaceRequestVariables = (inputRequest) => { - return _replaceGenericVariables(inputRequest, inputRequest, 'request') -} - -const replaceEnvironmentVariables = (inputRequest, environment) => { - return _replaceGenericVariables(inputRequest, environment, 'environment') -} - -const replaceRequestLevelEnvironmentVariables = (inputRequest, requestVariables) => { - return _replaceGenericVariables(inputRequest, requestVariables, 'requestVariables') -} - -const _replaceGenericVariables = (inputRequest, replaceObject, variablePrefix) => { - let resultObject = setResultObject(inputRequest) - if (!resultObject) { - return inputRequest - } - // Check once again for the replaced request variables - const matchedArray = resultObject.match(/{\$([^}]+)}/g) - if (matchedArray) { - matchedArray.forEach(element => { - // Check for the function type of param, if its function we need to call a function in custom-functions and replace the returned value - const splitArr = element.split('.') - if (splitArr[0] === '{$' + variablePrefix) { - const regExp1 = new RegExp('{\\$' + variablePrefix + '.(.*)}') - var temp2 = element.replace(regExp1, '$1') - var replacedValue2 = _.get(replaceObject, temp2) - if (replacedValue2 !== undefined) { - resultObject = resultObject.replace(element, replacedValue2) - } - } - }) - } - return (typeof inputRequest === 'object') ? JSON.parse(resultObject) : resultObject } -const replacePathVariables = (operationPath, params) => { - let resultObject = operationPath +// const OutboundSendLoop = async (inputTemplate, traceID, dfspId, iterations) => { +// const totalCounts = getTotalCounts(inputTemplate) + +// const globalConfig = { +// broadcastOutboundProgressEnabled: false, +// scriptExecution: true, +// testsExecution: true, +// totalProgress: { +// testCasesTotal: totalCounts.totalTestCases, +// testCasesProcessed: 0, +// requestsTotal: totalCounts.totalRequests, +// requestsProcessed: 0, +// assertionsTotal: totalCounts.totalAssertions, +// assertionsProcessed: 0, +// assertionsPassed: 0, +// assertionsFailed: 0 +// } +// } +// const tracing = getTracing(traceID, dfspId) + +// const environmentVariables = { ...inputTemplate.inputValues } +// try { +// const totalStartedTimeStamp = new Date() +// const totalReport = { +// iterations: [] +// } +// for (let itn = 0; itn < iterations; itn++) { +// const startedTimeStamp = new Date() +// // Deep copy the template +// const tmpTemplate = JSON.parse(JSON.stringify(inputTemplate)) +// // Execute all the test cases in the template +// for (const i in tmpTemplate.test_cases) { +// await processTestCase(tmpTemplate.test_cases[i], traceID, tmpTemplate.inputValues, environmentVariables, dfspId, globalConfig) +// } +// const completedTimeStamp = new Date() +// const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime() +// const runtimeInformation = { +// iterationNumber: itn, +// completedTimeISO: completedTimeStamp.toISOString(), +// startedTime: startedTimeStamp.toUTCString(), +// completedTime: completedTimeStamp.toUTCString(), +// runDurationMs: runDurationMs, +// totalAssertions: 0, +// totalPassedAssertions: 0 +// } +// // TODO: This can be optimized by storing only results into the iterations array +// totalReport.iterations.push(generateFinalReport(tmpTemplate, runtimeInformation)) +// notificationEmitter.broadcastOutboundProgress({ +// status: 'ITERATION_PROGRESS', +// outboundID: tracing.outboundID, +// iterationStatus: runtimeInformation +// }, tracing.sessionID) +// } + +// const totalCompletedTimeStamp = new Date() +// const totalRunDurationMs = totalCompletedTimeStamp.getTime() - totalStartedTimeStamp.getTime() +// // Send the total result to client +// if (tracing.outboundID) { +// notificationEmitter.broadcastOutboundProgress({ +// status: 'ITERATIONS_FINISHED', +// outboundID: tracing.outboundID, +// totalRunDurationMs, +// totalReport +// }, tracing.sessionID) +// } +// } catch (err) { +// notificationEmitter.broadcastOutboundProgress({ +// status: 'ITERATIONS_TERMINATED', +// outboundID: tracing.outboundID, +// errorMessage: err.message +// }, tracing.sessionID) +// } +// } - // Check the string for any inclusions like {$some_param} - const matchedArray = resultObject.match(/{([^}]+)}/g) - if (matchedArray) { - matchedArray.forEach(element => { - var temp = element.replace(/{([^}]+)}/, '$1') - if (params && params[temp]) { - resultObject = resultObject.replace(element, params[temp]) - } - }) - } - - return resultObject -} - -// Execute the function and return the result -const getFunctionResult = (param, inputValues, request) => { - return utilsInternal.getFunctionResult(param, inputValues, request) -} - -// Get Total Counts -const getTotalCounts = (inputTemplate) => { - var result = { - totalTestCases: 0, - totalRequests: 0, - totalAssertions: 0 - } - const { test_cases } = inputTemplate // eslint-disable-line - result.totalTestCases = test_cases.length - test_cases.forEach(testCase => { - const { requests } = testCase - result.totalRequests += requests.length - requests.forEach(request => { - result.totalAssertions += request.tests && request.tests.assertions && request.tests.assertions.length - }) - }) - return result +const terminateOutbound = (traceID) => { + terminateTraceIds[traceID] = true } -// Generate consolidated final report -const generateFinalReport = (inputTemplate, runtimeInformation) => { - const { test_cases, ...remaingPropsInTemplate } = inputTemplate // eslint-disable-line - const resultTestCases = test_cases.map(testCase => { - const { requests, ...remainingPropsInTestCase } = testCase - const resultRequests = requests.map(requestItem => { - const { testResult, request, ...remainginPropsInRequest } = requestItem.appended - if (request.tests && request.tests.assertions) { - request.tests.assertions = request.tests.assertions.map(assertion => { - return { - ...assertion, - resultStatus: testResult.results[assertion.id] - } - }) - request.tests.passedAssertionsCount = testResult.passedCount - runtimeInformation.totalAssertions += request.tests.assertions.length - runtimeInformation.totalPassedAssertions += request.tests.passedAssertionsCount - } - return { - request, - ...remainginPropsInRequest - } - }) - return { - ...remainingPropsInTestCase, - requests: resultRequests - } - }) - return { - ...remaingPropsInTemplate, - test_cases: resultTestCases, - runtimeInformation: runtimeInformation - } -} module.exports = { OutboundSend, - OutboundSendLoop, - terminateOutbound, - handleTests, - sendRequest, - replaceVariables, - replaceRequestVariables, - replaceEnvironmentVariables, - replaceRequestLevelEnvironmentVariables, - replacePathVariables, - getFunctionResult, - generateFinalReport + // OutboundSendLoop, + terminateOutbound } diff --git a/src/lib/test-outbound/testrunner-worker.js b/src/lib/test-outbound/testrunner-worker.js new file mode 100644 index 00000000..ff3e2962 --- /dev/null +++ b/src/lib/test-outbound/testrunner-worker.js @@ -0,0 +1,859 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + + * ModusBox + * Georgi Logodazhki + * Vijaya Kumar Guthi (Original Author) + -------------- + ******/ + +const _ = require('lodash') +const customLogger = require('../requestLogger') +const axios = require('axios').default +const https = require('https') +const Config = require('../config') +const MyEventEmitter = require('../MyEventEmitter') +const notificationEmitter = require('../notificationEmitter.js') +const { readFileAsync } = require('../utils') +const expectOriginal = require('chai').expect // eslint-disable-line +const JwsSigning = require('../jws/JwsSigning') +const { TraceHeaderUtils } = require('ml-testing-toolkit-shared-lib') +const ConnectionProvider = require('../configuration-providers/mb-connection-manager') +require('request-to-curl') +require('atob') // eslint-disable-line +delete axios.defaults.headers.common.Accept +const postmanContext = require('../scripting-engines/postman-sandbox') +const javascriptContext = require('../scripting-engines/vm-javascript-sandbox') +const openApiDefinitionsModel = require('../mocking/openApiDefinitionsModel') +const uuid = require('uuid') +const utilsInternal = require('../utilsInternal') +const dbAdapter = require('../db/adapters/dbAdapter') +const arrayStore = require('../arrayStore') +const UniqueIdGenerator = require('../../lib/uniqueIdGenerator') +const httpAgentStore = require('../httpAgentStore') +const { parentPort, workerData } = require('worker_threads') + +var terminateTraceIds = {} + +const getTracing = (traceID, dfspId) => { + const tracing = { + outboundID: traceID, + sessionID: null + } + if (traceID && TraceHeaderUtils.isCustomTraceID(traceID)) { + tracing.outboundID = TraceHeaderUtils.getEndToEndID(traceID) + tracing.sessionID = TraceHeaderUtils.getSessionID(traceID) + } + if (Config.getSystemConfig().HOSTING_ENABLED) { + tracing.sessionID = dfspId + } + return tracing +} + + +const OutboundSend = async (inputTemplate, traceID, dfspId) => { + const totalCounts = getTotalCounts(inputTemplate) + const globalConfig = { + broadcastOutboundProgressEnabled: true, + scriptExecution: true, + testsExecution: true, + totalProgress: { + testCasesTotal: totalCounts.totalTestCases, + testCasesProcessed: 0, + requestsTotal: totalCounts.totalRequests, + requestsProcessed: 0, + assertionsTotal: totalCounts.totalAssertions, + assertionsProcessed: 0, + assertionsPassed: 0, + assertionsFailed: 0 + } + } + + const startedTimeStamp = new Date() + const tracing = getTracing(traceID, dfspId) + + const variableData = { + environment: { ...inputTemplate.inputValues } + } + try { + for (const i in inputTemplate.test_cases) { + globalConfig.totalProgress.testCasesProcessed++ + await processTestCase(inputTemplate.test_cases[i], traceID, inputTemplate.inputValues, variableData, dfspId, globalConfig) + } + + const completedTimeStamp = new Date() + const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime() + // Send the total result to client + if (tracing.outboundID) { + const runtimeInformation = { + completedTimeISO: completedTimeStamp.toISOString(), + startedTime: startedTimeStamp.toUTCString(), + completedTime: completedTimeStamp.toUTCString(), + runDurationMs: runDurationMs, + avgResponseTime: 'NA', + totalAssertions: 0, + totalPassedAssertions: 0 + } + const totalResult = generateFinalReport(inputTemplate, runtimeInformation) + if (Config.getSystemConfig().HOSTING_ENABLED) { + const totalResultCopy = JSON.parse(JSON.stringify(totalResult)) + totalResultCopy.runtimeInformation.completedTimeISO = completedTimeStamp + dbAdapter.upsert('reports', totalResultCopy, { dfspId }) + } + notificationEmitter.broadcastOutboundProgress({ + status: 'FINISHED', + outboundID: tracing.outboundID, + totalResult + }, tracing.sessionID) + } + } catch (err) { + console.log(err) + notificationEmitter.broadcastOutboundProgress({ + status: 'TERMINATED', + outboundID: tracing.outboundID + }, tracing.sessionID) + } +} + +const OutboundSendLoop = async (inputTemplate, traceID, dfspId, iterations) => { + const totalCounts = getTotalCounts(inputTemplate) + + const globalConfig = { + broadcastOutboundProgressEnabled: false, + scriptExecution: true, + testsExecution: true, + totalProgress: { + testCasesTotal: totalCounts.totalTestCases, + testCasesProcessed: 0, + requestsTotal: totalCounts.totalRequests, + requestsProcessed: 0, + assertionsTotal: totalCounts.totalAssertions, + assertionsProcessed: 0, + assertionsPassed: 0, + assertionsFailed: 0 + } + } + const tracing = getTracing(traceID, dfspId) + + const environmentVariables = { ...inputTemplate.inputValues } + try { + const totalStartedTimeStamp = new Date() + const totalReport = { + iterations: [] + } + for (let itn = 0; itn < iterations; itn++) { + const startedTimeStamp = new Date() + // Deep copy the template + const tmpTemplate = JSON.parse(JSON.stringify(inputTemplate)) + // Execute all the test cases in the template + for (const i in tmpTemplate.test_cases) { + await processTestCase(tmpTemplate.test_cases[i], traceID, tmpTemplate.inputValues, environmentVariables, dfspId, globalConfig) + } + const completedTimeStamp = new Date() + const runDurationMs = completedTimeStamp.getTime() - startedTimeStamp.getTime() + const runtimeInformation = { + iterationNumber: itn, + completedTimeISO: completedTimeStamp.toISOString(), + startedTime: startedTimeStamp.toUTCString(), + completedTime: completedTimeStamp.toUTCString(), + runDurationMs: runDurationMs, + totalAssertions: 0, + totalPassedAssertions: 0 + } + // TODO: This can be optimized by storing only results into the iterations array + totalReport.iterations.push(generateFinalReport(tmpTemplate, runtimeInformation)) + notificationEmitter.broadcastOutboundProgress({ + status: 'ITERATION_PROGRESS', + outboundID: tracing.outboundID, + iterationStatus: runtimeInformation + }, tracing.sessionID) + } + + const totalCompletedTimeStamp = new Date() + const totalRunDurationMs = totalCompletedTimeStamp.getTime() - totalStartedTimeStamp.getTime() + // Send the total result to client + if (tracing.outboundID) { + notificationEmitter.broadcastOutboundProgress({ + status: 'ITERATIONS_FINISHED', + outboundID: tracing.outboundID, + totalRunDurationMs, + totalReport + }, tracing.sessionID) + } + } catch (err) { + notificationEmitter.broadcastOutboundProgress({ + status: 'ITERATIONS_TERMINATED', + outboundID: tracing.outboundID, + errorMessage: err.message + }, tracing.sessionID) + } +} + +const terminateOutbound = (traceID) => { + terminateTraceIds[traceID] = true +} + +const processTestCase = async (testCase, traceID, inputValues, variableData, dfspId, globalConfig) => { + const tracing = getTracing(traceID) + + // Load the requests array into an object by the request id to access a particular object faster + const requestsObj = {} + // Store the request ids into a new array + const templateIDArr = [] + for (const i in testCase.requests) { + requestsObj[testCase.requests[i].id] = testCase.requests[i] + templateIDArr.push(testCase.requests[i].id) + } + // Sort the request ids array + templateIDArr.sort((a, b) => { + return a > b + }) + + const apiDefinitions = workerData.apiDefinitions + // Iterate the request ID array + for (const i in templateIDArr) { + if (terminateTraceIds[traceID]) { + delete terminateTraceIds[traceID] + throw new Error('Terminated') + } + const request = requestsObj[templateIDArr[i]] + + const reqApiDefinition = apiDefinitions.find((item) => { + return ( + item.majorVersion === +request.apiVersion.majorVersion && + item.minorVersion === +request.apiVersion.minorVersion && + item.type === request.apiVersion.type + ) + }) + + if (request.delay) { + await new Promise(resolve => setTimeout(resolve, request.delay)) + } + + let convertedRequest = JSON.parse(JSON.stringify(request)) + + // Form the actual http request headers, body, path and method by replacing configurable parameters + // Replace the parameters + convertedRequest = replaceVariables(request, inputValues, request, requestsObj) + convertedRequest = replaceRequestVariables(convertedRequest) + + // Form the path from params and operationPath + convertedRequest.path = replacePathVariables(request.operationPath, convertedRequest.params) + + // Insert traceparent header if sessionID passed + if (tracing.sessionID) { + convertedRequest.headers = convertedRequest.headers || {} + convertedRequest.headers.traceparent = '00-' + traceID + '-0123456789abcdef0-00' + } + + const scriptsExecution = {} + let contextObj = null + if (globalConfig.scriptExecution) { + let context = postmanContext + if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') { + context = javascriptContext + } + contextObj = await context.generateContextObj(variableData.environment) + } + + // Send http request + try { + // Extra step to access request variables that consists of environment variables in scripts + const tmpRequest = replaceEnvironmentVariables(convertedRequest, variableData.environment) + if (globalConfig.scriptExecution) { + await executePreRequestScript(tmpRequest, scriptsExecution, contextObj, variableData) + } + + convertedRequest = replaceEnvironmentVariables(convertedRequest, variableData.environment) + convertedRequest = replaceRequestLevelEnvironmentVariables(convertedRequest, contextObj.requestVariables) + + let successCallbackUrl = null + let errorCallbackUrl = null + if (request.apiVersion.asynchronous === true) { + const cbMapRawdata = await readFileAsync(reqApiDefinition.callbackMapFile) + const reqCallbackMap = JSON.parse(cbMapRawdata) + if (reqCallbackMap[request.operationPath] && reqCallbackMap[request.operationPath][request.method]) { + const successCallback = reqCallbackMap[request.operationPath][request.method].successCallback + const errorCallback = reqCallbackMap[request.operationPath][request.method].errorCallback + successCallbackUrl = successCallback.method + ' ' + replaceVariables(successCallback.pathPattern, null, convertedRequest) + errorCallbackUrl = errorCallback.method + ' ' + replaceVariables(errorCallback.pathPattern, null, convertedRequest) + } + } + if (contextObj.requestVariables && contextObj.requestVariables.SKIP_REQUEST) { + await setSkippedResponse(convertedRequest, request, 'SKIPPED', tracing, testCase, scriptsExecution, globalConfig) + } else { + const resp = await sendRequest(convertedRequest.url, convertedRequest.method, convertedRequest.path, convertedRequest.queryParams, convertedRequest.headers, convertedRequest.body, successCallbackUrl, errorCallbackUrl, convertedRequest.ignoreCallbacks, dfspId, contextObj) + await setResponse(convertedRequest, resp, variableData, request, 'SUCCESS', tracing, testCase, scriptsExecution, contextObj, globalConfig) + } + } catch (err) { + let resp + try { + resp = JSON.parse(err.message) + } catch (parsingErr) { + resp = err.message + } + await setResponse(convertedRequest, resp, variableData, request, 'ERROR', tracing, testCase, scriptsExecution, contextObj, globalConfig) + } finally { + if (contextObj) { + contextObj.ctx.dispose() + contextObj.ctx = null + } + } + } + + // Return status report of this test case + return testCase + // Set a timeout if the response callback is not received in a particular time +} + +const setResponse = async (convertedRequest, resp, variableData, request, status, tracing, testCase, scriptsExecution, contextObj, globalConfig) => { + // Get the requestsHistory and callbacksHistory from the arrayStore + const requestsHistoryObj = arrayStore.get('requestsHistory') + const callbacksHistoryObj = arrayStore.get('callbacksHistory') + const backgroundData = { + requestsHistory: requestsHistoryObj, + callbacksHistory: callbacksHistoryObj + } + + if (globalConfig.scriptExecution) { + await executePostRequestScript(convertedRequest, resp, scriptsExecution, contextObj, variableData, backgroundData) + } + + let testResult = null + console.log('GVK', resp) + if (globalConfig.testsExecution) { + testResult = await handleTests(convertedRequest, resp.syncResponse, resp.callback, variableData.environment, backgroundData, contextObj.requestVariables) + } + request.appended = { + status: status, + testResult, + response: resp.syncResponse, + callback: resp.callback, + request: convertedRequest, + additionalInfo: { + curlRequest: resp.curlRequest + } + } + + // Update total progress counts + globalConfig.totalProgress.requestsProcessed++ + globalConfig.totalProgress.assertionsProcessed += request.tests && request.tests.assertions ? request.tests.assertions.length : 0 + globalConfig.totalProgress.assertionsPassed += testResult.passedCount + globalConfig.totalProgress.assertionsFailed += request.tests && request.tests.assertions ? (request.tests.assertions.length - testResult.passedCount) : 0 + + if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) { + notificationEmitter.broadcastOutboundProgress({ + outboundID: tracing.outboundID, + testCaseId: testCase.id, + testCaseName: testCase.name, + status: status, + requestId: request.id, + response: resp.syncResponse, + callback: resp.callback, + requestSent: convertedRequest, + additionalInfo: { + curlRequest: resp.curlRequest, + scriptsExecution: scriptsExecution + }, + testResult, + totalProgress: globalConfig.totalProgress + }, tracing.sessionID) + } +} + +const setSkippedResponse = async (convertedRequest, request, status, tracing, testCase, scriptsExecution, globalConfig) => { + let testResult = null + if (globalConfig.testsExecution) { + testResult = await setAllTestsSkipped(convertedRequest) + } + request.appended = { + status: status, + testResult, + response: null, + callback: null, + request: convertedRequest, + additionalInfo: { + curlRequest: null + } + } + + // Update total progress counts + globalConfig.totalProgress.requestsProcessed++ + globalConfig.totalProgress.assertionsProcessed += request.tests && request.tests.assertions && request.tests.assertions.length + globalConfig.totalProgress.assertionsPassed += testResult.passedCount + // globalConfig.totalProgress.assertionsFailed += 0 + + if (tracing.outboundID && globalConfig.broadcastOutboundProgressEnabled) { + notificationEmitter.broadcastOutboundProgress({ + outboundID: tracing.outboundID, + testCaseId: testCase.id, + testCaseName: testCase.name, + status: status, + requestId: request.id, + response: null, + callback: null, + requestSent: convertedRequest, + additionalInfo: { + curlRequest: null, + scriptsExecution: scriptsExecution + }, + testResult, + totalProgress: globalConfig.totalProgress + }, tracing.sessionID) + } +} + +const executePreRequestScript = async (convertedRequest, scriptsExecution, contextObj, variableData) => { + if (convertedRequest.scripts && convertedRequest.scripts.preRequest && convertedRequest.scripts.preRequest.exec.length > 0 && convertedRequest.scripts.preRequest.exec !== ['']) { + let context = postmanContext + if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') { + context = javascriptContext + } + const requestToPass = { + url: convertedRequest.url, + method: convertedRequest.method, + path: convertedRequest.path, + queryParams: convertedRequest.queryParams, + headers: convertedRequest.headers, + body: convertedRequest.body + } + scriptsExecution.preRequest = await context.executeAsync(convertedRequest.scripts.preRequest.exec, { context: { request: requestToPass }, id: uuid.v4() }, contextObj) + variableData.environment = scriptsExecution.preRequest.environment + } +} + +const executePostRequestScript = async (convertedRequest, resp, scriptsExecution, contextObj, variableData, backgroundData) => { + if (convertedRequest.scripts && convertedRequest.scripts.postRequest && convertedRequest.scripts.postRequest.exec.length > 0 && convertedRequest.scripts.postRequest.exec !== ['']) { + let response + if (_.isString(resp)) { + response = resp + } else if (resp.syncResponse) { + response = { code: resp.syncResponse.status, status: resp.syncResponse.statusText, body: resp.syncResponse.body || resp.syncResponse.data } + } + + let callback + if (resp.callback) { + callback = resp.callback + } + + // Pass the requestsHistory and callbacksHistory to postman sandbox + const collectionVariables = [] + collectionVariables.push( + { + type: 'any', + key: 'requestsHistory', + value: JSON.stringify(backgroundData.requestsHistory) + }, + { + type: 'any', + key: 'callbacksHistory', + value: JSON.stringify(backgroundData.callbacksHistory) + } + ) + let context = postmanContext + if (convertedRequest.scriptingEngine && convertedRequest.scriptingEngine === 'javascript') { + context = javascriptContext + } + scriptsExecution.postRequest = await context.executeAsync(convertedRequest.scripts.postRequest.exec, { context: { response, callback, collectionVariables }, id: uuid.v4() }, contextObj) + variableData.environment = scriptsExecution.postRequest.environment + } +} + +const handleTests = async (request, response = null, callback = null, environment = {}, backgroundData = {}, requestVariables = {}) => { + try { + const results = {} + let passedCount = 0 + if (request.tests && request.tests.assertions.length > 0) { + for (const k in request.tests.assertions) { + const testCase = request.tests.assertions[k] + try { + let status = 'SKIPPED' + const expect = (args) => { // eslint-disable-line + status = 'SUCCESS' + return expectOriginal(args) + } + const testsString = testCase.exec.join('\n') + + eval(testsString) // eslint-disable-line + results[testCase.id] = { + status + } + passedCount++ + } catch (err) { + console.log(err) + results[testCase.id] = { + status: 'FAILED', + message: err.message + } + } + } + } + return { results, passedCount } + } catch (err) { + console.log(err) + return null + } +} + +const setAllTestsSkipped = async (request) => { + const results = {} + let passedCount = 0 + if (request.tests && request.tests.assertions.length > 0) { + for (const k in request.tests.assertions) { + const testCase = request.tests.assertions[k] + results[testCase.id] = { + status: 'SKIPPED' + } + passedCount++ + } + } + return { results, passedCount } +} + +const getUrlPrefix = (baseUrl) => { + let returnUrl = baseUrl + if (!returnUrl.startsWith('http:') && !returnUrl.startsWith('https:')) { + returnUrl = 'http://' + returnUrl + } + if (returnUrl.endsWith('/')) { + returnUrl = returnUrl.slice(0, returnUrl.length - 1) + } + return returnUrl +} + +const sendRequest = (baseUrl, method, path, queryParams, headers, body, successCallbackUrl, errorCallbackUrl, ignoreCallbacks, dfspId, contextObj = {}) => { + return new Promise((resolve, reject) => { + (async () => { + const httpAgentProps = {} + const user = dfspId ? { dfspId } : undefined + const userConfig = await Config.getUserConfig(user) + const uniqueId = UniqueIdGenerator.generateUniqueId() + let urlGenerated = userConfig.CALLBACK_ENDPOINT + path + if (baseUrl) { + urlGenerated = getUrlPrefix(baseUrl) + path + } + if (userConfig.OUTBOUND_MUTUAL_TLS_ENABLED) { + const tlsConfig = await ConnectionProvider.getTlsConfig() + if (!tlsConfig.dfsps[dfspId]) { + const errorMsg = 'Outbound TLS is enabled, but there is no TLS config found for DFSP ID: ' + dfspId + customLogger.logMessage('error', errorMsg, { user }) + reject(new Error(JSON.stringify({ errorCode: 4000, errorDescription: errorMsg }))) + } + httpAgentProps.httpsAgent = new https.Agent({ + cert: tlsConfig.dfsps[dfspId].hubClientCert, + key: tlsConfig.hubClientKey, + ca: [tlsConfig.dfsps[dfspId].dfspServerCaRootCert], + rejectUnauthorized: true + }) + urlGenerated = urlGenerated.replace('http:', 'https:') + } else if (userConfig.CLIENT_MUTUAL_TLS_ENABLED) { + const urlObject = new URL(urlGenerated) + const cred = userConfig.CLIENT_TLS_CREDS.filter(item => item.HOST === urlObject.host) + if (Array.isArray(cred) && cred.length === 1) { + customLogger.logMessage('info', `Found the Client certificate for ${urlObject.host}`, { notification: false }) + httpAgentProps.httpsAgent = httpAgentStore.getHttpsAgent(urlObject.host, { + cert: cred[0].CERT, + key: cred[0].KEY, + rejectUnauthorized: false + }) + urlGenerated = urlGenerated.replace('http:', 'https:') + } else { + const errorMsg = `client mutual TLS is enabled, but there is no TLS config found for ${urlObject.host}` + customLogger.logMessage('error', errorMsg, { notification: false }) + } + } else { + if (urlGenerated.startsWith('https:')) { + httpAgentProps.httpsAgent = httpAgentStore.getHttpsAgent('generic', { + rejectUnauthorized: false + }) + } else { + httpAgentProps.httpAgent = httpAgentStore.getHttpAgent('generic') + } + } + + const reqOpts = { + method: method, + url: urlGenerated, + path: path, + params: queryParams, + headers: headers, + data: body, + timeout: (contextObj.requestVariables && contextObj.requestVariables.REQUEST_TIMEOUT) || userConfig.DEFAULT_REQUEST_TIMEOUT, + validateStatus: function (status) { + return status < 900 // Reject only if the status code is greater than or equal to 900 + }, + ...httpAgentProps + } + + if (contextObj.requestVariables && contextObj.requestVariables.TTK_JWS_SIGN_KEY) { + try { + await JwsSigning.signWithKey(reqOpts, contextObj.requestVariables.TTK_JWS_SIGN_KEY) + } catch (err) { + customLogger.logMessage('error', err.message, { additionalData: err }) + } + } else { + try { + await JwsSigning.sign(reqOpts) + customLogger.logOutboundRequest('info', 'JWS signed', { uniqueId, request: reqOpts }) + } catch (err) { + customLogger.logMessage('error', err.message, { additionalData: err }) + } + } + + var syncResponse = {} + var curlRequest = '' + var timer = null + if (successCallbackUrl && errorCallbackUrl && (ignoreCallbacks !== true)) { + timer = setTimeout(() => { + workerData.testOutboundEventPort.removeAllListeners() + reject(new Error(JSON.stringify({ curlRequest: curlRequest, syncResponse: syncResponse, errorCode: 4001, errorMessage: 'Timeout for receiving callback' }))) + }, userConfig.CALLBACK_TIMEOUT) + workerData.testOutboundEventPort.postMessage( { action: 'listenCallbacks', successCallbackUrl, errorCallbackUrl} ) + // Listen for callbacks + workerData.testOutboundEventPort.once('message', (data) => { + clearTimeout(timer) + const { callbackHeaders, callbackBody } = data + if (data.callbackType === 'successCallback') { + customLogger.logMessage('info', 'Received success callback ' + successCallbackUrl, { request: { headers: callbackHeaders, body: callbackBody }, notification: false }) + resolve({ curlRequest: curlRequest, syncResponse: syncResponse, callback: { url: successCallbackUrl, headers: callbackHeaders, body: callbackBody } }) + } else if (data.callbackType === 'errorCallback') { + customLogger.logMessage('info', 'Received error callback ' + errorCallbackUrl, { request: { headers: callbackHeaders, body: callbackBody }, notification: false }) + reject(new Error(JSON.stringify({ curlRequest: curlRequest, syncResponse: syncResponse, callback: { url: errorCallbackUrl, headers: callbackHeaders, body: callbackBody } }))) + } + }) + } + + customLogger.logOutboundRequest('info', 'Sending request ' + reqOpts.method + ' ' + reqOpts.url, { additionalData: { request: reqOpts }, user, uniqueId, request: reqOpts }) + + axios(reqOpts).then((result) => { + syncResponse = { + status: result.status, + statusText: result.statusText, + body: result.data, + headers: result.headers + } + curlRequest = result.request ? result.request.toCurl() : '' + + if (result.status > 299) { + customLogger.logOutboundRequest('error', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: { response: result }, user, uniqueId, request: reqOpts }) + if (timer) { + clearTimeout(timer) + MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(successCallbackUrl) + MyEventEmitter.getEmitter('testOutbound', user).removeAllListeners(errorCallbackUrl) + } + reject(new Error(JSON.stringify({ curlRequest: curlRequest, syncResponse }))) + } else { + customLogger.logOutboundRequest('info', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: { response: result }, user, uniqueId, request: reqOpts }) + } + + if (!successCallbackUrl || !errorCallbackUrl || ignoreCallbacks) { + resolve({ curlRequest: curlRequest, syncResponse: syncResponse }) + } + customLogger.logMessage('info', 'Received response ' + result.status + ' ' + result.statusText, { additionalData: result.data, notification: false, user }) + }, (err) => { + syncResponse = { + status: 500, + statusText: err.message + } + customLogger.logOutboundRequest('error', 'Failed to send request ' + method + ' Error: ' + err.message, { additionalData: { errorStack: err.stack }, user, uniqueId, request: reqOpts }) + customLogger.logMessage('error', 'Failed to send request ' + method + ' Error: ' + err.message, { additionalData: { errorStack: err.stack }, notification: false, user }) + reject(new Error(JSON.stringify({ errorCode: 4000, syncResponse }))) + }) + })() + }) +} + +const setResultObject = (inputObject) => { + if (typeof inputObject === 'string') { + return inputObject + } else if (typeof inputObject === 'object') { + return JSON.stringify(inputObject) + } +} + +const replaceVariables = (inputObject, inputValues, request, requestsObj) => { + let resultObject = setResultObject(inputObject) + if (!resultObject) { + return inputObject + } + // Check the string for any inclusions like {$some_param} + const matchedArray = resultObject.match(/{\$([^}]+)}/g) + if (matchedArray) { + matchedArray.forEach(element => { + // Check for the function type of param, if its function we need to call a function in custom-functions and replace the returned value + const splitArr = element.split('.') + switch (splitArr[0]) { + case '{$function': { + resultObject = resultObject.replace(element, getFunctionResult(element, inputValues, request)) + break + } + case '{$prev': { + const temp = element.replace(/{\$prev.(.*)}/, '$1') + const tempArr = temp.split('.') + try { + var replacedValue = _.get(requestsObj[tempArr[0]].appended, temp.replace(tempArr[0] + '.', '')) + if (replacedValue) { + resultObject = resultObject.replace(element, replacedValue) + } + } catch (err) { + customLogger.logMessage('error', `${element} not found`, { notification: false }) + } + break + } + case '{$request': { + const temp = element.replace(/{\$request.(.*)}/, '$1') + const replacedValue = _.get(request, temp) + if (replacedValue && !replacedValue.startsWith('{$')) { + resultObject = resultObject.replace(element, replacedValue) + } + break + } + case '{$inputs': { + const temp = element.replace(/{\$inputs.(.*)}/, '$1') + if (inputValues[temp]) { + resultObject = resultObject.replace(element, inputValues[temp]) + } + break + } + default: + break + } + }) + } + + return (typeof inputObject === 'object') ? JSON.parse(resultObject) : resultObject +} + +const replaceRequestVariables = (inputRequest) => { + return _replaceGenericVariables(inputRequest, inputRequest, 'request') +} + +const replaceEnvironmentVariables = (inputRequest, environment) => { + return _replaceGenericVariables(inputRequest, environment, 'environment') +} + +const replaceRequestLevelEnvironmentVariables = (inputRequest, requestVariables) => { + return _replaceGenericVariables(inputRequest, requestVariables, 'requestVariables') +} + +const _replaceGenericVariables = (inputRequest, replaceObject, variablePrefix) => { + let resultObject = setResultObject(inputRequest) + if (!resultObject) { + return inputRequest + } + + // Check once again for the replaced request variables + const matchedArray = resultObject.match(/{\$([^}]+)}/g) + if (matchedArray) { + matchedArray.forEach(element => { + // Check for the function type of param, if its function we need to call a function in custom-functions and replace the returned value + const splitArr = element.split('.') + if (splitArr[0] === '{$' + variablePrefix) { + const regExp1 = new RegExp('{\\$' + variablePrefix + '.(.*)}') + var temp2 = element.replace(regExp1, '$1') + var replacedValue2 = _.get(replaceObject, temp2) + if (replacedValue2 !== undefined) { + resultObject = resultObject.replace(element, replacedValue2) + } + } + }) + } + + return (typeof inputRequest === 'object') ? JSON.parse(resultObject) : resultObject +} + +const replacePathVariables = (operationPath, params) => { + let resultObject = operationPath + + // Check the string for any inclusions like {$some_param} + const matchedArray = resultObject.match(/{([^}]+)}/g) + if (matchedArray) { + matchedArray.forEach(element => { + var temp = element.replace(/{([^}]+)}/, '$1') + if (params && params[temp]) { + resultObject = resultObject.replace(element, params[temp]) + } + }) + } + + return resultObject +} + +// Execute the function and return the result +const getFunctionResult = (param, inputValues, request) => { + return utilsInternal.getFunctionResult(param, inputValues, request) +} + +// Get Total Counts +const getTotalCounts = (inputTemplate) => { + var result = { + totalTestCases: 0, + totalRequests: 0, + totalAssertions: 0 + } + const { test_cases } = inputTemplate // eslint-disable-line + result.totalTestCases = test_cases.length + test_cases.forEach(testCase => { + const { requests } = testCase + result.totalRequests += requests.length + requests.forEach(request => { + result.totalAssertions += request.tests && request.tests.assertions && request.tests.assertions.length + }) + }) + return result +} + +// Generate consolidated final report +const generateFinalReport = (inputTemplate, runtimeInformation) => { + const { test_cases, ...remaingPropsInTemplate } = inputTemplate // eslint-disable-line + const resultTestCases = test_cases.map(testCase => { + const { requests, ...remainingPropsInTestCase } = testCase + const resultRequests = requests.map(requestItem => { + const { testResult, request, ...remainginPropsInRequest } = requestItem.appended + if (request.tests && request.tests.assertions) { + request.tests.assertions = request.tests.assertions.map(assertion => { + return { + ...assertion, + resultStatus: testResult.results[assertion.id] + } + }) + request.tests.passedAssertionsCount = testResult.passedCount + runtimeInformation.totalAssertions += request.tests.assertions.length + runtimeInformation.totalPassedAssertions += request.tests.passedAssertionsCount + } + return { + request, + ...remainginPropsInRequest + } + }) + return { + ...remainingPropsInTestCase, + requests: resultRequests + } + }) + return { + ...remaingPropsInTemplate, + test_cases: resultTestCases, + runtimeInformation: runtimeInformation + } +} + +if (workerData.action === 'OutboundSend') { + OutboundSend(workerData.inputTemplate, workerData.traceID, workerData.dfspId) +}