Skip to content
This repository has been archived by the owner on Dec 6, 2017. It is now read-only.

Test endpoints #11

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
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
106 changes: 36 additions & 70 deletions api/documentation/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@
"properties": {
"realName": {
"type": "string",
"description": "The user's real name as an arbitrary string.",
"required": true
"description": "The user's real name as an arbitrary string."
},
"role": {
"type": "string",
Expand All @@ -122,120 +121,107 @@
"provider",
"state"
],
"description": "The user's role in the system",
"required": true
"description": "The user's role in the system"
},
"eligibility": {
"$ref": "#/definitions/Eligibility",
"required": true
"$ref": "#/definitions/Eligibility"
},
"token": {
"type": "string",
"description": "JWT token representing the user",
"required": true
"description": "JWT token representing the user"
}
}
},
"required": [ "realName", "description", "eligibility", "token" ]
},
"Client": {
"type": "object",
"properties": {
"dcn": {
"type": "string",
"description": "The department client number for this client",
"required": true
"description": "The department client number for this client"
},
"people": {
"$ref": "#/definitions/people",
"required": true
"$ref": "#/definitions/people"
},
"eligibility": {
"type": "string",
"description": "Medicaid eligibility code/descriptive text",
"required": true
"description": "Medicaid eligibility code/descriptive text"
},
"coverage": {
"type": "string",
"description": "Coverage code/descriptive text",
"required": true
"description": "Coverage code/descriptive text"
},
"spenddown": {
"$ref": "#/definitions/spenddown",
"required": true
"$ref": "#/definitions/spenddown"
}
}
},
"required": [ "dcn", "people", "eligibility", "coverage", "spenddown" ]
},
"people": {
"type": "object",
"description": "The people covered by this DCN",
"properties": {
"primary": {
"type": "string",
"description": "The full name of the primary client for the DCN",
"required": true
"description": "The full name of the primary client for the DCN"
},
"others": {
"type": "array",
"description": "Full names of all other members on the DCN",
"items": {
"type": "string"
},
"required": true
}
},
"address": {
"type": "array",
"description": "Address of the client; each item in the array corresponds to an address line",
"items": {
"type": "string"
},
"required": true
}
},
"phone": {
"type": "string",
"description": "Client's phone number",
"required": true
"description": "Client's phone number"
}
}
},
"required": [ "primary", "others", "address", "phone" ]
},
"spenddown": {
"type": "object",
"description": "Spend down information",
"properties": {
"monthlyAmount": {
"type": "number",
"description": "The total spend down dollar amount due each month",
"required": true
"description": "The total spend down dollar amount due each month"
},
"owed": {
"type": "number",
"description": "The total spend down dollar amount still owed for coverage to begin",
"required": true
"description": "The total spend down dollar amount still owed for coverage to begin"
},
"contributions": {
"type": "array",
"descriptions": "Contributions towards the client's spend down",
"items": {
"$ref": "#/definitions/spenddown-contribution"
},
"required": true
}
},
"paymentHistory": {
"type": "array",
"description": "History of payments",
"items": {
"$ref": "#/definitions/payment"
},
"required": true
}
}
}
},
"required": [ "monthlyAmount", "owed", "contributions", "paymentHistory" ]
},
"spenddown-contribution": {
"type": "object",
"description": "A spend down contribution",
"properties": {
"amount": {
"type": "number",
"description": "The dollar amount of the contribution",
"required": true
"description": "The dollar amount of the contribution"
},
"status": {
"type": "string",
Expand All @@ -244,62 +230,42 @@
"paid",
"unpaid",
"direct"
],
"required": true
]
},
"to": {
"type": "string",
"description": "Who the contribution was paid to or incurred from, or absent for direct contributions",
"required": false
"description": "Who the contribution was paid to or incurred from, or absent for direct contributions"
}
}
},
"required": [ "amount", "status" ]
},
"payment": {
"type": "object",
"description": "A payment",
"properties": {
"amount": {
"type": "number",
"description": "The dollar amount of the payment",
"required": true
"description": "The dollar amount of the payment"
},
"received": {
"$ref": "#/definitions/date",
"description": "The date the payment was received",
"required": true
"description": "The date the payment was received"
},
"applied": {
"$ref": "#/definitions/date",
"description": "The date the payment was applied",
"required": true
"description": "The date the payment was applied"
},
"month": {
"type": "string",
"description": "The coverage month that the payment was applied to",
"required": true
"description": "The coverage month that the payment was applied to"
}
}
},
"required": [ "amount", "received", "applied", "month" ]
},
"date": {
"type": "number",
"description": "A data expressed in Javascript epoch time, as the milliseconds since midnight, January 1, 1970, UTC"
},
"coverage-month": {
"type": "object",
"description": "A start and end date describing one coverage month",
"properties": {
"start": {
"$ref": "#/definitions/date",
"description": "The start date of the month",
"required": true
},
"end": {
"$ref": "#/definitions/date",
"description": "The end date of the month",
"required": true
}
}
},
"Eligibility": {
"type": "object",
"properties": {
Expand Down
9 changes: 6 additions & 3 deletions api/middleware/cors.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ module.exports = function cors(app, schema) {
if (pathSchema) {
// Get the list of methods that this path supports,
// and send that in the CORS response.
const supportedMethods = Object.keys(pathSchema).map(verb => verb.toUpperCase()).join(',');
res.header('Access-Control-Allow-Methods', supportedMethods);
const supportedMethods = Object.keys(pathSchema).map(verb => verb.toUpperCase());
// const supportedMethods = Object.keys(pathSchema).map(verb => verb.toUpperCase()).join(',');
res.header('Access-Control-Allow-Methods', supportedMethods.join(','));
res.header('Access-Control-Allow-Origin', req.get('origin'));
res.header('Access-Control-Allow-Headers', 'Accepts, Authorization, Content-Length, Content-Type');

if (req.method === 'OPTIONS') {
// If it's an OPTIONS request, just bail out here.
res.sendStatus(200);
} else {
} else if (supportedMethods.includes(req.method)) {
next();
} else {
res.sendStatus(405);
}
} else {
// If the path doesn't exist, 404.
Expand Down
15 changes: 3 additions & 12 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
"main": "index.js",
"scripts": {
"start": "node index.js",
"start-dev": "nodemon ./index.js",
"lint": "eslint --fix 'test/**/*.js' 'middleware/**/*.js' 'routes/**/*.js' *.js",
"test": "istanbul cover tape -- 'test/**/*.js' | tap-summary"
"start-dev": "nodemon ./index.js"
},
"keywords": [],
"author": "",
Expand All @@ -19,16 +17,9 @@
"jsonwebtoken": "^7.3.0"
},
"devDependencies": {
"eslint": "^3.17.1",
"eslint-config-airbnb-base": "^11.1.1",
"eslint-plugin-import": "^2.2.0",
"istanbul": "^0.4.5",
"nodemon": "^1.11.0",
"sinon": "^1.17.7",
"tap-summary": "^3.0.1",
"tape": "^4.6.3"
"nodemon": "^1.11.0"
},
"engines": {
"node": "~6.9.0"
"node": "^6.9.0"
}
}
113 changes: 113 additions & 0 deletions api/test/endpoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* eslint-disable import/no-extraneous-dependencies */
const tape = require('tape');
const fork = require('child_process').fork;
const path = require('path');
const request = require('request-promise-native');
const swaggerParser = require('swagger-parser');
const AJV = require('ajv');

const ajv = new AJV();
let swagger;
let serverProcess;

function methodIsUnsupported(methodName, test) {
request[methodName]('http://0.0.0.0:8000/client/dcn')
.then(() => {
test.fail('should not return successfully');
test.end();
})
.catch((err) => {
test.equal(err.response.statusCode, 405, 'returns a 405 error');
test.end();
})
.catch((err) => {
test.fail(err);
test.end();
});
}

function runTests() {
tape.test('api endpoints', (test) => {
test.test('/client/{department-client-number}...', (clientDCNTest) => {
clientDCNTest.test('get', (getTest) => {
getTest.test('with an invalid DCN', (invalidDCN) => {
request.get('http://0.0.0.0:8000/client/dcn')
.then(() => {
invalidDCN.fail('should not return successfully');
invalidDCN.end();
})
.catch((err) => {
invalidDCN.equal(err.response.statusCode, 404, 'returns a 404 error');
invalidDCN.end();
})
.catch((err) => {
invalidDCN.fail(err);
invalidDCN.end();
});
});

getTest.test('with a valid DCN', (validDCN) => {
request.get({ method: 'GET', uri: 'http://0.0.0.0:8000/client/123456789', resolveWithFullResponse: true })
.then((response) => {
validDCN.equal(response.statusCode, 200, 'returns a 200 HTTP status code');
const body = JSON.parse(response.body);
const validator = ajv.compile(swagger.paths['/client/{department-client-number}'].get.responses['200'].schema);
const valid = validator(body);
validDCN.ok(valid, 'body should be valid according to swagger spec');
validDCN.notOk(validator.errors, 'no errors');
validDCN.end();
})
.catch((err) => {
test.fail(err);
validDCN.end();
});
});

getTest.end();
});

clientDCNTest.test('put', (putTest) => {
methodIsUnsupported('put', putTest);
});

clientDCNTest.test('post', (postTest) => {
methodIsUnsupported('post', postTest);
});

clientDCNTest.test('delete', (deleteTest) => {
methodIsUnsupported('del', deleteTest);
});

clientDCNTest.end();
});

test.end();
});

// lean on tape's serial nature to teardown the server process
tape.test('HTTP server teardown', (t) => {
if (serverProcess) {
serverProcess.kill('SIGTERM');
}
t.end();
});
}

tape.test('HTTP server setup', (t) => {
// instantiate the HTTP server so we can actually hit its endpoints
serverProcess = fork(path.join(__dirname, '../index'));

// wait a second so it can actually crank up
setTimeout(() => {
// load swagger so we can validate against it
swaggerParser.dereference(path.join(__dirname, '../documentation/swagger.json'))
.then((parsedSwagger) => {
swagger = parsedSwagger;

// once we've got the swagger loaded, run the inner tests and
// mark this setup as complete
runTests();
t.end();
});
}, 1000);
});
Loading