Skip to content
179 changes: 110 additions & 69 deletions src/__tests__/resources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,29 +112,29 @@ function lastJsonLog(): Record<string, unknown> {

const BASE = 'https://api.blindpay.com/v1/instances/in_testInstance'

describe('Receivers', () => {
describe('Customers', () => {
beforeEach(setupTestEnv)
afterEach(teardownTestEnv)

test('lists receivers', async () => {
mockResponse.body = { data: [{ id: 're_1', type: 'individual', email: 'a@b.com' }] }
await resources.listReceivers({ json: true })
test('lists customers', async () => {
mockResponse.body = [{ id: 're_1', type: 'individual', email: 'a@b.com' }]
await resources.listCustomers({ json: true })
expect(lastCall().method).toBe('GET')
expect(lastCall().url).toBe(`${BASE}/receivers`)
expect(lastCall().url).toBe(`${BASE}/customers`)
})

test('fetches a receiver by id', async () => {
test('fetches a customer by id', async () => {
mockResponse.body = { id: 're_xyz' }
await resources.getReceiver('re_xyz', { json: true })
await resources.getCustomer('re_xyz', { json: true })
expect(lastCall().method).toBe('GET')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz`)
})

test('creates a receiver, splitting --name into first_name and last_name and filling defaults', async () => {
test('creates a customer, splitting --name into first_name and last_name and filling defaults', async () => {
mockResponse.body = { id: 're_new', type: 'individual' }
await resources.createReceiver({ email: 'a@b.com', name: 'Jane Doe', json: true })
await resources.createCustomer({ email: 'a@b.com', name: 'Jane Doe', json: true })
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/receivers`)
expect(lastCall().url).toBe(`${BASE}/customers`)
expect(lastCall().body).toEqual({
type: 'individual',
email: 'a@b.com',
Expand All @@ -150,34 +150,34 @@ describe('Receivers', () => {

test('updates only the fields explicitly passed via flags', async () => {
mockResponse.body = { id: 're_xyz' }
await resources.updateReceiver('re_xyz', { email: 'new@b.com', json: true })
await resources.updateCustomer('re_xyz', { email: 'new@b.com', json: true })
expect(lastCall().method).toBe('PUT')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz`)
expect(lastCall().body).toEqual({ email: 'new@b.com' })
})

test('deletes a receiver by id', async () => {
test('deletes a customer by id', async () => {
mockResponse.body = { success: true }
await resources.deleteReceiver('re_xyz', { json: true })
await resources.deleteCustomer('re_xyz', { json: true })
expect(lastCall().method).toBe('DELETE')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz`)
})

test('fetches receiver limits', async () => {
test('fetches customer limits', async () => {
mockResponse.body = { per_transaction: 100 }
await resources.getReceiverLimits('re_xyz', { json: true })
expect(lastCall().url).toBe(`${BASE}/limits/receivers/re_xyz`)
await resources.getCustomerLimits('re_xyz', { json: true })
expect(lastCall().url).toBe(`${BASE}/limits/customers/re_xyz`)
})

test('fetches receiver limit-increase requests', async () => {
test('fetches customer limit-increase requests', async () => {
mockResponse.body = []
await resources.getReceiverLimitsIncreaseRequests('re_xyz', { json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/limit-increase`)
await resources.getCustomerLimitIncreaseRequests('re_xyz', { json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/limit-increase`)
})

test('creates a limit-increase request with monetary fields parsed as integers', async () => {
mockResponse.body = { id: 'rl_new' }
await resources.createReceiverLimitIncrease('re_xyz', {
await resources.createCustomerLimitIncrease('re_xyz', {
perTransaction: '100000',
daily: '200000',
monthly: '1000000',
Expand All @@ -186,7 +186,7 @@ describe('Receivers', () => {
json: true,
})
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/limit-increase`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/limit-increase`)
expect(lastCall().body).toEqual({
per_transaction: 100000,
daily: 200000,
Expand All @@ -201,30 +201,30 @@ describe('Bank Accounts', () => {
beforeEach(setupTestEnv)
afterEach(teardownTestEnv)

test('lists bank accounts for a receiver', async () => {
test('lists bank accounts for a customer', async () => {
mockResponse.body = []
await resources.listBankAccounts({ receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts`)
await resources.listBankAccounts({ customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts`)
})

test('fetches a bank account by id', async () => {
mockResponse.body = { id: 'ba_1' }
await resources.getBankAccount('ba_1', { receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts/ba_1`)
await resources.getBankAccount('ba_1', { customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts/ba_1`)
})

test('creates a bank account, snake_casing keys and defaulting unset fields to null', async () => {
mockResponse.body = { id: 'ba_new', type: 'ach' }
await resources.createBankAccount({
receiverId: 're_xyz',
customerId: 're_xyz',
type: 'ach',
beneficiaryName: 'Jane',
routingNumber: '021000021',
accountNumber: '123456789',
json: true,
})
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts`)
expect(lastCall().body).toEqual({
type: 'ach',
name: 'CLI Bank Account',
Expand All @@ -236,44 +236,85 @@ describe('Bank Accounts', () => {
account_type: null,
account_class: null,
country: null,
swift_ifsc_branch_code: null,
})
})

test('creates a sepa bank account with the full sepa_beneficiary_* field set', async () => {
mockResponse.body = { id: 'ba_sepa', type: 'sepa' }
await resources.createBankAccount({
customerId: 're_xyz',
type: 'sepa',
accountClass: 'individual',
sepaIban: 'DE89370400440532013000',
sepaBeneficiaryBic: 'COBADEFFXXX',
sepaBeneficiaryLegalName: 'Jane Doe',
sepaBeneficiaryAddressLine1: 'Hauptstrasse 1',
sepaBeneficiaryAddressLine2: 'Apt 2',
sepaBeneficiaryCity: 'Berlin',
sepaBeneficiaryStateProvinceRegion: 'BE',
sepaBeneficiaryPostalCode: '10115',
sepaBeneficiaryCountry: 'DE',
json: true,
})
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts`)
expect(lastCall().body).toEqual({
type: 'sepa',
name: 'CLI Bank Account',
recipient_relationship: null,
pix_key: null,
beneficiary_name: null,
routing_number: null,
account_number: null,
account_type: null,
account_class: 'individual',
country: null,
sepa_iban: 'DE89370400440532013000',
sepa_beneficiary_bic: 'COBADEFFXXX',
sepa_beneficiary_legal_name: 'Jane Doe',
sepa_beneficiary_address_line_1: 'Hauptstrasse 1',
sepa_beneficiary_address_line_2: 'Apt 2',
sepa_beneficiary_city: 'Berlin',
sepa_beneficiary_state_province_region: 'BE',
sepa_beneficiary_postal_code: '10115',
sepa_beneficiary_country: 'DE',
})
})

test('deletes a bank account', async () => {
mockResponse.body = { success: true }
await resources.deleteBankAccount('ba_1', { receiverId: 're_xyz', json: true })
await resources.deleteBankAccount('ba_1', { customerId: 're_xyz', json: true })
expect(lastCall().method).toBe('DELETE')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts/ba_1`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts/ba_1`)
})
})

describe('Blockchain Wallets', () => {
beforeEach(setupTestEnv)
afterEach(teardownTestEnv)

test('lists blockchain wallets for a receiver', async () => {
test('lists blockchain wallets for a customer', async () => {
mockResponse.body = []
await resources.listBlockchainWallets({ receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets`)
await resources.listBlockchainWallets({ customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets`)
})

test('fetches a blockchain wallet by id', async () => {
mockResponse.body = { id: 'bw_1' }
await resources.getBlockchainWallet('bw_1', { receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets/bw_1`)
await resources.getBlockchainWallet('bw_1', { customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets/bw_1`)
})

test('creates a blockchain wallet for a receiver', async () => {
test('creates a blockchain wallet for a customer', async () => {
mockResponse.body = { id: 'bw_new', network: 'base' }
await resources.createBlockchainWallet({
receiverId: 're_xyz',
customerId: 're_xyz',
address: '0xabc',
network: 'base',
json: true,
})
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets`)
expect(lastCall().body).toEqual({
address: '0xabc',
network: 'base',
Expand All @@ -284,9 +325,9 @@ describe('Blockchain Wallets', () => {

test('deletes a blockchain wallet', async () => {
mockResponse.body = { success: true }
await resources.deleteBlockchainWallet('bw_1', { receiverId: 're_xyz', json: true })
await resources.deleteBlockchainWallet('bw_1', { customerId: 're_xyz', json: true })
expect(lastCall().method).toBe('DELETE')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/blockchain-wallets/bw_1`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/blockchain-wallets/bw_1`)
})
})

Expand Down Expand Up @@ -515,17 +556,17 @@ describe('Virtual Accounts', () => {
beforeEach(setupTestEnv)
afterEach(teardownTestEnv)

test('lists virtual accounts for a receiver', async () => {
test('lists virtual accounts for a customer', async () => {
mockResponse.body = []
await resources.listVirtualAccounts({ receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/virtual-accounts`)
await resources.listVirtualAccounts({ customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/virtual-accounts`)
})

test('creates a virtual account for a receiver', async () => {
test('creates a virtual account for a customer', async () => {
mockResponse.body = { id: 'va_new' }
await resources.createVirtualAccount({ receiverId: 're_xyz', blockchainWalletId: 'bw_1', json: true })
await resources.createVirtualAccount({ customerId: 're_xyz', blockchainWalletId: 'bw_1', json: true })
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/virtual-accounts`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/virtual-accounts`)
expect(lastCall().body).toEqual({ blockchain_wallet_id: 'bw_1' })
})
})
Expand All @@ -536,8 +577,8 @@ describe('Offramp Wallets', () => {

test('lists offramp wallets for a bank account', async () => {
mockResponse.body = []
await resources.listOfframpWallets({ receiverId: 're_xyz', bankAccountId: 'ba_1', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/bank-accounts/ba_1/offramp-wallets`)
await resources.listOfframpWallets({ customerId: 're_xyz', bankAccountId: 'ba_1', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/bank-accounts/ba_1/offramp-wallets`)
})
})

Expand Down Expand Up @@ -587,49 +628,49 @@ describe('Wallets (custodial)', () => {
beforeEach(setupTestEnv)
afterEach(teardownTestEnv)

test('lists custodial wallets for a receiver', async () => {
test('lists custodial wallets for a customer', async () => {
mockResponse.body = []
await resources.listWallets({ receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets`)
await resources.listWallets({ customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets`)
})

test('fetches a custodial wallet by id', async () => {
mockResponse.body = { id: 'bl_1' }
await resources.getWallet('bl_1', { receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets/bl_1`)
await resources.getWallet('bl_1', { customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets/bl_1`)
})

test('fetches a custodial wallet balance', async () => {
mockResponse.body = { USDC: { amount: 0 } }
await resources.getWalletBalance('bl_1', { receiverId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets/bl_1/balance`)
await resources.getWalletBalance('bl_1', { customerId: 're_xyz', json: true })
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets/bl_1/balance`)
})

test('creates a custodial wallet with --external-id', async () => {
mockResponse.body = { id: 'bl_new', network: 'polygon' }
await resources.createWallet({
receiverId: 're_xyz',
customerId: 're_xyz',
network: 'polygon',
name: 'Main',
externalId: 'ext-1',
json: true,
})
expect(lastCall().method).toBe('POST')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets`)
expect(lastCall().body).toEqual({ network: 'polygon', name: 'Main', external_id: 'ext-1' })
})

test('omits external_id from the body when --external-id is not passed', async () => {
mockResponse.body = { id: 'bl_new', network: 'polygon' }
await resources.createWallet({ receiverId: 're_xyz', network: 'polygon', name: 'Main', json: true })
await resources.createWallet({ customerId: 're_xyz', network: 'polygon', name: 'Main', json: true })
expect(lastCall().body).toEqual({ network: 'polygon', name: 'Main' })
})

test('deletes a custodial wallet', async () => {
mockResponse.body = { success: true }
await resources.deleteWallet('bl_1', { receiverId: 're_xyz', json: true })
await resources.deleteWallet('bl_1', { customerId: 're_xyz', json: true })
expect(lastCall().method).toBe('DELETE')
expect(lastCall().url).toBe(`${BASE}/receivers/re_xyz/wallets/bl_1`)
expect(lastCall().url).toBe(`${BASE}/customers/re_xyz/wallets/bl_1`)
})
})

Expand Down Expand Up @@ -732,7 +773,7 @@ describe('Terms of Service', () => {
mockResponse.body = { url: 'https://app.blindpay.com/e/terms-of-service?...' }
await resources.initiateTos({
idempotencyKey: 'idem-1',
receiverId: 're_xyz',
customerId: 're_xyz',
redirectUrl: 'https://example.com',
json: true,
})
Expand Down Expand Up @@ -785,13 +826,13 @@ describe('Error handling', () => {
afterEach(teardownTestEnv)

test('exits with code 2 and emits a JSON error payload on a non-2xx response', async () => {
mockResponse = { status: 404, body: { message: 'receiver not found', errors: [] } }
await expect(resources.getReceiver('re_xyz', { json: true })).rejects.toThrow('__test_exit__2')
mockResponse = { status: 404, body: { message: 'customer not found', errors: [] } }
await expect(resources.getCustomer('re_xyz', { json: true })).rejects.toThrow('__test_exit__2')
expect(lastJsonLog()).toMatchObject({
error: true,
exitCode: 2,
statusCode: 404,
message: 'receiver not found',
message: 'customer not found',
})
})

Expand All @@ -803,7 +844,7 @@ describe('Error handling', () => {
errors: [{ path: ['email'], message: 'required' }],
},
}
await expect(resources.createReceiver({ email: 'x', json: true })).rejects.toThrow('__test_exit__2')
await expect(resources.createCustomer({ email: 'x', json: true })).rejects.toThrow('__test_exit__2')
expect(lastJsonLog()).toMatchObject({
error: true,
exitCode: 2,
Expand Down
Loading
Loading