Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix tests and error handling #11

Merged
merged 1 commit into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading