@govuk-pay/run-amock
Version:
A drop-in replacement for Mountebank in our govuk-pay codebases.
1,112 lines (1,061 loc) • 36.6 kB
JavaScript
import * as net from 'node:net'
import { afterEach, beforeEach, describe, it } from 'node:test'
import * as assert from 'node:assert'
import { imposterClearUrl, imposterSetupUrl } from './constants.js'
import { httpPostJson, httpPatchJson } from './utils.js'
async function findAvailablePort () {
return await new Promise((resolve) => {
let port
const tmpServer = net.createServer(function (sock) {
sock.end('Hello world\n')
})
tmpServer.listen(0, function () {
port = tmpServer.address().port
tmpServer.close(() => {
resolve(port)
})
})
})
}
const mockPort = await findAvailablePort()
const mountebankConfig = {
name: 'mountebank',
imposterSetupUrl: 'http://localhost:2525/imposters',
imposterClearUrl: 'http://localhost:2525/imposters',
imposterClearMethod: 'DELETE',
mockedHttpBaseUrl: `http://localhost:${mockPort}`,
mockPort
}
const selfConfig = {
name: 'run-amock',
imposterSetupUrl,
imposterClearUrl,
imposterClearMethod: 'POST',
mockedHttpBaseUrl: 'http://localhost:9999',
mockPort: 9999
}
async function clearAllMocks (config) {
const result = await fetch(config.imposterClearUrl, { method: config.imposterClearMethod })
assert.equal(200, result.status, `Failed to clear all mocks, status code [${result.status}], body [${await result.text()}]`)
}
async function setupImposters (config, imposterSetupBody) {
const setupResult = await httpPostJson(config.imposterSetupUrl, imposterSetupBody)
const setupResultBody = await setupResult.text()
assert.equal(201, setupResult.status, `failed setup, status code: [${setupResult.status}], response body: [${setupResultBody}]`)
}
function jsonString (main, ...parts) {
const outParts = []
main.forEach((item, index) => {
outParts.push(item)
if (parts[index] !== undefined) {
outParts.push(JSON.stringify(parts[index]))
}
})
return outParts.join('')
}
const testRunConfigs = []
if (process.env.TEST_MB === 'true') {
testRunConfigs.push(mountebankConfig)
}
if (process.env.TEST_SELF !== 'false') {
testRunConfigs.push(selfConfig)
}
testRunConfigs.forEach(config => {
beforeEach(async () => {
await clearAllMocks(config)
})
afterEach(async () => {
await clearAllMocks(config)
})
describe('equality-with-mountebank', () => {
it(`should give one response multiple times, and respect deletes (${config.name})`, async () => {
const fakeBody = {
abc: 'def'
}
const statusCode = 200
const path = '/a'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
stubs: [
{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'GET',
path
}
}
],
responses: [
{
is: {
statusCode,
headers: {
'Content-Type': 'application/json'
},
body: fakeBody
}
}
]
}
]
})
let remainingRequests = 5
const fullMockUrl = config.mockedHttpBaseUrl + path
while (remainingRequests-- > 0) {
const result = await fetch(fullMockUrl)
const responseBody = await result.json()
const responseStatusCode = result.status
assert.equal(statusCode, responseStatusCode, `Response from [${fullMockUrl}] was [${responseStatusCode}], expected [${statusCode}]`)
assert.deepEqual(fakeBody, responseBody)
}
await clearAllMocks(config)
try {
const resultAfterDelete = await fetch(fullMockUrl)
const statusAfterDelete = resultAfterDelete.status
assert.equal(404, statusAfterDelete, `Response from [${fullMockUrl}] was [${statusAfterDelete}], expected [404]`)
} catch (error) {
// ignoring because an error is one valid way to handle this
}
})
it(`should alternate between two responses (${config.name})`, async () => {
const fakeBody1 = {
abc: 'def'
}
const fakeBody2 = {
ghi: 'jkl'
}
const imposterSetupBody = {
port: mockPort,
protocol: 'http',
stubs: [
{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'GET',
path: '/b'
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: fakeBody1
}
},
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: fakeBody2
}
}
]
}
]
}
await setupImposters(config, imposterSetupBody)
let counter = 0
const fullMockUrl = config.mockedHttpBaseUrl + '/b'
while (counter++ < 5) {
const result1 = await fetch(fullMockUrl)
const responseBody1 = await result1.json()
const responseStatusCode1 = result1.status
assert.equal(200, responseStatusCode1, `Response from [${fullMockUrl}] was [${responseStatusCode1}], expected [${200}]`)
assert.deepEqual(fakeBody1, responseBody1, jsonString`Expected response body (1) (repeat counter: [${counter}]) [${responseBody1}] to equal the configured output [${fakeBody1}]`)
const result2 = await fetch(fullMockUrl)
const responseBody2 = await result2.json()
const responseStatusCode2 = result2.status
assert.equal(200, responseStatusCode2, `Response from [${fullMockUrl}] was [${responseStatusCode1}], expected [${200}]`)
assert.deepEqual(fakeBody2, responseBody2, jsonString`Expected response body (2) (repeat counter: [${counter}]) [${responseBody2}] to equal the configured output [${fakeBody2}]`)
}
})
})
it(`should respond with the default response when configured (${config.name})`, async () => {
const fakeBody = 'No stub predicate matches the request'
const statusCode = 404
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode, body: fakeBody, headers: {} },
stubs: []
})
const fullMockUrl = config.mockedHttpBaseUrl + '/abc'
const result = await fetch(fullMockUrl)
const responseBody = await result.text()
const responseStatusCode = result.status
assert.equal(statusCode, responseStatusCode, `Response from [${fullMockUrl}] was [${responseStatusCode}], expected [${statusCode}]`)
assert.deepEqual(fakeBody, responseBody)
})
it(`should allow deep query string matching in predicates (${config.name})`, async () => {
const url = '/example'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'Default 404', headers: {} },
stubs: [
{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'GET',
path: url,
query: {
a: 'b',
c: 'd',
e: 'f'
}
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
hello: 'world'
}
}
}
]
}
]
})
const fullMockUrl = config.mockedHttpBaseUrl + url
const acceptableQueryStrings = [
'a=b&c=d&e=f',
'e=f&a=b&c=d',
'e=f&a=b&c=d&'
]
const unacceptableQueryStrings = [
'something=true&a=b&example=here&c=d&hello=world&e=f',
'a=b&c=d&e=g',
'a=c&c=d&e=g'
]
await Promise.all(acceptableQueryStrings.map(async (queryString) => {
const successResult = await fetch(fullMockUrl + '?' + queryString)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}] for query string [${queryString}]`)
}))
await Promise.all(unacceptableQueryStrings.map(async (queryString) => {
const successResult = await fetch(fullMockUrl + '?' + queryString)
assert.equal(404, successResult.status, `Expected a failure response from [${fullMockUrl}], got a [${successResult.status}] for query string [${queryString}]`)
}))
})
it(`should be able to distinguish between the presence and absence of a query string (option A) (${config.name})`, async () => {
const url = '/example'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'Default 404', headers: {} },
stubs: [
{
name: `With no query string`,
predicates: [
{
deepEquals: {
method: 'GET',
path: url,
query: {}
}
}
],
responses: [
{
is: {
statusCode: 301
}
}
]
},
{
name: `With a query string`,
predicates: [
{
deepEquals: {
method: 'GET',
path: url,
query: {
a: 'b'
}
}
}
],
responses: [
{
is: {
statusCode: 302
}
}
]
}
]
})
const fullMockUrl = config.mockedHttpBaseUrl + url
const noQueryStringResponse = await fetch(fullMockUrl)
assert.equal(301, noQueryStringResponse.status, `Expected a 301 response from [${fullMockUrl}], got a [${noQueryStringResponse.status}] for no query string`)
const queryString = 'a=b'
const withQueryStringResponse = await fetch(fullMockUrl + '?' + queryString)
assert.equal(302, withQueryStringResponse.status, `Expected a 302 response from [${fullMockUrl}], got a [${withQueryStringResponse.status}] for query string [${queryString}]`)
})
it(`should type correct query strings (${config.name})`, async () => {
const url = '/example'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'Default 404', headers: {} },
stubs: [
{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'GET',
path: url,
query: {
agreement_id: 'a-valid-agreement-id',
display_size: 5,
account_id: 10,
page: 1,
limit_total: true,
limit_total_size: 5001
}
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
hello: 'world'
}
}
}
]
}
]
})
const queryString = '?account_id=10&limit_total=true&limit_total_size=5001&agreement_id=a-valid-agreement-id&page=1&display_size=5'
const fullMockUrl = config.mockedHttpBaseUrl + url + queryString
const successResult = await fetch(fullMockUrl)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}] for query string [${queryString}]`)
})
it(`should allow partial query string matching in predicates (${config.name})`, async () => {
const url = '/example'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'Default 404', headers: {} },
stubs: [
{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
equals: {
method: 'GET',
path: url,
query: {
a: 'b',
c: 'd',
e: 'f'
}
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
hello: 'world'
}
}
}
]
}
]
})
const fullMockUrl = config.mockedHttpBaseUrl + url
const acceptableQueryStrings = [
'a=b&c=d&e=f',
'e=f&a=b&c=d',
'e=f&a=b&c=d&',
'something=true&a=b&example=here&c=d&hello=world&e=f'
]
const unacceptableQueryStrings = [
'a=b&c=d&e=g',
'a=c&c=d&e=g'
]
await Promise.all(acceptableQueryStrings.map(async (queryString) => {
const successResult = await fetch(fullMockUrl + '?' + queryString)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}]`)
}))
await Promise.all(unacceptableQueryStrings.map(async (queryString) => {
const successResult = await fetch(fullMockUrl + '?' + queryString)
assert.equal(404, successResult.status, `Expected a failure response from [${fullMockUrl}], got a [${successResult.status}]`)
}))
})
it(`should allow deep body matching in predicates (${config.name})`, async () => {
const uri = '/v1/api/services/a-service-external-id'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'No stub predicate matches the request', headers: {} },
stubs: [{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'PATCH',
path: uri,
body: {
op: 'replace',
path: 'default_billing_address_country',
value: 'GB'
}
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
matched: true
}
}
}
]
}]
})
const accepatableBodies = [
{
op: 'replace',
path: 'default_billing_address_country',
value: 'GB'
}
]
const fullMockUrl = config.mockedHttpBaseUrl + uri
await Promise.all(accepatableBodies.map(async (body) => {
const successResult = await httpPatchJson(fullMockUrl, body)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}]`)
}))
})
it(`should allow partial body matching in predicates (${config.name})`, async () => {
const uri = '/v1/api/services/a-service-external-id'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'No stub predicate matches the request', headers: {} },
stubs: [{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
equals: {
method: 'PATCH',
path: uri,
body: {
op: 'replace',
path: 'default_billing_address_country',
value: 'GB'
}
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
matched: true
}
}
}
]
}]
})
const accepatableBodies = [
{
op: 'replace',
path: 'default_billing_address_country',
value: 'GB'
},
{
op: 'replace',
path: 'default_billing_address_country',
value: 'GB',
extra: true
}
]
const fullMockUrl = config.mockedHttpBaseUrl + uri
await Promise.all(accepatableBodies.map(async (body) => {
const successResult = await httpPatchJson(fullMockUrl, body)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}] for body [${JSON.stringify(body)}]`)
}))
const unacceptableBodies = [
{
op: 'replace',
path: 'default_billing_address_country',
extra: true
}
]
await Promise.all(unacceptableBodies.map(async (body) => {
const successResult = await httpPatchJson(fullMockUrl, body)
assert.equal(404, successResult.status, `Expected a failure response from [${fullMockUrl}], got a [${successResult.status}] for body [${JSON.stringify(body)}]`)
}))
})
it(`should allow deep matching for body (${config.name})`, async () => {
const uri = '/v1/api/accounts/42/credentials/101'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'No stub predicate matches the request', headers: {} },
stubs: [{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'PATCH',
path: uri,
body: [
{
op: 'replace',
path: 'credentials/worldpay/recurring_customer_initiated',
value: {
username: 'a-cit-username',
password: 'a-password',
merchant_code: 'a-cit-merchant-code'
}
},
{
op: 'replace',
path: 'last_updated_by_user_external_id',
value: 'cd0fa54cf3b7408a80ae2f1b93e7c16e'
}
]
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
}
}
}
]
}
]
})
const acceptableBodies = [
[
{
op: 'replace',
path: 'credentials/worldpay/recurring_customer_initiated',
value: {
username: 'a-cit-username',
password: 'a-password',
merchant_code: 'a-cit-merchant-code'
}
},
{
op: 'replace',
path: 'last_updated_by_user_external_id',
value: 'cd0fa54cf3b7408a80ae2f1b93e7c16e'
}
],
[
{
path: 'credentials/worldpay/recurring_customer_initiated',
op: 'replace',
value: {
password: 'a-password',
username: 'a-cit-username',
merchant_code: 'a-cit-merchant-code'
}
},
{
path: 'last_updated_by_user_external_id',
op: 'replace',
value: 'cd0fa54cf3b7408a80ae2f1b93e7c16e'
}
],
[
{
path: 'last_updated_by_user_external_id',
op: 'replace',
value: 'cd0fa54cf3b7408a80ae2f1b93e7c16e'
},
{
path: 'credentials/worldpay/recurring_customer_initiated',
op: 'replace',
value: {
password: 'a-password',
username: 'a-cit-username',
merchant_code: 'a-cit-merchant-code'
}
}
]
]
const fullMockUrl = config.mockedHttpBaseUrl + uri
await Promise.all(acceptableBodies.map(async (body) => {
const successResult = await httpPatchJson(fullMockUrl, body)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}]`)
}))
const unacceptableBodies = [
[
{
path: 'credentials/worldpay/recurring_customer_initiated',
op: 'replace',
value: {
password: 'a-password',
username: 'a-cit-username',
merchant_code: 'a-cit-merchant-code'
}
}
]
]
await Promise.all(unacceptableBodies.map(async (body) => {
const successResult = await httpPatchJson(fullMockUrl, body)
assert.equal(404, successResult.status, `Expected a failure response from [${fullMockUrl}], got a [${successResult.status}]`)
}))
})
it(`should allow null in request body (${config.name})`, async () => {
const uri = '/v1/api/services/service-456-def'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
defaultResponse: { statusCode: 404, body: 'No stub predicate matches the request', headers: {} },
stubs: [
{
name: `The name doesn't matter (unique: ${Math.random()})`,
"predicates": [
{
"deepEquals": {
"method": "PATCH",
"path": uri,
"body": {
"op": "replace",
"path": "default_billing_address_country",
"value": null
}
}
}
],
"responses": [
{
"is": {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"id": 857,
"external_id": "service-456-def",
"name": "System Generated",
"gateway_account_ids": [
11
],
"service_name": {
"en": "System Generated"
},
"redirect_to_service_immediately_on_terminal_state": false,
"collect_billing_address": false,
"current_go_live_stage": "NOT_STARTED",
"experimental_features_enabled": true,
"current_psp_test_account_stage": "NOT_STARTED",
"agent_initiated_moto_enabled": false,
"takes_payments_over_phone": false,
"created_date": "2024-08-30"
}
}
}
]
}
]
})
const fullMockUrl = config.mockedHttpBaseUrl + uri
const acceptableBody = {
op: 'replace',
path: 'default_billing_address_country',
value: null
}
const successResult = await httpPatchJson(fullMockUrl, acceptableBody)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}]`)
const unacceptableBodies = [
{
op: 'replace',
path: 'default_billing_address_country',
value: undefined
},
{
op: 'replace',
path: 'default_billing_address_country'
},
{
op: 'replace',
path: 'default_billing_address_country',
value: 'something'
}
]
await Promise.all(unacceptableBodies.map(async (body) => {
const successResult = await httpPatchJson(fullMockUrl, body)
assert.equal(404, successResult.status, `Expected a failure response from [${fullMockUrl}], got a [${successResult.status}]`)
}))
})
it(`should work with Frontend tests for wallets (${config.name})`, async () => {
const uri = '/v1/frontend/charges/ub8de8r5mh4pb49rgm1ismaqfv'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
recordRequests: false,
stubs: [
{
predicates: [
{
equals: {
method: 'GET',
path: uri
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
amount: 1000,
state: {
finished: false,
status: 'created'
},
description: 'Example fixture payment',
payment_provider: 'worldpay',
language: 'en',
status: 'CREATED',
charge_id: 'ub8de8r5mh4pb49rgm1ismaqfv',
return_url: '/humans.txt?confirm',
created_date: '2019-02-12T17:53:31.307Z',
delayed_capture: false,
moto: true,
gateway_account: {
gateway_account_id: 6,
allow_apple_pay: false,
allow_google_pay: true,
gateway_merchant_id: 'SMTHG12345UP',
analytics_id: 'an-analytics-id',
corporate_credit_card_surcharge_amount: 0,
corporate_debit_card_surcharge_amount: 0,
corporate_prepaid_debit_card_surcharge_amount: 0,
email_collection_mode: 'MANDATORY',
block_prepaid_cards: false,
requires3ds: true,
service_name: 'My service',
type: 'test',
integration_version_3ds: 2,
card_types: [
{
brand: 'visa',
id: 'b9dae820-0d11-4280-b8bb-6a3a320b7e7a',
label: 'Visa',
requires3ds: false,
type: 'DEBIT'
},
{
brand: 'visa',
id: 'b2c53a34-8566-4050-963f-7f20b43f3650',
label: 'Visa',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'master-card',
id: 'e33c7b30-f2f5-4d9d-ac00-a61d598d353e',
label: 'Mastercard',
requires3ds: false,
type: 'DEBIT'
},
{
brand: 'master-card',
id: 'f9adcba5-3c8e-4bbb-bb29-3d8f1cf152fc',
label: 'Mastercard',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'american-express',
id: '9cb3f107-391b-4ca9-a42e-27e8a0b277a2',
label: 'American Express',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'diners-club',
id: 'c36f5a66-ce27-462f-a971-76783eed40e7',
label: 'Diners Club',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'discover',
id: 'e0f2590d-219f-4627-b693-43fa8bb41583',
label: 'Discover',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'jcb',
id: '45714fbd-ca7b-4900-9777-084fe5b223be',
label: 'Jcb',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'unionpay',
id: 'acd5ca07-d27d-43b6-9e2a-bb42699dc644',
label: 'Union Pay',
requires3ds: false,
type: 'CREDIT'
}
],
moto_mask_card_number_input: false,
moto_mask_card_security_code_input: false
},
agreement_id: 'an-agreement-id',
agreement: {
agreement_id: 'an-agreement-id',
description: 'a valid description',
reference: 'a-valid-reference'
},
save_payment_instrument_to_agreement: true
}
},
_behaviours: {
repeat: 1
}
},
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
amount: 1000,
state: {
finished: true,
status: 'success'
},
description: 'Example fixture payment',
payment_provider: 'worldpay',
language: 'en',
status: 'CAPTURE APPROVED',
charge_id: 'ub8de8r5mh4pb49rgm1ismaqfv',
return_url: '/humans.txt?confirm',
created_date: '2019-02-12T17:53:31.307Z',
delayed_capture: false,
moto: false,
gateway_account: {
gateway_account_id: 6,
allow_apple_pay: false,
allow_google_pay: true,
gateway_merchant_id: 'SMTHG12345UP',
analytics_id: 'an-analytics-id',
corporate_credit_card_surcharge_amount: 0,
corporate_debit_card_surcharge_amount: 0,
corporate_prepaid_debit_card_surcharge_amount: 0,
email_collection_mode: 'MANDATORY',
block_prepaid_cards: false,
requires3ds: true,
service_name: 'My service',
type: 'test',
integration_version_3ds: 2,
card_types: [
{
brand: 'visa',
id: 'b9dae820-0d11-4280-b8bb-6a3a320b7e7a',
label: 'Visa',
requires3ds: false,
type: 'DEBIT'
},
{
brand: 'visa',
id: 'b2c53a34-8566-4050-963f-7f20b43f3650',
label: 'Visa',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'master-card',
id: 'e33c7b30-f2f5-4d9d-ac00-a61d598d353e',
label: 'Mastercard',
requires3ds: false,
type: 'DEBIT'
},
{
brand: 'master-card',
id: 'f9adcba5-3c8e-4bbb-bb29-3d8f1cf152fc',
label: 'Mastercard',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'american-express',
id: '9cb3f107-391b-4ca9-a42e-27e8a0b277a2',
label: 'American Express',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'diners-club',
id: 'c36f5a66-ce27-462f-a971-76783eed40e7',
label: 'Diners Club',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'discover',
id: 'e0f2590d-219f-4627-b693-43fa8bb41583',
label: 'Discover',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'jcb',
id: '45714fbd-ca7b-4900-9777-084fe5b223be',
label: 'Jcb',
requires3ds: false,
type: 'CREDIT'
},
{
brand: 'unionpay',
id: 'acd5ca07-d27d-43b6-9e2a-bb42699dc644',
label: 'Union Pay',
requires3ds: false,
type: 'CREDIT'
}
],
moto_mask_card_number_input: false,
moto_mask_card_security_code_input: false
},
agreement_id: 'an-agreement-id',
agreement: {
agreement_id: 'an-agreement-id',
description: 'a valid description',
reference: 'a-valid-reference'
},
save_payment_instrument_to_agreement: true
}
}
}
]
}
]
})
const fullMockUrl = config.mockedHttpBaseUrl + uri
const expectedStatusesInOrder = ['CREATED', 'CAPTURE APPROVED', 'CREATED', 'CAPTURE APPROVED', 'CREATED', 'CAPTURE APPROVED', 'CREATED', 'CAPTURE APPROVED']
const actualStatusesInOrder = []
while (expectedStatusesInOrder.length > actualStatusesInOrder.length) {
const successResult = await fetch(fullMockUrl)
assert.equal(200, successResult.status, `Expected a success response from [${fullMockUrl}], got a [${successResult.status}]`)
const jsonBody = await successResult.json()
const jsonBodyStatus = jsonBody.status
actualStatusesInOrder.push(jsonBodyStatus)
}
assert.deepEqual(actualStatusesInOrder, expectedStatusesInOrder)
})
it(`should use provided headers (${config.name})`, async () => {
const uri = '/hello/world'
const uri2 = '/hello/world2'
await setupImposters(config, {
port: mockPort,
protocol: 'http',
stubs: [{
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'GET',
path: uri
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'X-Powered-By': 'Love'
},
body: {
matched: true
}
}
}
]
}, {
name: `The name doesn't matter (unique: ${Math.random()})`,
predicates: [
{
deepEquals: {
method: 'GET',
path: uri2
}
}
],
responses: [
{
is: {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'X-Powered-By': 'Vengeance'
},
body: {
matched: true
}
}
}
]
}]
})
const fullMockUrl1 = config.mockedHttpBaseUrl + uri
const result1 = await fetch(fullMockUrl1)
assert.equal('Love', result1.headers.get('x-powered-by'))
const fullMockUrl2 = config.mockedHttpBaseUrl + uri
const result2 = await fetch(fullMockUrl2)
assert.equal('Love', result2.headers.get('x-powered-by'))
})
})