Skip to content

Commit

Permalink
Merge branch 'MAS-129_121__112_Daily_Email' of https://github.com/nhs…
Browse files Browse the repository at this point in the history
…evidence/MAS into MAS-129_121__112_Daily_Email
  • Loading branch information
d-lan1 committed Jan 16, 2020
2 parents 8137313 + e18952a commit bbe0299
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 30 deletions.
93 changes: 93 additions & 0 deletions cms/__tests__/routes/api/items.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
jest.mock("keystone", () => {
const item = {
model: {
find: jest.fn()
}
};

return {
list: () => item
};
});

describe("items", () => {
let keystone, daily, Item, request, response, json;

beforeEach(() => {
jest.resetModules();

keystone = require("keystone");
Item = keystone.list("Item");
daily = require("../../../routes/api/items").daily;
request = { params: {} };
json = jest.fn();
response = {
json,
badRequest: jest.fn(),
error: jest.fn()
};
});

describe("daily", () => {});

it("should return a 400 bad request when the date format is invalid", async () => {
const date = "not a date";
await daily({ ...request, ...{ params: { date } } }, response);

expect(response.badRequest).toHaveBeenCalledWith(
"Couldn't get daily items",
"Date 'not a date' is not in the format YYYY-M-D",
true
);
});

it("should search for daily items between start and end of the given date", async () => {
const date = "2020-01-09";
await daily({ ...request, ...{ params: { date } } }, response);

expect(Item.model.find).toHaveBeenCalledWith({
createdAt: {
$gte: new Date(Date.parse("2020-01-09")),
$lt: new Date(Date.parse("2020-01-09 23:59:59.999Z"))
}
});
});

it("should return a 500 JSON error response when there's an error getting the daily items", async () => {
const error = new Error("An error getting daily items");

Item.model.find.mockImplementation(() => {
throw error;
});

await daily(
{ ...request, ...{ params: { date: "2020-01-09" } } },
response
);

expect(response.error).toHaveBeenCalledWith(error, true);
});

it("should return the found item as json with whitelist of fields", async () => {
const items = [{ title: "test", excludedField: "not used" }];

Item.model.find.mockImplementation(() => {
return {
populate: () => ({
populate: () => ({
populate: () => ({
exec: () => items
})
})
})
};
});

await daily(
{ ...request, ...{ params: { date: "2020-01-09" } } },
response
);

expect(json).toHaveBeenCalledWith([{ title: "test" }]);
});
});
30 changes: 27 additions & 3 deletions cms/models/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ const keystone = require("keystone"),
https = require("https"),
http = require("http"),
log4js = require("log4js"),
utils = require("keystone-utils");
utils = require("keystone-utils"),
_ = require("lodash");

const Types = keystone.Field.Types;

Expand Down Expand Up @@ -107,6 +108,27 @@ Item.add({
}
});

Item.fullResponseFields = [
"_id",
"title",
"slug",
"url",
"shortSummary",
"comment",
"resourceLinks",
"staticPath",
"source._id",
"source.title",
"specialities",
"evidenceType._id",
"evidenceType.title",
"evidenceType.key",
"evidenceType.broaderTitle",
"publicationDate",
"updatedAt",
"createdAt"
];

Item.schema.pre("validate", function(next) {
if (this.isInitial) {
this.isInitial = false;
Expand Down Expand Up @@ -155,7 +177,7 @@ const createWeeklyIfNeeded = async () => {
Item.schema.post("save", async function(doc, next) {
await createWeeklyIfNeeded();

logger.info("Post save, sending request...", doc);
logger.info("Post save, sending request...");

let item;

Expand All @@ -166,6 +188,7 @@ Item.schema.post("save", async function(doc, next) {
.populate("source")
.populate("evidenceType")
.populate("specialities")
.select(Item.fullResponseFields.join(" "))
.exec();
} catch (err) {
logger.error("An error occurred finding item: ", err.message);
Expand All @@ -176,7 +199,8 @@ Item.schema.post("save", async function(doc, next) {
const hostname = process.env.HOST_NAME;
const hostport = process.env.HOST_PORT;

const data = JSON.stringify(item);
const data = JSON.stringify(_.pick(item, Item.fullResponseFields));
logger.debug("Sending: ", data);

var options = {
hostname: hostname,
Expand Down
77 changes: 52 additions & 25 deletions cms/routes/api/items.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
const keystone = require("keystone"),
_ = require("lodash"),
moment = require("moment"),
Items = keystone.list("Item");

const log4js = require("log4js"),
logger = log4js.getLogger();

exports.single = function(req, res, next) {
/**
* Single item, given an id
* /api/items/5e205dadc12944a35f24572b
*/
exports.single = function(req, res) {
Items.model
.findById(req.params.itemId)
.populate("source")
.populate("evidenceType")
.populate("specialities")
.select(Items.fullResponseFields.join(" "))
.exec(function(err, item) {
if (err) {
logger.error(`Error getting item with id ${req.params.itemId}`, err);
Expand All @@ -23,40 +29,20 @@ exports.single = function(req, res, next) {
return res.notfound("Item not found", notFoundMsg, true);
}

const obj = _.pick(item, [
"_id",
"updatedAt",
"createdAt",
"slug",
"shortSummary",
"source._id",
"source.title",
"url",
"title",
"comment",
"publicationDate",
"resourceLinks",
"speciality",
"evidenceType._id",
"evidenceType.title",
"evidenceType.key",
"evidenceType.broaderTitle"
]);
const obj = _.pick(item, Items.fullResponseFields);

return res.json(obj);
});
};

/**
* List Items
* List of all items
* /api/items
*/
exports.list = function(req, res) {
// TODO: Pagination
Items.model
.find()
.populate("source")
.populate("evidenceType")
.populate("specialities")
.select("title slug")
.exec(function(err, items) {
if (err) {
logger.error(`Failed to get list of items`, err);
Expand All @@ -66,3 +52,44 @@ exports.list = function(req, res) {
res.json(items);
});
};

/**
* Daily items for a given date
* /api/items/daily/2020-01-16
*/
exports.daily = async function(req, res) {
const dateStr = req.params.date,
date = moment(dateStr, "YYYY-M-D", true);

if (!date.isValid()) {
const errorMessage = `Date '${dateStr}' is not in the format YYYY-M-D`;
logger.error(errorMessage);
return res.badRequest("Couldn't get daily items", errorMessage, true);
}

const startOfDay = date.clone().startOf("day"),
endOfDay = date.clone().endOf("day");

let items;
try {
items = await Items.model
.find({
createdAt: { $gte: startOfDay.toDate(), $lt: endOfDay.toDate() }
})
.populate("source")
.populate("evidenceType")
.populate("specialities")
.select(Items.fullResponseFields.join(" "))
.exec();
} catch (err) {
logger.error(`Error getting daily items for date ${dateStr}`, err);
return res.error(err, true);
}

if (items.length === 0)
logger.warn(`Zero daily items found for date ${dateStr}`);

const obj = _.map(items, _.partialRight(_.pick, Items.fullResponseFields));

res.json(obj);
};
1 change: 1 addition & 0 deletions cms/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ exports = module.exports = function(app) {
// Views
app.get("/", routes.views.index);
app.get("/api/items/:itemId", routes.api.items.single);
app.get("/api/items/daily/:date", routes.api.items.daily);
app.get("/api/items", routes.api.items.list);
app.get("/api/specialities/:specialityId", routes.api.specialities.single);
app.get("/api/specialities", routes.api.specialities.list);
Expand Down
8 changes: 8 additions & 0 deletions cms/routes/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,13 @@ exports.initErrorHandlers = function(req, res, next) {
return res.status(404).send(keystone.wrapHTMLError(title, message));
};

res.badRequest = function(title, message, useJson) {
if (useJson || req.is("json")) {
return res.status(400).json({ title, message });
}

return res.status(400).send(keystone.wrapHTMLError(title, message));
};

next();
};
4 changes: 2 additions & 2 deletions lambda/MAS/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
},
"CMS": {
"BaseUrl": "don't add this or anything else that needs securing. see here: https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?tabs=visual-studio",
"AllItemsPath": "/items",
"DailyItemsPath": "/items/daily/{0}"
"AllItemsPath": "items",
"DailyItemsPath": "items/daily/{0}"
},
"MailChimp": {
"ApiKey": "don't add this or anything else that needs securing. see here: https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?tabs=visual-studio",
Expand Down

2 comments on commit bbe0299

@NICE-TeamCity
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity MAS / MAS Build 1.1.0.578 outcome was FAILURE
Summary: Artifacts size 187.2 KB is 95% different from 3.6 MB in build #1.1.0.557-r893BC73; exit code 1 (Step: Set build number (Command Line)) (new) Build time: 00:00:11

@NICE-TeamCity
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity MAS / MAS Build 1.1.0.579-MAS-129_121__112_Dai outcome was FAILURE
Summary: Tests failed: 1, passed: 8; artifacts size 187.2 KB is 95% different from 3.6 MB in build #1.1.0.557-r893BC73 Build time: 00:00:41

Failed tests

api/items.test.js: items should return the found item as json with whitelist of fields: <no details avaliable>

Please sign in to comment.