From 39a1047aebd3672ccdcdb37b710d8b2ae7b946af Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 11 Nov 2022 10:43:14 -0500 Subject: [PATCH] add customer polling to canCreateTransaction (#102) * add customer polling to canCreateTransaction --- @stellar/anchor-tests/src/schemas/config.ts | 3 + .../src/tests/sep31/transactions.ts | 101 ++++++++++++++++++ @stellar/anchor-tests/src/types.ts | 7 ++ 3 files changed, 111 insertions(+) diff --git a/@stellar/anchor-tests/src/schemas/config.ts b/@stellar/anchor-tests/src/schemas/config.ts index d8236cb..096ee80 100644 --- a/@stellar/anchor-tests/src/schemas/config.ts +++ b/@stellar/anchor-tests/src/schemas/config.ts @@ -41,6 +41,9 @@ export const sep31ConfigSchema = { transactionFields: { type: "object", }, + customerPollingTimeout: { + type: "number", + }, }, required: [ "sendingAnchorClientSecret", diff --git a/@stellar/anchor-tests/src/tests/sep31/transactions.ts b/@stellar/anchor-tests/src/tests/sep31/transactions.ts index a7109dc..51b7e33 100644 --- a/@stellar/anchor-tests/src/tests/sep31/transactions.ts +++ b/@stellar/anchor-tests/src/tests/sep31/transactions.ts @@ -13,6 +13,8 @@ import { getTransactionSchema, } from "../../schemas/sep31"; import { hasExpectedAssetEnabled } from "./info"; +import { URLSearchParams } from "url"; +import { hasKycServerUrl } from "../sep12/toml"; const postTransactionsGroup = "POST /transactions"; const getTransactionsGroup = "GET /transactions/:id"; @@ -56,12 +58,14 @@ const canCreateTransaction: Test = { group: postTransactionsGroup, dependencies: [ hasDirectPaymentServer, + hasKycServerUrl, hasExpectedAssetEnabled, differentMemosSameAccount, ], context: { expects: { directPaymentServerUrl: undefined, + kycServerUrl: undefined, sendingAnchorClientKeypair: undefined, sendingAnchorToken: undefined, sendingCustomerId: undefined, @@ -88,6 +92,17 @@ const canCreateTransaction: Test = { "https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0031.md#post-transactions", }, }, + CUSTOMER_NOT_ACCEPTED: { + name: "customer's status not accepted", + text(args: any): string { + return ( + "Timed out waiting for customer(s) specified for the SEP31 transaction to be in " + + "'ACCEPTED' status " + + "Errors:\n\n" + + `${args.errors}` + ); + }, + }, ...genericFailures, }, async run(config: Config): Promise { @@ -97,6 +112,92 @@ const canCreateTransaction: Test = { // but to satisfy TypeScript we make these checks here. throw "improperly configured"; } + + async function pollCustomerAccepted( + getCustomerNetworkCall: NetworkCall, + timeout: number, + ) { + let startedAt = Date.now(); + let timeoutMs = timeout * 1000; + while (startedAt + timeoutMs > Date.now()) { + const resBody = await makeRequest( + getCustomerNetworkCall, + 200, + result, + "application/json", + ); + if (resBody.status === "ACCEPTED") { + return; + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + let customerId = getCustomerNetworkCall.request.url.split("id=")[1]; + throw `timed out pulling customer ${customerId} for ACCEPTED status`; + } + + function generateGetCustomerNetworkCall( + kycServerUrl: string, + customerId: string, + token: string, + ): NetworkCall { + const requestParamsObj: Record = { + id: customerId, + }; + + const customerType = + config?.sepConfig?.["12"]?.customers?.[ + config?.sepConfig?.["12"]?.createCustomer + ].type; + + if (customerType) requestParamsObj["type"] = customerType; + const searchParams = new URLSearchParams(requestParamsObj); + + const getCustomerCall: NetworkCall = { + request: new Request( + kycServerUrl + `/customer?${searchParams.toString()}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ), + }; + return getCustomerCall; + } + + const getSendingCustomerNetworkCall = generateGetCustomerNetworkCall( + this.context.expects.kycServerUrl, + this.context.expects.sendingCustomerId, + this.context.expects.sendingAnchorToken, + ); + + const getReceivingCustomerNetworkCall = generateGetCustomerNetworkCall( + this.context.expects.kycServerUrl, + this.context.expects.receivingCustomerId, + this.context.expects.sendingAnchorToken, + ); + + const customerPollingTimeout = + config.sepConfig["31"].customerPollingTimeout || 30; + + try { + await Promise.all([ + pollCustomerAccepted( + getReceivingCustomerNetworkCall, + customerPollingTimeout, + ), + pollCustomerAccepted( + getSendingCustomerNetworkCall, + customerPollingTimeout, + ), + ]); + } catch (err) { + result.failure = makeFailure(this.failureModes.CUSTOMER_NOT_ACCEPTED, { + errors: err, + }); + return result; + } + const postTransactionsCall: NetworkCall = { request: new Request( this.context.expects.directPaymentServerUrl + "/transactions", diff --git a/@stellar/anchor-tests/src/types.ts b/@stellar/anchor-tests/src/types.ts index 61b56d7..a257d18 100644 --- a/@stellar/anchor-tests/src/types.ts +++ b/@stellar/anchor-tests/src/types.ts @@ -139,6 +139,13 @@ export interface Sep31Config { * contain a value for every required field. */ transactionFields?: any; + + /** + * Before the SEP-31 canCreateTransaction test runs, it will poll the status of the customers created in the SEP-12 test and + * check that their status is `ACCEPTED`. customerPollingTimeout is the timeout value (seconds) for polling the customer's status. + * Defaults to 30 seconds. + */ + customerPollingTimeout?: number; } /**