From 4bdf48ed7ac318474992082c5927a00147a1f362 Mon Sep 17 00:00:00 2001 From: erika-sdf <92893480+erika-sdf@users.noreply.github.com> Date: Tue, 28 Jun 2022 16:25:09 -0500 Subject: [PATCH] Add SEP-31+38 tests where SEP-31 POST transactions depend on SEP-38 POST quote (#90) * Add sep31+38 tests to test transactions when quotes_required is true * Add changelog entry and update package version. --- @stellar/anchor-tests/package.json | 2 +- @stellar/anchor-tests/src/helpers/sep31.ts | 15 + @stellar/anchor-tests/src/helpers/test.ts | 4 + .../src/tests/sep31/transactions.ts | 36 ++- .../src/tests/sep31And38/tests.ts | 3 + .../src/tests/sep31And38/transactions.ts | 275 ++++++++++++++++++ CHANGELOG.md | 10 + server/package.json | 2 +- 8 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 @stellar/anchor-tests/src/helpers/sep31.ts create mode 100644 @stellar/anchor-tests/src/tests/sep31And38/tests.ts create mode 100644 @stellar/anchor-tests/src/tests/sep31And38/transactions.ts diff --git a/@stellar/anchor-tests/package.json b/@stellar/anchor-tests/package.json index 9118a08..2d59e03 100644 --- a/@stellar/anchor-tests/package.json +++ b/@stellar/anchor-tests/package.json @@ -1,6 +1,6 @@ { "name": "@stellar/anchor-tests", - "version": "0.5.0", + "version": "0.5.1", "description": "stellar-anchor-tests is a library and command line interface for testing Stellar anchors.", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/@stellar/anchor-tests/src/helpers/sep31.ts b/@stellar/anchor-tests/src/helpers/sep31.ts new file mode 100644 index 0000000..c51bf91 --- /dev/null +++ b/@stellar/anchor-tests/src/helpers/sep31.ts @@ -0,0 +1,15 @@ +export function getQuotesRequiredFromInfo( + sep31Info: undefined, + assetCode: string, +): boolean { + // @ts-ignore + return sep31Info.receive[assetCode].quotes_required; +} + +export function getQuotesSupportedFromInfo( + sep31Info: undefined, + assetCode: string, +): boolean { + // @ts-ignore + return sep31Info.receive[assetCode].quotes_supported; +} diff --git a/@stellar/anchor-tests/src/helpers/test.ts b/@stellar/anchor-tests/src/helpers/test.ts index f264d34..08aebef 100644 --- a/@stellar/anchor-tests/src/helpers/test.ts +++ b/@stellar/anchor-tests/src/helpers/test.ts @@ -7,6 +7,7 @@ import { default as sep10Tests } from "../tests/sep10/tests"; import { default as sep12Tests } from "../tests/sep12/tests"; import { default as sep24Tests } from "../tests/sep24/tests"; import { default as sep31Tests } from "../tests/sep31/tests"; +import { default as sep31And38Tests } from "../tests/sep31And38/tests"; import { default as sep38Tests } from "../tests/sep38/tests"; import { makeFailure } from "./failure"; import { checkConfig } from "./config"; @@ -349,6 +350,9 @@ function getTopLevelTests(config: Config): Test[] { if (config.seps.includes(38)) { tests = tests.concat(sep38Tests); } + if (config.seps.includes(31) && config.seps.includes(38)) { + tests = tests.concat(sep31And38Tests); + } return filterBySearchStrings(tests, config.searchStrings as string[]); } diff --git a/@stellar/anchor-tests/src/tests/sep31/transactions.ts b/@stellar/anchor-tests/src/tests/sep31/transactions.ts index d9ba4bb..a7109dc 100644 --- a/@stellar/anchor-tests/src/tests/sep31/transactions.ts +++ b/@stellar/anchor-tests/src/tests/sep31/transactions.ts @@ -7,6 +7,7 @@ import { hasDirectPaymentServer } from "./toml"; import { differentMemosSameAccount } from "../sep12/putCustomer"; import { genericFailures, makeFailure } from "../../helpers/failure"; import { makeRequest } from "../../helpers/request"; +import { getQuotesRequiredFromInfo } from "../../helpers/sep31"; import { postTransactionsSchema, getTransactionSchema, @@ -47,6 +48,8 @@ const requiresJwt: Test = { }; tests.push(requiresJwt); +let quotesRequired: boolean; + const canCreateTransaction: Test = { assertion: "can create a transaction", sep: 31, @@ -63,6 +66,7 @@ const canCreateTransaction: Test = { sendingAnchorToken: undefined, sendingCustomerId: undefined, receivingCustomerId: undefined, + sep31InfoObj: undefined, }, provides: { transactionId: undefined, @@ -117,7 +121,25 @@ const canCreateTransaction: Test = { ), }; result.networkCalls.push(postTransactionsCall); - const responseBody = await makeRequest( + + let assetCode = config.assetCode; + if (assetCode === undefined) { + throw "asset not configured"; + } + quotesRequired = getQuotesRequiredFromInfo( + this.context.expects.sep31InfoObj, + assetCode, + ); + + // If quotes are required, ignore this test, this will be addressed in SEP 31+38 tests + if (quotesRequired) { + await makeRequest(postTransactionsCall, 400, result, "application/json"); + this.context.provides.transactionId = null; + return result; + } + + // if quotes are not required, test this as usual. + let responseBody = await makeRequest( postTransactionsCall, 201, result, @@ -273,6 +295,13 @@ const canFetchTransaction: Test = { failureModes: genericFailures, async run(_config: Config): Promise { const result: Result = { networkCalls: [] }; + + // If quotes are required, ignore this test, this will be addressed in SEP 31+38 tests + if (quotesRequired) { + this.context.provides.transactionJson = null; + return result; + } + const getTransactionCall: NetworkCall = { request: new Request( this.context.expects.directPaymentServerUrl + @@ -337,6 +366,11 @@ const hasValidTransactionJson: Test = { }, async run(_config: Config): Promise { const result: Result = { networkCalls: [] }; + // If quotes are required, ignore this test, this will be addressed in SEP 31+38 tests + if (quotesRequired) { + this.context.provides.transactionJson = null; + return result; + } const validationResult = validate( this.context.expects.transactionJson, getTransactionSchema, diff --git a/@stellar/anchor-tests/src/tests/sep31And38/tests.ts b/@stellar/anchor-tests/src/tests/sep31And38/tests.ts new file mode 100644 index 0000000..4d95e1e --- /dev/null +++ b/@stellar/anchor-tests/src/tests/sep31And38/tests.ts @@ -0,0 +1,3 @@ +import { default as transactionsTests } from "./transactions"; + +export default transactionsTests; diff --git a/@stellar/anchor-tests/src/tests/sep31And38/transactions.ts b/@stellar/anchor-tests/src/tests/sep31And38/transactions.ts new file mode 100644 index 0000000..82dabab --- /dev/null +++ b/@stellar/anchor-tests/src/tests/sep31And38/transactions.ts @@ -0,0 +1,275 @@ +import { Request } from "node-fetch"; +import { validate } from "jsonschema"; +import { Keypair, Memo } from "stellar-sdk"; + +import { Test, Result, Config, NetworkCall } from "../../types"; +import { hasDirectPaymentServer } from "../sep31/toml"; +import { differentMemosSameAccount } from "../sep12/putCustomer"; +import { canCreateQuote } from "../sep38/postQuote"; +import { genericFailures, makeFailure } from "../../helpers/failure"; +import { makeRequest } from "../../helpers/request"; +import { + getTransactionSchema, + postTransactionsSchema, +} from "../../schemas/sep31"; +import { hasExpectedAssetEnabled } from "../sep31/info"; +import { getQuotesSupportedFromInfo } from "../../helpers/sep31"; + +const postTransactionsGroup = "POST /transactions"; +const getTransactionsGroup = "GET /transactions/:id"; +const tests: Test[] = []; + +let quotesSupported: boolean; + +const canCreateTransaction: Test = { + assertion: "[with SEP-38, quotes_required] can create a transaction", + sep: 31, + group: postTransactionsGroup, + dependencies: [ + hasDirectPaymentServer, + hasExpectedAssetEnabled, + differentMemosSameAccount, + canCreateQuote, + ], + context: { + expects: { + directPaymentServerUrl: undefined, + sendingAnchorClientKeypair: undefined, + sendingAnchorToken: undefined, + sendingCustomerId: undefined, + sep38QuoteResponseObj: undefined, + receivingCustomerId: undefined, + sep31InfoObj: undefined, + }, + provides: { + transactionId: undefined, + }, + }, + failureModes: { + INVALID_SCHEMA: { + name: "invalid schema", + text(args: any): string { + return ( + "The response body from POST /transactions does not match " + + "the schema defined by the protocol. " + + "Errors:\n\n" + + `${args.errors}` + ); + }, + links: { + "POST Transaction Schema": + "https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0031.md#post-transactions", + }, + }, + ...genericFailures, + }, + async run(config: Config): Promise { + const result: Result = { networkCalls: [] }; + if (!config.sepConfig || !config.sepConfig["31"]) { + // this configuration is checked prior to running tests + // but to satisfy TypeScript we make these checks here. + throw "improperly configured"; + } + + let assetCode = config.assetCode; + if (assetCode === undefined) { + throw "asset not configured"; + } + quotesSupported = getQuotesSupportedFromInfo( + this.context.expects.sep31InfoObj, + assetCode, + ); + + // If quotes are not supported, ignore this test, this will be addressed in SEP 31 tests + if (!quotesSupported) { + this.context.provides.transactionId = null; + return result; + } + + const splitAsset = + this.context.expects.sep38QuoteResponseObj.sell_asset.split(":", 3); + const postTransactionsCall: NetworkCall = { + request: new Request( + this.context.expects.directPaymentServerUrl + "/transactions", + { + method: "POST", + headers: { + Authorization: `Bearer ${this.context.expects.sendingAnchorToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + sender_id: this.context.expects.sendingCustomerId, + receiver_id: this.context.expects.receivingCustomerId, + amount: 100, + asset_code: splitAsset[1], + quote_id: this.context.expects.sep38QuoteResponseObj.id, + fields: { + transaction: { + ...config.sepConfig["31"].transactionFields, + }, + }, + }), + }, + ), + }; + result.networkCalls.push(postTransactionsCall); + const responseBody = await makeRequest( + postTransactionsCall, + 201, + result, + "application/json", + ); + if (!responseBody) return result; + const validationResult = validate(responseBody, postTransactionsSchema); + if (validationResult.errors.length !== 0) { + result.failure = makeFailure(this.failureModes.INVALID_SCHEMA, { + errors: validationResult.errors.join("\n"), + }); + return result; + } + try { + Keypair.fromPublicKey(responseBody.stellar_account_id); + } catch { + result.failure = makeFailure(this.failureModes.INVALID_SCHEMA, { + errors: "'stellar_acocunt_id' must be a valid Stellar public key", + }); + return result; + } + let memoValue = responseBody.stellar_memo; + if (responseBody.stellar_memo_type === "hash") { + memoValue = Buffer.from(responseBody.stellar_memo, "base64"); + } + try { + new Memo(responseBody.stellar_memo_type, memoValue); + } catch { + result.failure = makeFailure(this.failureModes.INVALID_SCHEMA, { + errors: "invalid 'stellar_memo' for 'stellar_memo_type'", + }); + return result; + } + this.context.provides.transactionId = responseBody.id; + return result; + }, +}; +tests.push(canCreateTransaction); + +const canFetchTransaction: Test = { + assertion: "[with SEP-38, quotes_required] can fetch a created transaction", + sep: 31, + group: getTransactionsGroup, + dependencies: [hasDirectPaymentServer, canCreateTransaction], + context: { + expects: { + sendingAnchorToken: undefined, + directPaymentServerUrl: undefined, + transactionId: undefined, + }, + provides: { + transactionJson: undefined, + }, + }, + failureModes: genericFailures, + async run(_config: Config): Promise { + const result: Result = { networkCalls: [] }; + + // If quotes are not supported, ignore this test, this will be addressed in SEP 31 tests + if (!quotesSupported) { + this.context.provides.transactionJson = null; + return result; + } + + const getTransactionCall: NetworkCall = { + request: new Request( + this.context.expects.directPaymentServerUrl + + `/transactions/${this.context.expects.transactionId}`, + { + headers: { + Authorization: `Bearer ${this.context.expects.sendingAnchorToken}`, + }, + }, + ), + }; + result.networkCalls.push(getTransactionCall); + this.context.provides.transactionJson = await makeRequest( + getTransactionCall, + 200, + result, + "application/json", + ); + return result; + }, +}; +tests.push(canFetchTransaction); + +const hasValidTransactionJson: Test = { + assertion: + "[with SEP-38, quotes_required] response body complies with protocol schema", + sep: 31, + group: getTransactionsGroup, + dependencies: [canFetchTransaction], + context: { + expects: { + transactionId: undefined, + transactionJson: undefined, + }, + provides: {}, + }, + failureModes: { + INVALID_SCHEMA: { + name: "invalid schema", + text(args: any): string { + return ( + "The response body from GET /transactions/:id does not match " + + "the schema defined by the protocol. " + + "Errors:\n\n" + + `${args.errors}` + ); + }, + links: { + "Transactions Schema": + "https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0031.md#get-transaction", + }, + }, + INVALID_ID: { + name: "invalid 'id'", + text(_args: any): string { + return "The 'id' value returned in the response body does match the ID used to fetch the transaction"; + }, + links: { + "Transactions Schema": + "https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0031.md#get-transaction", + }, + }, + }, + async run(_config: Config): Promise { + const result: Result = { networkCalls: [] }; + // If quotes are not supported, ignore this test, this will be addressed in SEP 31 tests + if (!quotesSupported) { + return result; + } + + const validationResult = validate( + this.context.expects.transactionJson, + getTransactionSchema, + ); + if (validationResult.errors.length !== 0) { + result.failure = makeFailure(this.failureModes.INVALID_SCHEMA, { + errors: validationResult.errors.join("\n"), + }); + return result; + } + if ( + this.context.expects.transactionId !== + this.context.expects.transactionJson.transaction.id + ) { + result.failure = makeFailure(this.failureModes.INVALID_ID); + result.expected = this.context.expects.transactionId; + result.actual = this.context.expects.transactionJson.transaction.id; + return result; + } + return result; + }, +}; +tests.push(hasValidTransactionJson); + +export default tests; diff --git a/CHANGELOG.md b/CHANGELOG.md index a38fd6c..ca44432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ This changelog documents all releases and included changes to the @stellar/ancho A breaking change will get clearly marked in this log. +## [v0.5.1](https://github.com/stellar/stellar-anchor-tests/compare/v0.5.0...v0.5.1) + +### Update + +- Add SEP-31+38 tests ([#90](https://github.com/stellar/stellar-anchor-tests/pull/90)) + - SEP-31+38 tests run when both SEPs are enabled and `quotes_required` is true. + - SEP-31 tests now test for failure if SEP-38 is not implemented and `quotes_supported` is true. + +### Update + ## [v0.5.0](https://github.com/stellar/stellar-anchor-tests/compare/v0.4.1...v0.5.0) ### Update diff --git a/server/package.json b/server/package.json index 66e508f..357db01 100644 --- a/server/package.json +++ b/server/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@types/node": "^14.14.41", - "@stellar/anchor-tests": "0.5.0", + "@stellar/anchor-tests": "0.5.1", "express": "^4.17.1", "socket.io": "^4.1.2", "tslib": "^2.2.0",