From 97fc5b4361d4e3df08e34847b59d1cc837225690 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 10 May 2026 21:23:34 -0300 Subject: [PATCH 1/2] Add sandbox routing smoke coverage --- modules/investec.js | 2 +- package.json | 2 +- routes/investec.js | 6 +++--- routes/investec/auth.js | 4 +++- tests/sandbox-smoke.js | 41 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 tests/sandbox-smoke.js diff --git a/modules/investec.js b/modules/investec.js index 96a9f0a..19999ad 100644 --- a/modules/investec.js +++ b/modules/investec.js @@ -61,7 +61,7 @@ class Investec { } async get(path) { - await axios.get(path, this.axiosConfig()).catch(logError) + return await axios.get(path, this.axiosConfig()).catch(logError) } async getWithAuth(path) { diff --git a/package.json b/package.json index 8e92ba3..30617c8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node tests/sandbox-smoke.js" }, "keywords": [], "author": "", diff --git a/routes/investec.js b/routes/investec.js index e94919d..e280ec3 100644 --- a/routes/investec.js +++ b/routes/investec.js @@ -31,7 +31,7 @@ router.get('/za/v1/cards', async (_req, _res) => { router.get('/*', async (_req, _res) => { const investec = new Investec(_req.currentUser.token) const response = await investec.getWithAuth(_req.url) - _res.json(response.data) + _res.status(response.status || 200).json(response.data) }) // proxies any POST request to the investec adapter @@ -39,7 +39,7 @@ router.get('/*', async (_req, _res) => { router.post('/*', async (_req, _res) => { const investec = new Investec(_req.currentUser.token) const response = await investec.postWithAuth(_req.url, _req.body) - _res.json(response.data) + _res.status(response.status || 200).json(response.data) }) -module.exports = router \ No newline at end of file +module.exports = router diff --git a/routes/investec/auth.js b/routes/investec/auth.js index 7a76fc1..2fa066d 100644 --- a/routes/investec/auth.js +++ b/routes/investec/auth.js @@ -2,6 +2,9 @@ function splitPartitionFromToken(partitionedToken) { // pulls out an optional partition (subfolder) from API credentials // which is used when filtering Investec API responses to show only // certain cards + if (partitionedToken == "SANDBOX") { + return { token: "SANDBOX", username: "SANDBOX", partition: undefined } + } const partitionedCredentials = new Buffer(partitionedToken, 'base64').toString() const [partitionedUsername, password] = partitionedCredentials.split(":") @@ -16,7 +19,6 @@ function getAuth(_req, _res, next) { if (authType == "Basic") { let { token, username, partition } = splitPartitionFromToken(partitionedToken) _req.currentUser = { username, partition, token } - console.log(_req.currentUser) } if (authType == "RootCard") { diff --git a/tests/sandbox-smoke.js b/tests/sandbox-smoke.js new file mode 100644 index 0000000..a06944e --- /dev/null +++ b/tests/sandbox-smoke.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const Investec = require('../modules/investec') +const getAuth = require('../routes/investec/auth') + +async function testSandboxAdapter() { + const sandbox = new Investec('SANDBOX') + + const accountsResponse = await sandbox.getWithAuth('/za/pb/v1/accounts') + assert.strictEqual(accountsResponse.status, 200) + assert.strictEqual(accountsResponse.data.data.accounts.length, 1) + + const accountId = accountsResponse.data.data.accounts[0].accountId + const transactionsResponse = await sandbox.getWithAuth(`/za/pb/v1/accounts/${accountId}/transactions`) + assert.strictEqual(transactionsResponse.status, 200) + assert.strictEqual(transactionsResponse.data.data.transactions.length, 2) + + const missingResponse = await sandbox.getWithAuth('/za/pb/v1/cards') + assert.strictEqual(missingResponse.status, 404) + assert.strictEqual(missingResponse.data.data.path, '/za/pb/v1/cards') +} + +function testSandboxAuthHeader() { + const req = { + headers: { authorization: 'Basic SANDBOX' } + } + + getAuth(req, {}, () => {}) + + assert.deepStrictEqual(req.currentUser, { + username: 'SANDBOX', + partition: undefined, + token: 'SANDBOX' + }) +} + +async function run() { + await testSandboxAdapter() + testSandboxAuthHeader() +} + +run() From cf52b8e8ffadb88456cc0d9433e2210a4b87cc88 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 10 May 2026 21:32:40 -0300 Subject: [PATCH 2/2] Harden Investec proxy error handling --- modules/investec.js | 5 ++++- routes/investec.js | 12 ++++++++++-- routes/investec/auth.js | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/investec.js b/modules/investec.js index 19999ad..9d0379f 100644 --- a/modules/investec.js +++ b/modules/investec.js @@ -1,6 +1,9 @@ const axios = require('axios') const InvestecSandbox = require('./investec_sandbox') -const logError = (error) => console.log(error.toJSON()) +const logError = (error) => { + console.log(error.toJSON ? error.toJSON() : error) + return error.response +} // Simple adapter to fetch bearer tokens and proxy requests to Investec's API diff --git a/routes/investec.js b/routes/investec.js index e280ec3..6e08b97 100644 --- a/routes/investec.js +++ b/routes/investec.js @@ -6,6 +6,14 @@ router.use('/special', require('./investec/special')) const Investec = require('../modules/investec') const cardPartitions = require('./investec/card_partitions') +function sendInvestecResponse(_res, response) { + if (!response) { + return _res.status(502).json({ error: 'No response received from Investec API' }) + } + + return _res.status(response.status || 200).json(response.data) +} + // This route is not a pure proxy because we want to filter the results if there is a partition i.e. when sharing a single account with many users while only showing them particular cards router.get('/za/v1/cards', async (_req, _res) => { const investec = new Investec(_req.currentUser.token) @@ -31,7 +39,7 @@ router.get('/za/v1/cards', async (_req, _res) => { router.get('/*', async (_req, _res) => { const investec = new Investec(_req.currentUser.token) const response = await investec.getWithAuth(_req.url) - _res.status(response.status || 200).json(response.data) + sendInvestecResponse(_res, response) }) // proxies any POST request to the investec adapter @@ -39,7 +47,7 @@ router.get('/*', async (_req, _res) => { router.post('/*', async (_req, _res) => { const investec = new Investec(_req.currentUser.token) const response = await investec.postWithAuth(_req.url, _req.body) - _res.status(response.status || 200).json(response.data) + sendInvestecResponse(_res, response) }) module.exports = router diff --git a/routes/investec/auth.js b/routes/investec/auth.js index 2fa066d..188b245 100644 --- a/routes/investec/auth.js +++ b/routes/investec/auth.js @@ -6,10 +6,10 @@ function splitPartitionFromToken(partitionedToken) { return { token: "SANDBOX", username: "SANDBOX", partition: undefined } } - const partitionedCredentials = new Buffer(partitionedToken, 'base64').toString() + const partitionedCredentials = Buffer.from(partitionedToken, 'base64').toString() const [partitionedUsername, password] = partitionedCredentials.split(":") const [username, partition] = partitionedUsername.split("/") - const token = (new Buffer.from(`${username}:${password}`)).toString('base64') + const token = Buffer.from(`${username}:${password}`).toString('base64') return { token, username, partition } }