Skip to content

Commit

Permalink
fix tests and error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
jchartrand committed Aug 23, 2023
1 parent 9bea798 commit 0e65edd
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 129 deletions.
13 changes: 10 additions & 3 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function build(opts = {}) {

await verifyAuthHeader(authHeader, tenantName)
// NOTE: we throw the error here which will then be caught by middleware errorhandler
if (!req.body || !Object.keys(req.body).length) throw {code:400, message:'A verifiable credential must be provided in the body'}
if (!unSignedVC || !Object.keys(unSignedVC).length) throw {code:400, message:'A verifiable credential must be provided in the body'}
const vcWithStatus = enableStatusService ?
await callService(`http://${statusServiceEndpoint}/credentials/status/allocate`, unSignedVC)
:
Expand All @@ -93,10 +93,17 @@ export async function build(opts = {}) {
const authHeader = req.headers.authorization
const statusUpdate = req.body
await verifyAuthHeader(authHeader, tenantName)
const updateResult = await callService(`http://${statusServiceEndpoint}/credentials/status`, statusUpdate)
// NOTE: we throw the error here which will then be caught by middleware errorhandler
if (!statusUpdate || !Object.keys(statusUpdate).length) throw {code:400, message:'A status update must be provided in the body.'}
const updateResult = await callService(`http://${statusServiceEndpoint}/credentials/status`, statusUpdate)
return res.json(updateResult)
} catch (error) {
// have to catch and forward async errors to middleware:
if (error.response?.status == 404) {
// if it is a 404 then just forward on the error
// we got from the service
next(error.response.data)
}
// otherwise, forward the error to middleware:
next(error)
}
})
Expand Down
143 changes: 57 additions & 86 deletions src/app.test.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
import nock from 'nock';
import axios from 'axios'
import { expect } from 'chai'
import { dirname } from 'path';
import request from 'supertest';
import { fileURLToPath } from 'url';
import { getUnsignedVC, getUnsignedVCWithStatus } from './test-fixtures/vc.js';
import unsignedNock from './test-fixtures/nocks/unprotected_sign.js'


axios.defaults.adapter = 'http'
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
nock.back.fixtures = __dirname + '/nockBackFixtures'
let saveNockRecording;



async function startNockBackRecording(fixtureFileName) {
nock.back.setMode('wild')
const { nockDone } = await nock.back('nockMocks.json');
saveNockRecording = nockDone
// allow the requests to localhost, i.e, the test calls themselves
//nock.enableNetConnect(/127\.0\.0\.1/);
//nock.enableNetConnect(/localhost/);
}

async function stopAndSaveNockRecording() {
saveNockRecording()
//nock.back.setMode('wild')
}
import unprotectedNock from './test-fixtures/nocks/unprotected_status_signing.js'
import protectedNock from './test-fixtures/nocks/protected_status_signing.js'
import unprotectedStatusUpdateNock from './test-fixtures/nocks/unprotected_status_update.js'
import unknownStatusIdNock from './test-fixtures/nocks/unknown_status_id_nock.js'
import protectedStatusUpdateNock from './test-fixtures/nocks/protected_status_update.js'

import { build } from './app.js';

Expand All @@ -42,24 +20,17 @@ describe('api', () => {

before(async () => {
//testDIDSeed = await decodeSeed(process.env.TENANT_SEED_TESTING)
testTenantToken = process.env.TENANT_TOKEN_TESTING
testTenantToken2 = process.env.TENANT_TOKEN_TESTING_2

//didDocument = (await didKeyDriver.generate({ seed: testDIDSeed })).didDocument
//verificationMethod = didKeyDriver.publicMethodFor({ didDocument, purpose: 'assertionMethod' }).id
//signingDID = didDocument.id
testTenantToken = process.env.TENANT_TOKEN_PROTECTED_TEST
testTenantToken2 = process.env.TENANT_TOKEN_PROTECTED_TEST_2
statusUpdateBody = { "credentialId": "urn:uuid:951b475e-b795-43bc-ba8f-a2d01efd2eb1", "credentialStatus": [{ "type": "StatusList2021Credential", "status": "revoked" }] }

//startNockBackRecording()
});

after(() => {
//stopAndSaveNockRecording()
})

beforeEach(async () => {
app = await build();

if (!nock.isActive()) nock.activate()
});

afterEach(async () => {
Expand Down Expand Up @@ -93,26 +64,25 @@ describe('api', () => {

it('returns 400 if no body', done => {
request(app)
.post("/instance/testing/credentials/issue")
.post("/instance/protected_test/credentials/issue")
.set('Authorization', `Bearer ${testTenantToken}`)
.expect('Content-Type', /json/)
.expect(400, done)
})

it('returns 401 if tenant token is missing from auth header', done => {
request(app)
.post("/instance/testing/credentials/issue")
.post("/instance/protected_test/credentials/issue")
.send(getUnsignedVC())
.expect('Content-Type', /json/)
.expect(401, done)
})

it.only('issues credential for UNPROTECTED tenant, without auth header', async () => {
//nock.recorder.rec()
unsignedNock();
it('issues credential for unprotected tenant', async () => {
unprotectedNock();

const response = await request(app)
.post("/instance/testing3/credentials/issue")
.post("/instance/un_protected_test/credentials/issue")
.send(getUnsignedVC())

expect(response.header["content-type"]).to.have.string("json");
Expand All @@ -124,28 +94,28 @@ describe('api', () => {

it('returns 403 if token is not valid', done => {
request(app)
.post("/instance/testing/credentials/issue")
.post("/instance/protected_test/credentials/issue")
.set('Authorization', `Bearer badToken`)
.send(getUnsignedVC())
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(403, done)
})

it('returns 403 when trying to use token for a different tenant', done => {
request(app)
.post("/instance/testing/credentials/issue")
.post("/instance/protected_test/credentials/issue")
.set('Authorization', `Bearer ${testTenantToken2}`)
.send(getUnsignedVC())
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(403, done)
})

it('returns 401 if token is not marked as Bearer', done => {
request(app)
.post("/instance/testing/credentials/issue")
.post("/instance/protected_test/credentials/issue")
.set('Authorization', `${testTenantToken}`)
.send(getUnsignedVC())
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(401, done)
})

Expand All @@ -155,29 +125,25 @@ describe('api', () => {
.set('Authorization', `${testTenantToken}`)
.send(getUnsignedVC())
.expect(404, done)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)

})

it('invokes the signing service', async () => {})

it('invokes the status service', async () => {})


it('returns the vc from signing service', async () => {
// get the returned VC from the nock, once we've run nock-back.
const credFromSigningService = "will get from nock."
const sentCred = getUnsignedVCWithStatus()
it('returns signed vc for protected tenant', async () => {
//nock.recorder.rec()
protectedNock()
const sentCred = getUnsignedVC()
const response = await request(app)
.post("/instance/testing/credentials/issue")
.post("/instance/protected_test/credentials/issue")
.set('Authorization', `Bearer ${testTenantToken}`)
.send(sentCred)

expect(response.header["content-type"]).to.have.string("json");
expect(response.status).to.eql(200);

const returnedCred = JSON.parse(JSON.stringify(response.body));
expect(credFromSigningService).to.eql(returnedCred)
// this proof value comes from the nock:
expect(returnedCred.proof.proofValue).to.eql("z5QQ12zr5JvEsKvbnEN2EYZ6punR6Pa5wMJzywGJ2dCh6SSA5oQb9hBiGADsNTbs57bopArwdBHE9kEVemMxcu1Fq")

});

Expand All @@ -187,43 +153,45 @@ describe('api', () => {

it('returns 400 if no body', done => {
request(app)
.post("/instance/testing/credentials/status")
.post("/instance/un_protected_test/credentials/status")
.set('Authorization', `Bearer ${testTenantToken}`)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(400, done)
})

it('returns 401 if tenant token is missing from auth header', done => {
request(app)
.post("/instance/testing/credentials/status")
.post("/instance/protected_test/credentials/status")
.send(statusUpdateBody)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(401, done)
})

it('no auth header needed to update status when token not set for tenant in config', done => {
it('update unprotected status when token not set for tenant in config', done => {
//nock.recorder.rec()
unprotectedStatusUpdateNock()
request(app)
.post("/instance/testing3/credentials/status")
.post("/instance/un_protected_test/credentials/status")
.send(statusUpdateBody)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(200, done)
})

it('returns 403 if token is not valid', done => {
request(app)
.post("/instance/testing/credentials/status")
.post("/instance/protected_test/credentials/status")
.set('Authorization', `Bearer ThisIsABadToken`)
.send(statusUpdateBody)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(403, done)
})

it('returns 401 if token is not marked as Bearer', done => {
request(app)
.post("/instance/testing/credentials/status")
.post("/instance/protected_test/credentials/status")
.set('Authorization', `${testTenantToken}`)
.send(statusUpdateBody)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(401, done)
})

Expand All @@ -233,40 +201,43 @@ describe('api', () => {
.set('Authorization', `${testTenantToken}`)
.send(statusUpdateBody)
.expect(404, done)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)

})

it('returns 403 when trying to use token for a different tenant', done => {
request(app)
.post("/instance/testing/credentials/status")
.post("/instance/protected_test/credentials/status")
.set('Authorization', `Bearer ${testTenantToken2}`)
.send(statusUpdateBody)
.expect('Content-Type', /text/)
.expect('Content-Type', /json/)
.expect(403, done)
})

it('returns 404 for unknown cred id', done => {
// it wil have gotten the 404 from the status service and then
// simply returned that.
it('returns 404 for unknown cred id', async () => {
// nock.recorder.rec()
unknownStatusIdNock()
const statusUpdateBodyWithUnknownId = JSON.parse(JSON.stringify(statusUpdateBody))
statusUpdateBodyWithUnknownId.credentialId = 'kj09ij'
request(app)
.post("/instance/testing/credentials/status")
const response = await request(app)
.post("/instance/protected_test/credentials/status")
.set('Authorization', `Bearer ${testTenantToken}`)
.send(statusUpdateBodyWithUnknownId)
.expect('Content-Type', /text/)
.expect(404, done)


expect(response.header["content-type"]).to.have.string("json");
expect(response.status).to.eql(404);
})
// AND A TEST FOR THE GENERAL BAD REQUEST THAT DOESN'T FALL INTO THE OTHER CATEGORIES.

it('calls status manager for protected tenant', async () => {

it('calls status manager', async () => {
protectedStatusUpdateNock()
const response = await request(app)
.post("/instance/testing/credentials/status")
.post("/instance/protected_test/credentials/status")
.set('Authorization', `Bearer ${testTenantToken}`)
.send(statusUpdateBody)

expect(response.header["content-type"]).to.have.string("text");
expect(response.header["content-type"]).to.have.string("json");
expect(response.status).to.eql(200);
})

Expand Down
13 changes: 6 additions & 7 deletions src/middleware/errorHandler.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import axios, { isAxiosError } from 'axios';
import { isAxiosError } from 'axios';
function handleAxiosError(error) {
const errorResponse = { serviceConfig: error.config, code: 500 }
const errorResponse = { code: 500 }
if (error.response) {
// The microservice responded with a status code other than 2xx
errorResponse.message = "One of the internal microservices returned an error"
errorResponse.serviceResponseError = error.response.data
errorResponse.serviceResponseStatus = error.response.status
errorResponse.serviceResponseHeaders = error.response.headers
} else if (error.request) {
errorResponse.message = "One of the internal microservices didn't respond."
errorResponse.serviceRequest = error.request
errorResponse.message = "One of the internal microservices didn't respond. Hit / to check heartbeats."
// errorResponse.serviceRequest = error.request
} else {
errorResponse.message = `Likely an error when setting up the request that prevented the request from being formulated: ${error.message}`
errorResponse.error = error
Expand All @@ -22,9 +22,8 @@ const errorHandler = (error, request, response, next) => {
if (isAxiosError(error)) {
errorResponse = handleAxiosError(error);
} else {
// this is an error returned for something internal,
// like the check for an access token,
// or a missing argument on a call
// otherwise, this is an error that we've thrown ourselves,
// or it is some other error, so pass it along
errorResponse = error
}
response.header("Content-Type", 'application/json')
Expand Down
15 changes: 8 additions & 7 deletions src/middleware/errorLogger.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import axios, {isAxiosError} from 'axios';
import {isAxiosError} from 'axios';
import logger from "../utils/logger.js";

const errorLogger = (error, request, response, next) => {

const logEntry = {}

if (isAxiosError(error)) {
// an error when calling one of the microservices
logEntry.serviceConfig = error.config
if (error.response) {
// The microservice responded with a status code other than 2xx
logEntry.message = "One of the internal microservices returned an error"
logEntry.message = "One of the internal microservices returned an error."
logEntry.serviceResponseError = error.response.data
logEntry.serviceResponseStatus = error.response.status
logEntry.serviceResponseHeaders = error.response.headers
} else if (error.request) {
logEntry.message = "One of the internal microservices didn't respond."
logEntry.serviceRequest = error.request
logEntry.message = "One of the internal microservices didn't respond. Hit / to check heartbeats."
} else {
logEntry.message = `Likely an error when setting up a request to one of the internal microservices that prevented the request from being formulated: ${error.message}`
logEntry.error = error
}
} else {
// a non-axios error
console.log(error)
logEntry.message = error.message || "An unknown error occurred."
}
const errorLogMessage = `${error.statusCode} || ${response.statusMessage} - ${request.originalUrl} - ${request.method} - ${request.ip} - ${error.message}`

const errorLogMessage = `Error for route: ${request.originalUrl} - ${request.method} - IP: ${request.ip}`

// note that the logEntry here is what Winston calls a 'meta' object and it simply
// output to the log as provided, as JSON in this case.
Expand Down
Loading

0 comments on commit 0e65edd

Please sign in to comment.