diff --git a/cms/__tests__/routes/api/items.test.js b/cms/__tests__/routes/api/items.test.js
new file mode 100644
index 00000000..cd9d2293
--- /dev/null
+++ b/cms/__tests__/routes/api/items.test.js
@@ -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" }]);
+ });
+});
diff --git a/cms/evidence-types.jsonld b/cms/evidence-types.jsonld
new file mode 100644
index 00000000..9c9320b2
--- /dev/null
+++ b/cms/evidence-types.jsonld
@@ -0,0 +1,371 @@
+{
+ "@graph" : [ {
+ "@id" : "mas_evidence_types:Audit%20reports",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Audit reports"
+ }
+ }, {
+ "@id" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Evidence type"
+ }
+ }, {
+ "@id" : "mas_evidence_types:CONCEPT-Guidance_and_advice",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Guidance and advice"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Care%20pathways",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Care pathways"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Commissioning%20guides",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Guidance_and_advice",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Commissioning guides"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Drug%20best%20practice%20guidance",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Guidance_and_advice",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Drug best practice guidance"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Drug%20costs",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Drug costs"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Drug%20horizon%20scanning",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Drug horizon scanning"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Drug%20prescribing",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Guidance_and_advice",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Drug prescribing"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Drug%20regulatory%20and%20marketing",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Drug regulatory and marketing"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Drug%2Fmedicines%20management",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Drug/medicines management"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Effective%20practice%20examples",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Effective practice examples"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Evidence%20summaries",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Evidence%20summary",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Evidence summaries"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Evidence%20summary",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Evidence summary"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Evidence%20updates",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Evidence%20summary",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Evidence updates"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Evidence-based%20management%20reports",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Evidence-based management reports"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Eyes%20on%20evidence%20commentaries",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Evidence%20summary",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Eyes on evidence commentaries"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Guidance",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Guidance_and_advice",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Guidance"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Health%20technology%20assessments",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Systematic%20review",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Health technology assessments"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Implementation%20support%20tools",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Implementation support tools"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Learning%20materials",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Learning materials"
+ }
+ }, {
+ "@id" : "mas_evidence_types:MAS_evidence_type",
+ "@type" : "http://www.w3.org/2004/02/skos/core#ConceptScheme",
+ "hasTopConcept" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "MAS evidence type"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Media%20and%20commentaries",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Media and commentaries"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Medicines%20Q%20%26%20A",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Evidence%20summary",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Medicines Q & A"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Medicines%20evidence%20commentaries",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Evidence%20summary",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Medicines evidence commentaries"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Ongoing%20or%20unpublished%20research",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Primary%20research",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Ongoing or unpublished research"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Other%20economic%20evaluations",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Other economic evaluations"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Other%20evidence",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Other evidence"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Other%20primary%20research",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Primary%20research",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Other primary research"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Patient%20decision%20aids",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Patient decision aids"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Patient%20information",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Patient information"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Policy",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Policy"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Population%20intelligence",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Population intelligence"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Population%20needs%20assessment",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Population needs assessment"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Primary%20research",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Primary research"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Quality%20measures",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Other%20evidence",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Quality measures"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Randomised%20controlled%20trials",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Primary%20research",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Randomised controlled trials"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Safety%20alerts",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Safety alerts"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Systematic%20review",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:CONCEPT-Evidence_type",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Systematic review"
+ }
+ }, {
+ "@id" : "mas_evidence_types:Systematic%20reviews",
+ "@type" : "http://www.w3.org/2004/02/skos/core#Concept",
+ "broader" : "mas_evidence_types:Systematic%20review",
+ "prefLabel" : {
+ "@language" : "en",
+ "@value" : "Systematic reviews"
+ }
+ }, {
+ "@id" : "urn:x-evn-master:mas_evidence_types",
+ "@type" : "owl:Ontology",
+ "status" : "metadata:UnderDevelopmentStatus",
+ "defaultNamespace" : "http://nice.org.uk/MAS/evidence_type/",
+ "newInstancesUserCannotModifyURI" : "0",
+ "comment" : "A list of evidence types includes in the Medicines Awareness Service",
+ "label" : "MAS evidence types",
+ "imports" : [ "http://topbraid.org/skos.shapes", "http://topbraid.org/imported" ]
+ } ],
+ "@context" : {
+ "prefLabel" : {
+ "@id" : "http://www.w3.org/2004/02/skos/core#prefLabel"
+ },
+ "broader" : {
+ "@id" : "http://www.w3.org/2004/02/skos/core#broader",
+ "@type" : "@id"
+ },
+ "imports" : {
+ "@id" : "http://www.w3.org/2002/07/owl#imports",
+ "@type" : "@id"
+ },
+ "status" : {
+ "@id" : "http://topbraid.org/metadata#status",
+ "@type" : "@id"
+ },
+ "label" : {
+ "@id" : "http://www.w3.org/2000/01/rdf-schema#label"
+ },
+ "newInstancesUserCannotModifyURI" : {
+ "@id" : "http://topbraid.org/teamwork#newInstancesUserCannotModifyURI",
+ "@type" : "http://www.w3.org/2001/XMLSchema#boolean"
+ },
+ "comment" : {
+ "@id" : "http://www.w3.org/2000/01/rdf-schema#comment"
+ },
+ "defaultNamespace" : {
+ "@id" : "http://topbraid.org/swa#defaultNamespace"
+ },
+ "hasTopConcept" : {
+ "@id" : "http://www.w3.org/2004/02/skos/core#hasTopConcept",
+ "@type" : "@id"
+ },
+ "metadata" : "http://topbraid.org/metadata#",
+ "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ "owl" : "http://www.w3.org/2002/07/owl#",
+ "teamwork" : "http://topbraid.org/teamwork#",
+ "mas_evidence_types" : "http://nice.org.uk/MAS/evidence_type/",
+ "rdfs" : "http://www.w3.org/2000/01/rdf-schema#"
+ }
+}
diff --git a/cms/models/EvidenceType.js b/cms/models/EvidenceType.js
index d67e5084..db05f9d4 100644
--- a/cms/models/EvidenceType.js
+++ b/cms/models/EvidenceType.js
@@ -1,4 +1,25 @@
-const keystone = require("keystone");
+const keystone = require("keystone"),
+ fs = require("fs"),
+ path = require("path"),
+ log4js = require("log4js");
+
+const logger = log4js.getLogger("Item.js");
+
+// Load the JSONLD for evidence types on application load
+// so we don't have to hit the file system again and again
+let evidenceTypesJsonLd;
+fs.readFile(
+ path.resolve(__dirname, "../evidence-types.jsonld"),
+ "UTF-8",
+ function(err, contents) {
+ if (err) {
+ logger.error(err);
+ throw err;
+ }
+
+ evidenceTypesJsonLd = JSON.parse(contents);
+ }
+);
const Types = keystone.Field.Types;
@@ -25,6 +46,18 @@ EvidenceType.relationship({
refPath: "evidenceType"
});
+EvidenceType.schema.virtual("broaderTitle").get(function() {
+ const key = this.key,
+ evidenceTypes = evidenceTypesJsonLd["@graph"],
+ concept = evidenceTypes.find(e => e["@id"] === key);
+
+ // Assume only 2 levels
+ const broaderConcept = evidenceTypes.find(e => e["@id"] === concept.broader);
+
+ if (broaderConcept.broader) return broaderConcept.prefLabel["@value"];
+ else return concept.prefLabel["@value"];
+});
+
EvidenceType.defaultColumns = "title, oldEPiServerId, key";
EvidenceType.register();
diff --git a/cms/models/Item.js b/cms/models/Item.js
index 3b63fbc0..356fe167 100644
--- a/cms/models/Item.js
+++ b/cms/models/Item.js
@@ -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;
@@ -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;
@@ -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;
@@ -165,6 +187,8 @@ Item.schema.post("save", async function(doc, next) {
.model.findById(doc._id)
.populate("source")
.populate("evidenceType")
+ .populate("specialities")
+ .select(Item.fullResponseFields.join(" "))
.exec();
} catch (err) {
logger.error("An error occurred finding item: ", err.message);
@@ -175,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,
diff --git a/cms/routes/api/items.js b/cms/routes/api/items.js
index 589cbf66..a1f7a5a0 100644
--- a/cms/routes/api/items.js
+++ b/cms/routes/api/items.js
@@ -1,14 +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);
@@ -21,19 +29,20 @@ exports.single = function(req, res, next) {
return res.notfound("Item not found", notFoundMsg, true);
}
- res.json(item);
+ 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")
+ .select("title slug")
.exec(function(err, items) {
if (err) {
logger.error(`Failed to get list of items`, err);
@@ -43,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);
+};
diff --git a/cms/routes/index.js b/cms/routes/index.js
index 3b4488c5..4559b728 100644
--- a/cms/routes/index.js
+++ b/cms/routes/index.js
@@ -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);
diff --git a/cms/routes/middleware.js b/cms/routes/middleware.js
index d94e6d6a..54d68ad1 100644
--- a/cms/routes/middleware.js
+++ b/cms/routes/middleware.js
@@ -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();
};
diff --git a/lambda/MAS.Tests/Feeds/multiple-items.json b/lambda/MAS.Tests/Feeds/all-items.json
similarity index 68%
rename from lambda/MAS.Tests/Feeds/multiple-items.json
rename to lambda/MAS.Tests/Feeds/all-items.json
index c68a3ab5..ef0788d5 100644
--- a/lambda/MAS.Tests/Feeds/multiple-items.json
+++ b/lambda/MAS.Tests/Feeds/all-items.json
@@ -17,7 +17,18 @@
"publicationDate": "2019-11-25T13:48:36.000Z",
"createdDate": "2019-10-22T15:05:05.927Z",
"specialities": [
- "5daf1a5c8a34d485cb05b5ba"
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
],
"evidenceType": {
"_id": "5df7abf383138898ee1f67ef",
@@ -46,7 +57,18 @@
"publicationDate": "2019-11-23T13:48:36.000Z",
"createdDate": "2019-10-23T10:38:43.000Z",
"specialities": [
- "5daf1a5c8a34d478fd05b5d4"
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
],
"evidenceType": {
"_id": "5df7abf383138898ee1f67ef",
@@ -75,7 +97,18 @@
"publicationDate": "2019-10-23T00:00:00.000Z",
"createdDate": "2019-10-23T10:56:40.000Z",
"specialities": [
- "5daf1a5c8a34d4d6e505b5b9"
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
],
"evidenceType": {
"_id": "5df7abf383138898ee1f67ef",
@@ -104,7 +137,18 @@
"publicationDate": "2019-10-28T00:00:00.000Z",
"createdDate": "2019-10-28T11:07:11.000Z",
"specialities": [
- "5daf1a5c8a34d485cb05b5ba"
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
],
"evidenceType": {
"_id": "5df7abf383138898ee1f67ef",
diff --git a/lambda/MAS.Tests/Feeds/daily-items-2020-01-01.json b/lambda/MAS.Tests/Feeds/daily-items-2020-01-01.json
new file mode 100644
index 00000000..f6e325a6
--- /dev/null
+++ b/lambda/MAS.Tests/Feeds/daily-items-2020-01-01.json
@@ -0,0 +1,95 @@
+[
+ {
+ "_id": "5e208e42306e5d4f5ff39659",
+ "title": "Item 1",
+ "slug": "item-1",
+ "url": "https://www.abc.com",
+ "shortSummary": "This is some short summary",
+ "comment": "
This is some comment
",
+ "resourceLinks": "https://www.abc.com
",
+ "staticPath": "item-1.html",
+ "source": {
+ "_id": "5e1ddf2a306e5d5250f3904a",
+ "title": "2020health"
+ },
+ "specialities": [
+ {
+ "_id": "5e1ddf2c306e5d0fa9f39611",
+ "key": "8f13726e-5635-471f-ad3c-fc910a6ac2b1",
+ "title": "Cancers",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5e1ddf2c306e5d6eabf39641",
+ "title": "Evidence summary - Evidence summaries",
+ "key": "mas_evidence_types:Evidence%20summaries",
+ "broaderTitle": "Evidence summary"
+ },
+ "publicationDate": "2020-01-16T00:00:00.000Z",
+ "updatedAt": "2020-01-16T16:25:37.188Z",
+ "createdAt": "2020-01-16T16:24:34.985Z"
+ },
+ {
+ "_id": "5e208f7f0348d6060f8d448c",
+ "title": "Item 2",
+ "slug": "item-2",
+ "url": "https://www.google.com",
+ "shortSummary": "Some short summary",
+ "comment": "This is some comment
",
+ "resourceLinks": "https://www.google.com/
",
+ "staticPath": "item-2.html",
+ "source": {
+ "_id": "5e1ddf2a306e5da2b0f3904d",
+ "title": "ABL Health Ltd"
+ },
+ "specialities": [
+ {
+ "_id": "5e1ddf2c306e5d4e6cf39631",
+ "key": "d0ea592c-932a-462f-b206-cf454e40fa20",
+ "title": "Stroke",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5e1ddf2c306e5d6eabf39641",
+ "title": "Evidence summary - Evidence summaries",
+ "key": "mas_evidence_types:Evidence%20summaries",
+ "broaderTitle": "Evidence summary"
+ },
+ "publicationDate": "2020-01-16T00:00:00.000Z",
+ "updatedAt": "2020-01-16T16:31:11.333Z",
+ "createdAt": "2020-01-16T16:29:51.539Z"
+ },
+ {
+ "_id": "5e208fe00348d67ca08d448d",
+ "title": "Item 3",
+ "slug": "item-3",
+ "url": "https://www.google.com/",
+ "shortSummary": "Some short summary",
+ "comment": "Some comment
",
+ "resourceLinks": "https://www.google.com/
\r\nhttps://www.google.com/
\r\nhttps://www.google.com/
",
+ "staticPath": "item-3.html",
+ "source": {
+ "_id": "5e1ddf2a306e5da2b0f3904d",
+ "title": "ABL Health Ltd"
+ },
+ "specialities": [
+ {
+ "_id": "5e1ddf2c306e5d7e78f3961f",
+ "key": "cb0faa85-1781-484e-a265-2715621c1be9",
+ "title": "Later life",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5e1ddf2c306e5dcf61f39644",
+ "title": "Evidence summary - Eyes on evidence commentaries",
+ "key": "mas_evidence_types:Eyes%20on%20evidence%20commentaries",
+ "broaderTitle": "Evidence summary"
+ },
+ "publicationDate": "2020-01-16T00:00:00.000Z",
+ "updatedAt": "2020-01-16T16:32:02.535Z",
+ "createdAt": "2020-01-16T16:31:28.754Z"
+ }
+]
\ No newline at end of file
diff --git a/lambda/MAS.Tests/Feeds/daily-items-single.json b/lambda/MAS.Tests/Feeds/daily-items-single.json
new file mode 100644
index 00000000..e1537f99
--- /dev/null
+++ b/lambda/MAS.Tests/Feeds/daily-items-single.json
@@ -0,0 +1,42 @@
+[
+ {
+ "_id": "5daf1aa18a34d4bb8405b5e0",
+ "slug": "testing-789",
+ "source": {
+ "_id": "5de526fc43e373581b7810c8",
+ "oldEPiServerId": 2,
+ "title": "ACP Journal Club",
+ "__v": 0
+ },
+ "shortSummary": "testing",
+ "resourceLinks": "http://localhost:3010/keystone/items",
+ "url": "https://www.nhs.uk/",
+ "title": "Wonder Drug",
+ "__v": 0,
+ "comment": "A
",
+ "publicationDate": "2019-11-25T13:48:36.000Z",
+ "createdDate": "2019-10-22T15:05:05.927Z",
+ "specialities": [
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5df7abf383138898ee1f67ef",
+ "title": "Safety alerts",
+ "key": "mas_evidence_types:Safety%20alerts",
+ "oldEPiServerId": 778708,
+ "__v": 0
+ },
+ "isInitial": false
+ }
+]
\ No newline at end of file
diff --git a/lambda/MAS.Tests/Feeds/daily-items.json b/lambda/MAS.Tests/Feeds/daily-items.json
new file mode 100644
index 00000000..ef0788d5
--- /dev/null
+++ b/lambda/MAS.Tests/Feeds/daily-items.json
@@ -0,0 +1,162 @@
+[
+ {
+ "_id": "5daf1aa18a34d4bb8405b5e0",
+ "slug": "testing-789",
+ "source": {
+ "_id": "5de526fc43e373581b7810c8",
+ "oldEPiServerId": 2,
+ "title": "ACP Journal Club",
+ "__v": 0
+ },
+ "shortSummary": "testing",
+ "resourceLinks": "http://localhost:3010/keystone/items",
+ "url": "https://www.nhs.uk/",
+ "title": "Wonder Drug",
+ "__v": 0,
+ "comment": "A
",
+ "publicationDate": "2019-11-25T13:48:36.000Z",
+ "createdDate": "2019-10-22T15:05:05.927Z",
+ "specialities": [
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5df7abf383138898ee1f67ef",
+ "title": "Safety alerts",
+ "key": "mas_evidence_types:Safety%20alerts",
+ "oldEPiServerId": 778708,
+ "__v": 0
+ },
+ "isInitial": false
+ },
+ {
+ "_id": "5db02d6d8a34d43bde05b5e1",
+ "slug": "my-medicine",
+ "source": {
+ "_id": "5de5261b8ce66f142e44cd08",
+ "oldEPiServerId": 1394,
+ "title": "ADHD Awareness Month",
+ "__v": 0
+ },
+ "shortSummary": "this is my medicine",
+ "resourceLinks": "http://localhost:3010/keystone/items",
+ "url": "https://www.nice.org.uk/",
+ "title": "MY MEDICINE",
+ "__v": 0,
+ "comment": "this is my comment
",
+ "publicationDate": "2019-11-23T13:48:36.000Z",
+ "createdDate": "2019-10-23T10:38:43.000Z",
+ "specialities": [
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5df7abf383138898ee1f67ef",
+ "title": "Safety alerts",
+ "key": "mas_evidence_types:Safety%20alerts",
+ "oldEPiServerId": 778708,
+ "__v": 0
+ },
+ "isInitial": false
+ },
+ {
+ "_id": "5db031d78a34d4a69905b5e3",
+ "slug": "hello-world",
+ "source": {
+ "_id": "5de52a02c676eee677e97f41",
+ "oldEPiServerId": 2,
+ "title": "Substance Misuse Management in General Practice",
+ "__v": 0
+ },
+ "shortSummary": "test",
+ "resourceLinks": "http://localhost:3010/keystone/items",
+ "url": "https://www.bbc.co.uk/",
+ "title": "Hello world",
+ "__v": 0,
+ "comment": "comment
",
+ "publicationDate": "2019-10-23T00:00:00.000Z",
+ "createdDate": "2019-10-23T10:56:40.000Z",
+ "specialities": [
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5df7abf383138898ee1f67ef",
+ "title": "Safety alerts",
+ "key": "mas_evidence_types:Safety%20alerts",
+ "oldEPiServerId": 778708,
+ "__v": 0
+ },
+ "isInitial": false
+ },
+ {
+ "_id": "5db6cbcc8a34d4ca5905b5e4",
+ "slug": "hello-there",
+ "source": {
+ "_id": "5de52a01c676ee589ae97d2a",
+ "oldEPiServerId": 2,
+ "title": "Journal of Surgical Oncology",
+ "__v": 0
+ },
+ "shortSummary": "hello",
+ "resourceLinks": "http://localhost:3010/keystone/items",
+ "url": "https://www.google.com/",
+ "title": "A Placebo",
+ "__v": 0,
+ "comment": "test
",
+ "publicationDate": "2019-10-28T00:00:00.000Z",
+ "createdDate": "2019-10-28T11:07:11.000Z",
+ "specialities": [
+ {
+ "_id": "5e0e4331200a585f718e1ee5",
+ "key": "be1c2e2f-745e-4a82-b5aa-d4cef4d31a1b",
+ "title": "Anaesthesia and pain",
+ "__v": 0
+ },
+ {
+ "_id": "5e0e4331200a58dead8e1ee8",
+ "key": "53fc67a4-46d8-4171-9447-7fcf216c8749",
+ "title": "Complementary and alternative therapies",
+ "__v": 0
+ }
+ ],
+ "evidenceType": {
+ "_id": "5df7abf383138898ee1f67ef",
+ "title": "Safety alerts",
+ "key": "mas_evidence_types:Safety%20alerts",
+ "oldEPiServerId": 778708,
+ "__v": 0
+ },
+ "isInitial": false
+ }
+]
\ No newline at end of file
diff --git a/lambda/MAS.Tests/Infrastructure/MASWebApplicationFactory.cs b/lambda/MAS.Tests/Infrastructure/MASWebApplicationFactory.cs
new file mode 100644
index 00000000..38b75bca
--- /dev/null
+++ b/lambda/MAS.Tests/Infrastructure/MASWebApplicationFactory.cs
@@ -0,0 +1,46 @@
+using MAS.Configuration;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace MAS.Tests.Infrastructure
+{
+ ///
+ /// See https://github.com/aspnet/AspNetCore.Docs/issues/7063#issuecomment-414661566
+ ///
+ public class MASWebApplicationFactory : WebApplicationFactory
+ {
+ protected override IWebHostBuilder CreateWebHostBuilder()
+ {
+ return WebHost.CreateDefaultBuilder()
+ .UseStartup()
+ .UseLambdaServer();
+ }
+
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ var projectDir = Directory.GetCurrentDirectory();
+
+ builder.ConfigureAppConfiguration((context, conf) =>
+ {
+ conf.AddJsonFile(Path.Combine(projectDir, "appsettings.test.json"));
+ });
+
+ builder.ConfigureTestServices(services => {
+ AppSettings.CMSConfig.BaseUrl = new Uri("file://" + projectDir + "/Feeds").ToString();
+ });
+
+ base.ConfigureWebHost(builder);
+ }
+ }
+}
diff --git a/lambda/MAS.Tests/Infrastructure/TestAppSettings.cs b/lambda/MAS.Tests/Infrastructure/TestAppSettings.cs
index 20f00c4c..1fb859af 100644
--- a/lambda/MAS.Tests/Infrastructure/TestAppSettings.cs
+++ b/lambda/MAS.Tests/Infrastructure/TestAppSettings.cs
@@ -4,22 +4,36 @@
namespace MAS.Tests.Infrastructure
{
- public class TestAppSettings
+ ///
+ /// Use this in integration tests to override CMS config per test e.g. AppSettings.CMSConfig = TestAppSettings.CMS.InvalidURI;
+ ///
+ public static class TestAppSettings
{
- public static CMSConfig GetInvalidURI()
+ public static class CMS
{
- return new CMSConfig()
+ public static CMSConfig Default
{
- URI = new Uri("file://" + Directory.GetCurrentDirectory() + "/Feeds/nonexistanturl").ToString()
- };
- }
+ get
+ {
+ return new CMSConfig()
+ {
+ BaseUrl = new Uri("file://" + Directory.GetCurrentDirectory() + "/Feeds").ToString(),
+ AllItemsPath = "/all-items.json",
+ DailyItemsPath = "/daily-items.json",
+ };
+ }
+ }
- public static CMSConfig GetMultipleItemsFeed()
- {
- return new CMSConfig()
+ public static CMSConfig InvalidURI
{
- URI = new Uri("file://" + Directory.GetCurrentDirectory() + "/Feeds/multiple-items.json").ToString()
- };
+ get
+ {
+ return new CMSConfig()
+ {
+ BaseUrl = new Uri("file://" + Directory.GetCurrentDirectory() + "/Feeds/nonexistanturl").ToString()
+ };
+ }
+ }
}
}
}
diff --git a/lambda/MAS.Tests/Infrastructure/TestBase.cs b/lambda/MAS.Tests/Infrastructure/TestBase.cs
index e2264ec9..bec7c97b 100644
--- a/lambda/MAS.Tests/Infrastructure/TestBase.cs
+++ b/lambda/MAS.Tests/Infrastructure/TestBase.cs
@@ -1,34 +1,46 @@
using MAS.Configuration;
+using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
+using System;
using System.Net.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.DependencyInjection.Extensions;
namespace MAS.Tests.Infrastructure
{
- public class TestBase
+ public abstract class TestBase : IDisposable
{
- protected readonly TestServer _server;
- protected readonly HttpClient _client;
- protected readonly IConfigurationRoot _config;
+ protected readonly MASWebApplicationFactory _factory;
public TestBase()
{
- _config = new ConfigurationBuilder()
- .AddJsonFile("appsettings.json")
- .AddUserSecrets("adafe3d8-65fb-49fd-885e-03341a36dc88")
- .Build();
+ _factory = new MASWebApplicationFactory();
+ }
- var builder = new WebHostBuilder()
- .UseContentRoot("../../../../MAS")
- .ConfigureServices(services =>
+ protected WebApplicationFactory WithImplementation(TService implementation)
+ {
+ return _factory.WithWebHostBuilder(builder =>
+ {
+ builder.ConfigureTestServices(services =>
{
- AppSettings.Configure(services, _config);
- })
- .UseEnvironment("Production")
- .UseStartup(typeof(Startup));
- _server = new TestServer(builder);
- _client = _server.CreateClient();
+ var serviceProvider = services.BuildServiceProvider();
+
+ var descriptor =
+ new ServiceDescriptor(
+ typeof(TService), implementation);
+
+ services.Replace(descriptor);
+ });
+ });
+ }
+
+ public virtual void Dispose()
+ {
+ _factory.Dispose();
}
}
}
diff --git a/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApproved.approved.txt b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApproved.approved.txt
new file mode 100644
index 00000000..aed03a43
--- /dev/null
+++ b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApproved.approved.txt
@@ -0,0 +1,53 @@
+
+DailyEmail
+
+
+
+
Safety alerts
+
+
+ Wonder Drug
+
+ ACP Journal Club
+
+ Anaesthesia and pain | Complementary and alternative therapies
+
+ testing
+
+
SPS Comment
+
+
+ MY MEDICINE
+
+ ADHD Awareness Month
+
+ Anaesthesia and pain | Complementary and alternative therapies
+
+ this is my medicine
+
+
SPS Comment
+
+
+ Hello world
+
+ Substance Misuse Management in General Practice
+
+ Anaesthesia and pain | Complementary and alternative therapies
+
+ test
+
+
SPS Comment
+
+
+ A Placebo
+
+ Journal of Surgical Oncology
+
+ Anaesthesia and pain | Complementary and alternative therapies
+
+ hello
+
+
SPS Comment
+
+
+
diff --git a/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApprovedForSingleItem.approved.txt b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApprovedForSingleItem.approved.txt
new file mode 100644
index 00000000..634797a8
--- /dev/null
+++ b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApprovedForSingleItem.approved.txt
@@ -0,0 +1,20 @@
+
+DailyEmail
+
+
+
+
Safety alerts
+
+
+ Wonder Drug
+
+ ACP Journal Club
+
+ Anaesthesia and pain | Complementary and alternative therapies
+
+ testing
+
+
SPS Comment
+
+
+
diff --git a/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApprovedForSingleItemWithSpecificDate.approved.txt b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApprovedForSingleItemWithSpecificDate.approved.txt
new file mode 100644
index 00000000..634797a8
--- /dev/null
+++ b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.EmailBodyHtmlMatchesApprovedForSingleItemWithSpecificDate.approved.txt
@@ -0,0 +1,20 @@
+
+DailyEmail
+
+
+
+
Safety alerts
+
+
+ Wonder Drug
+
+ ACP Journal Club
+
+ Anaesthesia and pain | Complementary and alternative therapies
+
+ testing
+
+
SPS Comment
+
+
+
diff --git a/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.cs b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.cs
new file mode 100644
index 00000000..363780a9
--- /dev/null
+++ b/lambda/MAS.Tests/IntegrationTests/DailyEmailTests.cs
@@ -0,0 +1,121 @@
+using MailChimp.Net.Core;
+using MailChimp.Net.Interfaces;
+using MailChimp.Net.Models;
+using MAS.Configuration;
+using MAS.Models;
+using MAS.Services;
+using MAS.Tests.Infrastructure;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Web.Mvc;
+using Xunit;
+using Source = MAS.Models.Source;
+
+namespace MAS.Tests.IntegrationTests
+{
+ public class TestMailService : IMailService
+ {
+ public Task CreateAndSendDailyAsync(string subject, string previewText, string body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class DailyEmailTests : TestBase
+ {
+ [Fact]
+ public async void EmailBodyHtmlMatchesApproved()
+ {
+ // Arrange
+ var fakeMailService = new Mock();
+
+ string bodyHtml = string.Empty;
+ fakeMailService.Setup(s => s.CreateAndSendDailyAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((subject, previewText, body) => bodyHtml = body)
+ .ReturnsAsync("1234");
+
+ var client = WithImplementation(fakeMailService.Object).CreateClient();
+
+ // Act
+ var response = await client.PutAsync("/api/mail/daily", null);
+
+ // Assert
+ response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
+
+ var responseText = await response.Content.ReadAsStringAsync();
+ responseText.ShouldBe("1234");
+
+ bodyHtml.ShouldMatchApproved();
+ }
+
+ [Fact]
+ public async void EmailBodyHtmlMatchesApprovedForSingleItem()
+ {
+ // Arrange
+ var fakeMailService = new Mock();
+
+ string bodyHtml = string.Empty;
+ fakeMailService.Setup(s => s.CreateAndSendDailyAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((subject, previewText, body) => bodyHtml = body)
+ .ReturnsAsync("1234");
+
+ var client = WithImplementation(fakeMailService.Object).CreateClient();
+
+ AppSettings.CMSConfig.DailyItemsPath = "/daily-items-single.json";
+
+ // Act
+ var response = await client.PutAsync("/api/mail/daily", null);
+
+ // Assert
+ response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
+
+ var responseText = await response.Content.ReadAsStringAsync();
+ responseText.ShouldBe("1234");
+
+ bodyHtml.ShouldMatchApproved();
+ }
+
+ [Fact]
+ public async void EmailBodyHtmlMatchesApprovedForSingleItemWithSpecificDate()
+ {
+ // Arrange
+ var fakeMailService = new Mock();
+
+ string bodyHtml = string.Empty;
+ fakeMailService.Setup(s => s.CreateAndSendDailyAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((subject, previewText, body) => bodyHtml = body)
+ .ReturnsAsync("1234");
+
+ var client = WithImplementation(fakeMailService.Object).CreateClient();
+
+ AppSettings.CMSConfig.DailyItemsPath = "/daily-items-{0}.json";
+
+ // Act
+ var response = await client.PutAsync("/api/mail/daily?date=01-01-2020", null);
+
+ // Assert
+ response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
+
+ var responseText = await response.Content.ReadAsStringAsync();
+ responseText.ShouldBe("1234");
+
+ bodyHtml.ShouldMatchApproved();
+ }
+
+ }
+}
diff --git a/lambda/MAS.Tests/IntegrationTests/MailControllerTests.cs b/lambda/MAS.Tests/IntegrationTests/MailControllerTests.cs
new file mode 100644
index 00000000..9616a263
--- /dev/null
+++ b/lambda/MAS.Tests/IntegrationTests/MailControllerTests.cs
@@ -0,0 +1,44 @@
+using System.Threading.Tasks;
+using Xunit;
+using Shouldly;
+using MAS.Tests.Infrastructure;
+using System.Text;
+using System;
+using MAS.Configuration;
+using System.Net.Http.Headers;
+using System.Net.Http;
+using MailChimp.Net.Models;
+using Newtonsoft.Json;
+
+namespace MAS.Tests.IntegrationTests.Mail
+{
+ //public class MailControllerTests : TestBase
+ //{
+ // [Fact]
+ // public async Task PutRequestCreatesAndSendsCampaign()
+ // {
+ // //Arrange
+ // const string mailChimpCampaignsURI = "https://us5.api.mailchimp.com/3.0/campaigns/";
+
+ // var authValue = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"anystring:{AppSettings.MailConfig.ApiKey}")));
+ // var client = new HttpClient()
+ // {
+ // DefaultRequestHeaders = { Authorization = authValue }
+ // };
+
+ // //Act
+ // var response = await _client.PutAsync("/api/mail/daily", null);
+
+ // //Get campaign to check if it saved
+ // var campaignId = await response.Content.ReadAsStringAsync();
+ // var campaign = await client.GetAsync(mailChimpCampaignsURI + campaignId);
+ // var campaignJson = await campaign.Content.ReadAsStringAsync();
+ // var campaignResult = JsonConvert.DeserializeObject(campaignJson);
+
+ // // Assert
+ // response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
+ // campaignResult.Status.ShouldNotBeNull();
+ // campaignResult.Status.ShouldNotBe("Draft");
+ // }
+ //}
+}
diff --git a/lambda/MAS.Tests/IntergrationTests/ContentControllerTests.PutCMSItemSavesItemIntoS3.approved.txt b/lambda/MAS.Tests/IntegrationTests/StaticContentTests.PutCMSItemSavesItemIntoS3.approved.txt
similarity index 96%
rename from lambda/MAS.Tests/IntergrationTests/ContentControllerTests.PutCMSItemSavesItemIntoS3.approved.txt
rename to lambda/MAS.Tests/IntegrationTests/StaticContentTests.PutCMSItemSavesItemIntoS3.approved.txt
index 4f56ef60..d1a2aef8 100644
--- a/lambda/MAS.Tests/IntergrationTests/ContentControllerTests.PutCMSItemSavesItemIntoS3.approved.txt
+++ b/lambda/MAS.Tests/IntegrationTests/StaticContentTests.PutCMSItemSavesItemIntoS3.approved.txt
@@ -18,10 +18,10 @@
-
+
-
+
diff --git a/lambda/MAS.Tests/IntegrationTests/StaticContentTests.cs b/lambda/MAS.Tests/IntegrationTests/StaticContentTests.cs
new file mode 100644
index 00000000..39b8957a
--- /dev/null
+++ b/lambda/MAS.Tests/IntegrationTests/StaticContentTests.cs
@@ -0,0 +1,68 @@
+using System.Threading.Tasks;
+using Xunit;
+using MAS.Tests.Infrastructure;
+using Shouldly;
+using Amazon.S3;
+using Amazon;
+using MAS.Configuration;
+using System.IO;
+using MAS.Models;
+using Amazon.S3.Model;
+using Newtonsoft.Json;
+using System.Text;
+using System.Net.Http;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Moq;
+using System.Threading;
+
+namespace MAS.Tests.IntegrationTests
+{
+ public class StaticContentTests : TestBase
+ {
+ private Item item = new Item()
+ {
+ Id = "1234",
+ Slug = "effect-of-vit-d",
+ URL = "www.website.com",
+ Title = "Effect of Vitamin D and Omega-3 Fatty Acid Supplementation on Kidney Function in Patients With Type 2 Diabetes: A Randomized Clinical Trial",
+ ShortSummary = "RCT (n=1,312) found that among adults with type 2 diabetes, supplementation with vitamin D3 or omega-3 fatty acids, compared with placebo, resulted in no significant difference in change in eGFR at 5 years.",
+ Source = new Source()
+ {
+ Id = "789",
+ Title = "Journal of the American Medical Association"
+ },
+ EvidenceType = new EvidenceType
+ {
+ Key = "mas_evidence_types:Safety%20alerts",
+ Title = "Safety alerts"
+ },
+ Comment = "A related editorial discusses this research and details previous epidemiological studies that suggest improved outcomes with vitamin D supplementation in various clinical scenarios. It states that contrasting the results of this study and its predecessor vitamin D trials with the impressive body of epidemiological research that implicated vitamin D deficiency in various adverse health outcomes offers a stark lesson on the chasm between association and causation. Editorial authors highlight that it now seems safe to conclude that many prior epidemiological associations between vitamin D deficiency and adverse health outcomes were driven by unmeasured residual confounding or reverse causality.",
+ ResourceLinks = "Vitamin D and Health Outcomes - Then Came the Randomized Clinical Trials
\r\nLink 2
"
+
+ };
+
+ [Fact]
+ public async Task PutCMSItemSavesItemIntoS3()
+ {
+ // Arrange
+ var fakeS3Service = new Mock();
+
+ PutObjectRequest putObjectRequest = null;
+ fakeS3Service.Setup(s => s.PutObjectAsync(It.IsAny(), default(CancellationToken)))
+ .Callback((pOR, cT) => putObjectRequest = pOR)
+ .ReturnsAsync(new PutObjectResponse { HttpStatusCode = System.Net.HttpStatusCode.OK });
+
+ var client = WithImplementation(fakeS3Service.Object).CreateClient();
+ var content = new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json");
+
+ // Act
+ var response = await client.PutAsync("/api/content/", content);
+
+ // Assert
+ response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
+ putObjectRequest.Key.ShouldBe("effect-of-vit-d.html");
+ putObjectRequest.ContentBody.ShouldMatchApproved();
+ }
+
+ }
+}
diff --git a/lambda/MAS.Tests/IntergrationTests/ContentControllerTests.cs b/lambda/MAS.Tests/IntergrationTests/ContentControllerTests.cs
deleted file mode 100644
index 2dc7100b..00000000
--- a/lambda/MAS.Tests/IntergrationTests/ContentControllerTests.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using System.Threading.Tasks;
-using Xunit;
-using MAS.Tests.Infrastructure;
-using Shouldly;
-using Amazon.S3;
-using Amazon;
-using MAS.Configuration;
-using System.IO;
-using MAS.Models;
-using Amazon.S3.Model;
-using Newtonsoft.Json;
-using System.Text;
-using System.Net.Http;
-
-namespace MAS.Tests.IntergrationTests.Content
-{
- public class ContentControllerTests : TestBase
- {
- [Fact]
- public async Task PutCMSItemSavesItemIntoS3()
- {
- //Arrange
- AmazonS3Config config = new AmazonS3Config()
- {
- RegionEndpoint = RegionEndpoint.EUWest1,
- ServiceURL = AppSettings.AWSConfig.ServiceURL,
- ForcePathStyle = true
- };
- AmazonS3Client s3Client = new AmazonS3Client(AppSettings.AWSConfig.AccessKey, AppSettings.AWSConfig.SecretKey, config);
-
- Item item = new Item()
- {
- Id = "1234",
- Slug = "Effect-of-vit-d",
- URL = "www.website.com",
- Title = "Effect of Vitamin D and Omega-3 Fatty Acid Supplementation on Kidney Function in Patients With Type 2 Diabetes: A Randomized Clinical Trial",
- ShortSummary = "RCT (n=1,312) found that among adults with type 2 diabetes, supplementation with vitamin D3 or omega-3 fatty acids, compared with placebo, resulted in no significant difference in change in eGFR at 5 years.",
- Source = new Source()
- {
- Id = "789",
- Title = "Journal of the American Medical Association"
- },
- EvidenceType = new EvidenceType
- {
- Key = "mas_evidence_types:Safety%20alerts",
- Title = "Safety alerts"
- },
- Comment = "A related editorial discusses this research and details previous epidemiological studies that suggest improved outcomes with vitamin D supplementation in various clinical scenarios. It states that contrasting the results of this study and its predecessor vitamin D trials with the impressive body of epidemiological research that implicated vitamin D deficiency in various adverse health outcomes offers a stark lesson on the chasm between association and causation. Editorial authors highlight that it now seems safe to conclude that many prior epidemiological associations between vitamin D deficiency and adverse health outcomes were driven by unmeasured residual confounding or reverse causality.",
- ResourceLinks = "Vitamin D and Health Outcomes - Then Came the Randomized Clinical Trials
\r\nLink 2
"
-
- };
-
- var content = new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json");
-
- //Act
- var response = await _client.PutAsync("/api/content/", content);
-
- // Assert
- response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
-
- using (var bucketItem = await s3Client.GetObjectAsync(AppSettings.AWSConfig.BucketName, "Effect-of-vit-d.html"))
- {
- using (StreamReader reader = new StreamReader(bucketItem.ResponseStream))
- {
- string contents = reader.ReadToEnd();
- contents.ShouldMatchApproved();
- }
- }
- }
-
- }
-}
diff --git a/lambda/MAS.Tests/IntergrationTests/MailControllerTests.cs b/lambda/MAS.Tests/IntergrationTests/MailControllerTests.cs
deleted file mode 100644
index 096ce967..00000000
--- a/lambda/MAS.Tests/IntergrationTests/MailControllerTests.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.Threading.Tasks;
-using Xunit;
-using Shouldly;
-using MAS.Tests.Infrastructure;
-using System.Text;
-using System;
-using MAS.Configuration;
-using System.Net.Http.Headers;
-using System.Net.Http;
-using MailChimp.Net.Models;
-using Newtonsoft.Json;
-
-namespace MAS.Tests.IntergrationTests.Mail
-{
- public class MailControllerTests : TestBase
- {
- [Fact]
- public async Task PutRequestCreatesAndSendsCampaign()
- {
- //Arrange
- const string mailChimpCampaignsURI = "https://us5.api.mailchimp.com/3.0/campaigns/";
- AppSettings.CMSConfig = TestAppSettings.GetMultipleItemsFeed();
-
- var authValue = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"anystring:{AppSettings.MailConfig.ApiKey}")));
- var client = new HttpClient()
- {
- DefaultRequestHeaders = { Authorization = authValue }
- };
-
- //Act
- var response = await _client.PutAsync("/api/mail/daily", null);
-
- //Get campaign to check if it saved
- var campaignId = await response.Content.ReadAsStringAsync();
- var campaign = await client.GetAsync(mailChimpCampaignsURI + campaignId);
- var campaignJson = await campaign.Content.ReadAsStringAsync();
- var campaignResult = JsonConvert.DeserializeObject(campaignJson);
-
- // Assert
- response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK);
- campaignResult.Status.ShouldNotBeNull();
- campaignResult.Status.ShouldNotBe("Draft");
- }
- }
-}
diff --git a/lambda/MAS.Tests/MAS.Tests.csproj b/lambda/MAS.Tests/MAS.Tests.csproj
index c15eb412..d4bafc9b 100644
--- a/lambda/MAS.Tests/MAS.Tests.csproj
+++ b/lambda/MAS.Tests/MAS.Tests.csproj
@@ -10,25 +10,27 @@
-
-
-
+
+
+
+
+
-
- PreserveNewest
+
+ PreserveNewest
-
- PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
PreserveNewest
-
-
-
- Always
-
@@ -36,7 +38,8 @@
-
+
+
@@ -48,4 +51,9 @@
+
+
+ PreserveNewest
+
+
\ No newline at end of file
diff --git a/lambda/MAS.Tests/UnitTests/Controllers/MailControllerTests.cs b/lambda/MAS.Tests/UnitTests/Controllers/MailControllerTests.cs
new file mode 100644
index 00000000..e339839d
--- /dev/null
+++ b/lambda/MAS.Tests/UnitTests/Controllers/MailControllerTests.cs
@@ -0,0 +1,63 @@
+using MAS.Controllers;
+using MAS.Models;
+using MAS.Services;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace MAS.Tests.UnitTests.Controllers
+{
+ public class MailControllerTests
+ {
+ [Fact]
+ public async void GetDailyItemsForToday()
+ {
+
+ var mockContentService = new Mock();
+ var mailService = new MailController(Mock.Of(), mockContentService.Object, Mock.Of(), Mock.Of>());
+
+ //Act
+ await mailService.PutMailAsync();
+
+ //Assert
+ mockContentService.Verify(mock => mock.GetDailyItemsAsync(null), Times.Once());
+ }
+
+ [Fact]
+ public async void GetDailyItemsForSpecificDate()
+ {
+ DateTime date = new DateTime(2020, 1, 15);
+ var mockContentService = new Mock();
+ var mailService = new MailController(Mock.Of(), mockContentService.Object, Mock.Of(), Mock.Of>());
+
+ //Act
+ await mailService.PutMailAsync(date);
+
+ //Assert
+ mockContentService.Verify(mock => mock.GetDailyItemsAsync(date), Times.Once());
+ }
+
+ [Fact]
+ public async void RendersDailyViewWithContentItems()
+ {
+ var items = new List- () { }.AsEnumerable();
+ var mockContentService = new Mock();
+ mockContentService.Setup(x => x.GetDailyItemsAsync(null)).ReturnsAsync(items);
+
+ var mockViewRenderer = new Mock();
+
+ var mailController = new MailController(Mock.Of(), mockContentService.Object, mockViewRenderer.Object, Mock.Of>());
+
+ //Act
+ await mailController.PutMailAsync();
+
+ //Assert
+ mockViewRenderer.Verify(mock => mock.RenderViewAsync(mailController, "~/Views/DailyEmail.cshtml", items, false), Times.Once());
+ }
+ }
+}
diff --git a/lambda/MAS.Tests/UnitTests/ContentServiceTests.cs b/lambda/MAS.Tests/UnitTests/Services/ContentServiceTests.cs
similarity index 81%
rename from lambda/MAS.Tests/UnitTests/ContentServiceTests.cs
rename to lambda/MAS.Tests/UnitTests/Services/ContentServiceTests.cs
index 4845c4ae..c60ae0aa 100644
--- a/lambda/MAS.Tests/UnitTests/ContentServiceTests.cs
+++ b/lambda/MAS.Tests/UnitTests/Services/ContentServiceTests.cs
@@ -11,7 +11,7 @@
namespace MAS.Tests.UnitTests
{
- public class ContentServiceTests : TestBase
+ public class ContentServiceTests
{
[Fact]
public async Task ReadMultipleItems()
@@ -19,11 +19,11 @@ public async Task ReadMultipleItems()
//Arrange
var mockLogger = new Mock>();
- AppSettings.CMSConfig = TestAppSettings.GetMultipleItemsFeed();
+ AppSettings.CMSConfig = TestAppSettings.CMS.Default;
var contentService = new ContentService(mockLogger.Object);
//Act
- var result = await contentService.GetItemsAsync();
+ var result = await contentService.GetAllItemsAsync();
//Assert
result.Count().ShouldBe(4);
@@ -39,11 +39,11 @@ public async Task InvalidURIThrowsError()
//Arrange
var mockLogger = new Mock>();
- AppSettings.CMSConfig = TestAppSettings.GetInvalidURI();
+ AppSettings.CMSConfig = TestAppSettings.CMS.InvalidURI;
var contentService = new ContentService(mockLogger.Object);
//Act + Assert
- await Should.ThrowAsync(() => contentService.GetItemsAsync());
+ await Should.ThrowAsync(() => contentService.GetAllItemsAsync());
}
}
}
diff --git a/lambda/MAS.Tests/UnitTests/MailServiceTests.cs b/lambda/MAS.Tests/UnitTests/Services/MailServiceTests.cs
similarity index 88%
rename from lambda/MAS.Tests/UnitTests/MailServiceTests.cs
rename to lambda/MAS.Tests/UnitTests/Services/MailServiceTests.cs
index d2674883..98c6bde0 100644
--- a/lambda/MAS.Tests/UnitTests/MailServiceTests.cs
+++ b/lambda/MAS.Tests/UnitTests/Services/MailServiceTests.cs
@@ -13,7 +13,7 @@
namespace MAS.Tests.UnitTests
{
- public class MailServiceTests : TestBase
+ public class MailServiceTests
{
[Fact]
public void CreateCampaignAndSendToMailChimp()
@@ -28,8 +28,10 @@ public void CreateCampaignAndSendToMailChimp()
var mailService = new MailService(mockMailChimpManager.Object, mockLogger.Object);
+ AppSettings.MailConfig = Mock.Of();
+
//Act
- var response = mailService.CreateAndSendCampaignAsync("Test Subject", "Preview Text", "Body Text");
+ var response = mailService.CreateAndSendDailyAsync("Test Subject", "Preview Text", "Body Text");
//Assert
response.Exception.ShouldBe(null);
@@ -51,7 +53,7 @@ public void ErrorInSendingCampaignShouldThrowError()
var mailService = new MailService(mockMailChimpManager.Object, mockLogger.Object);
//Act + Assert
- Should.Throw(() => mailService.CreateAndSendCampaignAsync("Test Subject", "Preview Text", "Body Text"));
+ Should.Throw(() => mailService.CreateAndSendDailyAsync("Test Subject", "Preview Text", "Body Text"));
}
}
}
diff --git a/lambda/MAS.Tests/UnitTests/ViewRendererTests.cs b/lambda/MAS.Tests/UnitTests/Services/ViewRendererTests.cs
similarity index 100%
rename from lambda/MAS.Tests/UnitTests/ViewRendererTests.cs
rename to lambda/MAS.Tests/UnitTests/Services/ViewRendererTests.cs
diff --git a/lambda/MAS.Tests/appsettings.Development.json b/lambda/MAS.Tests/appsettings.Development.json
deleted file mode 100644
index 38fb825a..00000000
--- a/lambda/MAS.Tests/appsettings.Development.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "AWS": {
- "Region": "eu-west-1"
- }
-}
\ No newline at end of file
diff --git a/lambda/MAS.Tests/appsettings.json b/lambda/MAS.Tests/appsettings.json
deleted file mode 100644
index d9f81bc1..00000000
--- a/lambda/MAS.Tests/appsettings.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "Logging": {
- "RabbitMQHost": "",
- "RabbitMQPort": "",
- "IncludeScopes": false,
- "LogFilePath": "Serilog-{Date}.json",
- "LogLevel": {
- "Default": "Debug",
- "System": "Information",
- "Microsoft": "Information"
- },
- "UseRabbit": true,
- "UseFile": true
- },
- "AppSettings": {
- "Environment": {
- "Name": "local"
- }
- },
- "AWS": {
- "AccessKey": "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",
- "SecretKey": "",
- "ServiceURL": "",
- "BucketName": ""
- },
- "CMS": {
- "URI": "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"
- },
- "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",
- "ListId": "",
- "DailyTemplateId": "",
- "CampaignFolderId": ""
- }
-}
\ No newline at end of file
diff --git a/lambda/MAS.Tests/appsettings.test.json b/lambda/MAS.Tests/appsettings.test.json
new file mode 100644
index 00000000..8f0b8bf5
--- /dev/null
+++ b/lambda/MAS.Tests/appsettings.test.json
@@ -0,0 +1,15 @@
+{
+ "AppSettings": {
+ "Environment": {
+ "Name": "tests"
+ }
+ },
+ "AWS": {
+ "StaticUrl": "http://mas-integration-tests/"
+ },
+ "CMS": {
+ "BaseUrl": "We override this in test setup to point to the Feeds folder on the filesystem",
+ "AllItemsPath": "/all-items.json",
+ "DailyItemsPath": "/daily-items.json"
+ }
+}
diff --git a/lambda/MAS/Configuration/CMSConfig.cs b/lambda/MAS/Configuration/CMSConfig.cs
index a7679e92..d007ae1e 100644
--- a/lambda/MAS/Configuration/CMSConfig.cs
+++ b/lambda/MAS/Configuration/CMSConfig.cs
@@ -2,6 +2,8 @@
{
public class CMSConfig
{
- public string URI { get; set; }
+ public string BaseUrl { get; set; }
+ public string AllItemsPath { get; set; }
+ public string DailyItemsPath { get; set; }
}
}
diff --git a/lambda/MAS/Controllers/MailController.cs b/lambda/MAS/Controllers/MailController.cs
index d9ea13a4..e81a8635 100644
--- a/lambda/MAS/Controllers/MailController.cs
+++ b/lambda/MAS/Controllers/MailController.cs
@@ -1,9 +1,9 @@
-using System;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
-using MAS.Services;
+using MAS.Services;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
namespace MAS.Controllers
{
@@ -12,30 +12,35 @@ public class MailController : ControllerBase
{
private readonly IMailService _mailService;
private readonly IContentService _contentService;
+ private readonly IViewRenderer _viewRenderer;
+ private readonly ILogger _logger;
- public MailController(IMailService mailService, IContentService contentService)
+ public MailController(IMailService mailService, IContentService contentService, IViewRenderer viewRenderer, ILogger logger)
{
_mailService = mailService;
_contentService = contentService;
+ _viewRenderer = viewRenderer;
+ _logger = logger;
}
- //PUT api/mail/daily
+ //PUT api/mail/daily?date=01-01-2020
[HttpPut("daily")]
- public async Task PutMailAsync()
+ public async Task PutMailAsync(DateTime? date = null)
{
- var items = await _contentService.GetItemsAsync();
+ var items = await _contentService.GetDailyItemsAsync(date);
- var body = _mailService.CreateEmailBody(items);
+ var body = await _viewRenderer.RenderViewAsync(this, "~/Views/DailyEmail.cshtml", items.ToList());
var subject = "MAS Email";
var previewText = "This MAS email was created " + DateTime.Now.ToShortDateString();
try
{
- var campaignId = await _mailService.CreateAndSendCampaignAsync(subject, previewText, body);
+ var campaignId = await _mailService.CreateAndSendDailyAsync(subject, previewText, body);
return Content(campaignId);
}
catch (Exception e)
{
+ _logger.LogError(e, $"Failed to send daily email - exception: {e.Message}");
return StatusCode(500, new ProblemDetails { Status = 500, Title = e.Message, Detail = e.InnerException?.Message });
}
}
diff --git a/lambda/MAS/Models/Item.cs b/lambda/MAS/Models/Item.cs
index 4dfc11a6..69ecbe69 100644
--- a/lambda/MAS/Models/Item.cs
+++ b/lambda/MAS/Models/Item.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MAS.Models
@@ -13,6 +14,8 @@ public class Item
public string Title { get; set; }
[Required, JsonRequired]
public Source Source { get; set; }
+ [JsonProperty("specialities")]
+ public List Specialities { get; set; }
[Required, JsonRequired]
public EvidenceType EvidenceType { get; set; }
public string ShortSummary { get; set; }
diff --git a/lambda/MAS/Models/Speciality.cs b/lambda/MAS/Models/Speciality.cs
new file mode 100644
index 00000000..800ea34e
--- /dev/null
+++ b/lambda/MAS/Models/Speciality.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MAS.Models
+{
+ public class Speciality
+ {
+ public string Title { get; set; }
+
+ public string Key { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/lambda/MAS/Services/ContentService.cs b/lambda/MAS/Services/ContentService.cs
index 028f802c..809343de 100644
--- a/lambda/MAS/Services/ContentService.cs
+++ b/lambda/MAS/Services/ContentService.cs
@@ -11,7 +11,8 @@ namespace MAS.Services
{
public interface IContentService
{
- Task> GetItemsAsync();
+ Task> GetAllItemsAsync();
+ Task> GetDailyItemsAsync(DateTime? date = null);
}
public class ContentService : IContentService
@@ -23,15 +24,15 @@ public ContentService(ILogger logger)
_logger = logger;
}
- public async Task> GetItemsAsync()
+ public async Task> GetAllItemsAsync()
{
using (WebClient client = new WebClient())
{
try
{
- var jsonStr = await client.DownloadStringTaskAsync(new Uri(AppSettings.CMSConfig.URI));
- var json = JsonConvert.DeserializeObject
- (jsonStr);
- return json;
+ var jsonStr = await client.DownloadStringTaskAsync(new Uri(AppSettings.CMSConfig.BaseUrl + AppSettings.CMSConfig.AllItemsPath));
+ var item = JsonConvert.DeserializeObject
- (jsonStr);
+ return item;
}
catch(Exception e)
{
@@ -40,5 +41,27 @@ public async Task> GetItemsAsync()
}
}
}
+
+ public async Task> GetDailyItemsAsync(DateTime? date = null)
+ {
+ date = date ?? DateTime.Today;
+
+ using (WebClient client = new WebClient())
+ {
+ var path = string.Format(AppSettings.CMSConfig.DailyItemsPath, date.Value.ToString("yyyy-MM-dd"));
+ try
+ {
+ var jsonStr = await client.DownloadStringTaskAsync(new Uri(AppSettings.CMSConfig.BaseUrl + path));
+ var item = JsonConvert.DeserializeObject
- (jsonStr);
+ return item;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError($"Failed to get items from CMS - exception: {e.Message}");
+ throw new Exception($"Failed to get items from CMS - exception: {e.Message}");
+ }
+ }
+ }
+
}
}
diff --git a/lambda/MAS/Services/MailService.cs b/lambda/MAS/Services/MailService.cs
index aa7ab767..cd05b3a0 100644
--- a/lambda/MAS/Services/MailService.cs
+++ b/lambda/MAS/Services/MailService.cs
@@ -3,18 +3,15 @@
using MailChimp.Net.Models;
using MAS.Configuration;
using Microsoft.Extensions.Logging;
-using MAS.Models;
using System;
using System.Collections.Generic;
-using System.Text;
using System.Threading.Tasks;
namespace MAS.Services
{
public interface IMailService
{
- Task CreateAndSendCampaignAsync(string subject, string previewText, string body);
- string CreateEmailBody(IEnumerable
- item);
+ Task CreateAndSendDailyAsync(string subject, string previewText, string body);
}
public class MailService: IMailService
@@ -28,7 +25,7 @@ public MailService(IMailChimpManager mailChimpManager, ILogger logg
_logger = logger;
}
- public async Task CreateAndSendCampaignAsync(string subject, string previewText, string body)
+ public async Task CreateAndSendDailyAsync(string subject, string previewText, string body)
{
try
{
@@ -72,22 +69,5 @@ public async Task CreateAndSendCampaignAsync(string subject, string prev
}
}
-
- public string CreateEmailBody(IEnumerable
- items)
- {
- var body = new StringBuilder();
-
- foreach (var item in items)
- {
- body.Append(item.Source.Title);
- body.Append("
");
- body.Append(item.Title);
- body.Append("
");
- body.Append(item.ShortSummary);
- body.Append("
");
- }
-
- return body.ToString();
- }
}
}
diff --git a/lambda/MAS/Views/DailyEmail.cshtml b/lambda/MAS/Views/DailyEmail.cshtml
new file mode 100644
index 00000000..f9e2fbde
--- /dev/null
+++ b/lambda/MAS/Views/DailyEmail.cshtml
@@ -0,0 +1,156 @@
+@using MAS.Models;
+@model List-
+@{
+ ViewData["Title"] = "MAS Daily";
+
+ var groupedItems = Model.GroupBy(x => x.EvidenceType.Title).ToList();
+ var specialitiesAsString = String.Join(',', Model.SelectMany(x => x.Specialities).Select(x => x.Title).ToList());
+}
+
+
+
+
+
+
+
+
+
+ |
+
+ *|INTERESTED:Daily specialities of interest:@specialitiesAsString|*
+ Articles that match your chosen specialities today
+ *|ELSE:|*
+ No articles match your chosen specialities today
+ *|END:INTERESTED|*
+ - Edit your subscription
+
+ |
+ |
+
+
+
+ |
+
+
+
+
+
+@foreach (var group in groupedItems)
+{
+ var evidenceType = group.Key;
+
+ @:*|INTERESTED:Daily specialities of interest:@specialitiesAsString|*
+
+
+
+
+ @evidenceType
+ |
+
+
+
+
+ @foreach (var item in group)
+ {
+ @:*|INTERESTED:Daily specialities of interest:@specialitiesAsString|*
+
+
+
+
+
+
+
+ |
+
+ @item.ShortSummary
+ Read SPS commentary
+
+ @item.Source.Title
+ @foreach (var spec in item.Specialities)
+ {
+ @spec.Title
+ }
+
+ |
+ |
+
+
+
+ |
+
+
+
+ @:*|END:INTERESTED|*
+ }
+ @:*|END:INTERESTED|*
+}
+
+*|INTERESTED:Send me everything from Medicines Awareness Daily:Send me everything|*
+
+
+
+
+
+
+
+ |
+
+ All articles from Medicines awareness services: Daily edition
+
+ |
+ |
+
+
+
+ |
+
+
+
+
+@foreach (var group in groupedItems)
+{
+ var evidenceType = group.Key;
+
+
+
+
+
+ @evidenceType
+ |
+
+
+
+
+ @foreach (var item in group)
+ {
+
+
+
+
+
+
+
+ |
+
+ @item.ShortSummary
+ Read SPS commentary
+
+ @item.Source.Title
+ @foreach (var spec in item.Specialities)
+ {
+ @spec.Title
+ }
+
+ |
+ |
+
+
+
+ |
+
+
+
+ }
+}
+*|END:INTERESTED|*
+
diff --git a/lambda/MAS/Views/Shared/_EmailLayout.cshtml b/lambda/MAS/Views/Shared/_EmailLayout.cshtml
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/lambda/MAS/Views/Shared/_EmailLayout.cshtml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lambda/MAS/appsettings.json b/lambda/MAS/appsettings.json
index 09fba1e0..10618c25 100644
--- a/lambda/MAS/appsettings.json
+++ b/lambda/MAS/appsettings.json
@@ -26,12 +26,14 @@
"StaticURL": ""
},
"CMS": {
- "URI": "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"
+ "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}"
},
"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",
"ListId": "",
- "DailyTemplateId": "",
+ "DailyTemplateId": 0,
"CampaignFolderId": ""
}
}
\ No newline at end of file
diff --git a/mailchimp-templates/daily.html b/mailchimp-templates/daily.html
new file mode 100644
index 00000000..38e603e4
--- /dev/null
+++ b/mailchimp-templates/daily.html
@@ -0,0 +1,2528 @@
+
+
+
+
+
+ Medicines awareness service - daily edition
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
\ No newline at end of file