739 lines
25 KiB
JavaScript
739 lines
25 KiB
JavaScript
const assert = require("assert");
|
|
const {
|
|
loadModule,
|
|
createRes,
|
|
respondSuccessMock,
|
|
respondErrorMock
|
|
} = require("./_shared.cjs");
|
|
|
|
const tests = [];
|
|
const test = (name, fn) => tests.push({ name, fn });
|
|
|
|
test("getevents returns INCIDENT_ID_REQUIRED when incidentID missing", async () => {
|
|
const mod = loadModule("pages/api/email/getevents.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: { get: async () => ({ data: { value: [] } }) },
|
|
_: { get: (obj, key, fallback) => obj?.data?.value || fallback },
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "INCIDENT_ID_REQUIRED");
|
|
});
|
|
|
|
test("getevents returns SIPS_RECORD_NOT_FOUND when linked record is missing", async () => {
|
|
const mod = loadModule("pages/api/email/getevents.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: { get: async () => ({ data: { value: [] } }) },
|
|
_: { get: (obj, key, fallback) => obj?.data?.value || fallback },
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: { incidentID: "123" } };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 404);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "SIPS_RECORD_NOT_FOUND");
|
|
});
|
|
|
|
test("getevents dependency failure returns EVENTS_FETCH_FAILED", async () => {
|
|
const mod = loadModule("pages/api/email/getevents.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: {
|
|
get: async () => {
|
|
throw new Error("upstream failed");
|
|
}
|
|
},
|
|
_: { get: (obj, key, fallback) => obj?.data?.value || fallback },
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: { incidentID: "123" } };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 500);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "EVENTS_FETCH_FAILED");
|
|
});
|
|
|
|
test("getdocuments returns INCIDENT_ID_REQUIRED when incidentid missing", async () => {
|
|
const mod = loadModule("pages/api/email/getdocuments.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeadersPaged: () => ({}),
|
|
axios: { get: async () => ({ data: { value: [] } }) },
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "INCIDENT_ID_REQUIRED");
|
|
});
|
|
|
|
test("getdocuments dependency failure returns DOCUMENTS_FETCH_FAILED", async () => {
|
|
const mod = loadModule("pages/api/email/getdocuments.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeadersPaged: () => ({}),
|
|
axios: {
|
|
get: async () => {
|
|
throw new Error("docs failed");
|
|
}
|
|
},
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: { incidentid: "123" } };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 500);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "DOCUMENTS_FETCH_FAILED");
|
|
});
|
|
|
|
test("getall top-level failure returns EMAIL_COMBINED_FETCH_FAILED", async () => {
|
|
const mod = loadModule("pages/api/email/getall.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => {
|
|
throw new Error("token failed");
|
|
},
|
|
consoleLogger: () => {},
|
|
formatDates: () => "01/01/2026",
|
|
NotifyClient: function NotifyClient() {
|
|
return { sendEmail: async () => ({ id: "n1" }) };
|
|
},
|
|
require: (name) => {
|
|
if (name === "notifications-node-client") {
|
|
return {
|
|
NotifyClient: function NotifyClient() {
|
|
return { sendEmail: async () => ({ id: "n1" }) };
|
|
}
|
|
};
|
|
}
|
|
return require(name);
|
|
}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 500);
|
|
assert.strictEqual(
|
|
res.state.jsonBody.error.code,
|
|
"EMAIL_COMBINED_FETCH_FAILED"
|
|
);
|
|
});
|
|
|
|
test("getmailinglist success returns flattened mailing list results", async () => {
|
|
const mod = loadModule("pages/api/email/getmailinglist.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: {
|
|
get: async () => ({
|
|
data: {
|
|
value: [
|
|
{
|
|
pinswg_emailnotifications: true,
|
|
pinswg_Contact: {
|
|
contactid: "c1",
|
|
emailaddress1: "a@test.local"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
})
|
|
},
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 200);
|
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), [
|
|
{
|
|
pinswg_emailnotifications: true,
|
|
contactid: "c1",
|
|
contact_email: "a@test.local"
|
|
}
|
|
]);
|
|
});
|
|
|
|
test("getmailinglist failure returns MAILING_LIST_FETCH_FAILED", async () => {
|
|
const mod = loadModule("pages/api/email/getmailinglist.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: {
|
|
get: async () => {
|
|
throw new Error("mailing list failed");
|
|
}
|
|
},
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(
|
|
res.state.jsonBody.error.code,
|
|
"MAILING_LIST_FETCH_FAILED"
|
|
);
|
|
});
|
|
|
|
test("getcaseref success returns flattened case ref results", async () => {
|
|
const mod = loadModule("pages/api/email/getcaseref.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: {
|
|
get: async () => ({
|
|
data: {
|
|
value: [
|
|
{
|
|
pinswg_emailnotifications: true,
|
|
pinswg_Contact: {
|
|
contactid: "c2",
|
|
emailaddress1: "b@test.local"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
})
|
|
},
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 200);
|
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), [
|
|
{
|
|
pinswg_emailnotifications: true,
|
|
contactid: "c2",
|
|
contact_email: "b@test.local"
|
|
}
|
|
]);
|
|
});
|
|
|
|
test("getcaseref failure returns CASE_REF_FETCH_FAILED", async () => {
|
|
const mod = loadModule("pages/api/email/getcaseref.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
getToken: async () => ({ access_token: "token" }),
|
|
hashAPIPath: () => "&hash=expected",
|
|
azureHeaders: () => ({}),
|
|
axios: {
|
|
get: async () => {
|
|
throw new Error("case ref failed");
|
|
}
|
|
},
|
|
consoleLogger: () => {}
|
|
});
|
|
|
|
const req = { query: {} };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "CASE_REF_FETCH_FAILED");
|
|
});
|
|
|
|
test("notify returns EMAIL_ADDRESS_REQUIRED when emailAddress is missing", async () => {
|
|
const mod = loadModule("pages/api/email/notify.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
consoleLogger: () => {},
|
|
redactSensitive: (value) => value,
|
|
sanitizeString: () => "",
|
|
isNonEmptyString: () => false,
|
|
getPreferredLanguage: async () => ({ value: [] })
|
|
});
|
|
|
|
const req = { body: { templateId: "t1", reference: "OTHER" } };
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "EMAIL_ADDRESS_REQUIRED");
|
|
});
|
|
|
|
test("notify send failure returns EMAIL_NOTIFY_FAILED", async () => {
|
|
const mod = loadModule("pages/api/email/notify.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
consoleLogger: () => {},
|
|
redactSensitive: (value) => value,
|
|
sanitizeString: (value) => value,
|
|
isNonEmptyString: () => true,
|
|
getPreferredLanguage: async () => ({ value: [] }),
|
|
require: (name) => {
|
|
if (name === "notifications-node-client") {
|
|
return {
|
|
NotifyClient: function NotifyClient() {
|
|
return {
|
|
sendEmail: async () => {
|
|
throw new Error("notify failed");
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
return require(name);
|
|
}
|
|
});
|
|
|
|
const req = {
|
|
body: {
|
|
templateId: "t1",
|
|
emailAddress: "user@test.local",
|
|
personalisation: {},
|
|
reference: "OTHER"
|
|
}
|
|
};
|
|
const res = createRes();
|
|
await mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "EMAIL_NOTIFY_FAILED");
|
|
});
|
|
|
|
const trustedContactOrigin = "https://portal.example.test";
|
|
const trustedWelshContactOrigin = "https://porth.example.test";
|
|
const contactConfirmationTemplateEn = "69228c5f-8500-40ff-a6c5-95a9434c3ec8";
|
|
const contactConfirmationTemplateCy = "b8f1f76d-4ab9-4a2f-b294-e3623c6095a3";
|
|
|
|
const contactSubjects = {
|
|
plq: {
|
|
en: "Questions about Planning legislation or policy",
|
|
cy: "Ymholiad deddfwriaeth gynllunio"
|
|
},
|
|
ipq: {
|
|
en: "Questions or comments about applications for major infrastructure projects",
|
|
cy: "Ymholiad gweithdrefn seilwaith"
|
|
},
|
|
pcpq: {
|
|
en: "Questions or comments about planning and environmental appeals",
|
|
cy: "Ymholiad gweithdrefn gwaith achos cynllunio"
|
|
},
|
|
pi: { en: "Query about using the Casework Portal", cy: "Materion Porth" },
|
|
appadvice: { en: "Advice How to submit an appeal", cy: "Cyngor cyflwyno apêl" },
|
|
repadvice: {
|
|
en: "Advice How to comment on an appeal or application",
|
|
cy: "Cyngor cyflwyno sylwadau"
|
|
},
|
|
ceq: {
|
|
en: "Queries about forthcoming Hearing or Inquiry event",
|
|
cy: "Ymholiad Digwyddiadau Gwaith Achos"
|
|
},
|
|
other: { en: "Other enquiry for PEDW", cy: "Unrhyw Ymholiadau Eraill" },
|
|
postdec: {
|
|
en: "Issues or questions about a case after the decision is issued",
|
|
cy: "Ar ôl Penderfyniad"
|
|
},
|
|
comp: {
|
|
en: "General Complaints of Compliments",
|
|
cy: "Cwynion/Canmoliaethau"
|
|
}
|
|
};
|
|
|
|
const loadContactLinkUtility = () =>
|
|
loadModule("lib/security/contactus-email-links.js").default;
|
|
|
|
const validContactConfirmationRequest = (overrides = {}) => ({
|
|
method: "POST",
|
|
headers: {
|
|
origin: trustedContactOrigin,
|
|
"content-type": "application/json; charset=utf-8"
|
|
},
|
|
body: {
|
|
contactEmail: "applicant@example.com",
|
|
contactSubject: contactSubjects.ipq.en,
|
|
contactbody: "Please help with my enquiry.",
|
|
typeOfContact: "ipq",
|
|
language: "en"
|
|
},
|
|
...overrides
|
|
});
|
|
|
|
const loadContactConfirmationEndpoint = ({ sendEmail } = {}) => {
|
|
const notifyCalls = [];
|
|
const removeContactUsEmailLinks = loadContactLinkUtility();
|
|
const notifySend =
|
|
sendEmail ||
|
|
(async (...args) => {
|
|
notifyCalls.push(args);
|
|
return { id: "notify-1" };
|
|
});
|
|
|
|
const mod = loadModule("pages/api/email/contactus-confirmation.js", {
|
|
respondError: respondErrorMock,
|
|
respondSuccess: respondSuccessMock,
|
|
sanitizeString: (value) => (typeof value === "string" ? value.trim() : ""),
|
|
isNonEmptyString: (value) =>
|
|
typeof value === "string" && value.trim().length > 0,
|
|
removeContactUsEmailLinks,
|
|
consoleLogger: () => {},
|
|
require: (name) => {
|
|
if (name === "notifications-node-client") {
|
|
return {
|
|
NotifyClient: function NotifyClient() {
|
|
return { sendEmail: notifySend };
|
|
}
|
|
};
|
|
}
|
|
|
|
return require(name);
|
|
}
|
|
});
|
|
|
|
return { mod, notifyCalls };
|
|
};
|
|
|
|
const withContactOrigins = async (fn) => {
|
|
const previousNextAuthUrl = process.env.NEXTAUTH_URL;
|
|
const previousCyApiRoot = process.env.CY_API_ROOT;
|
|
process.env.NEXTAUTH_URL = trustedContactOrigin;
|
|
process.env.CY_API_ROOT = trustedWelshContactOrigin;
|
|
|
|
try {
|
|
await fn();
|
|
} finally {
|
|
if (previousNextAuthUrl === undefined) delete process.env.NEXTAUTH_URL;
|
|
else process.env.NEXTAUTH_URL = previousNextAuthUrl;
|
|
|
|
if (previousCyApiRoot === undefined) delete process.env.CY_API_ROOT;
|
|
else process.env.CY_API_ROOT = previousCyApiRoot;
|
|
}
|
|
};
|
|
|
|
const assertContactConfirmationRejected = async ({ req, status, code }) => {
|
|
await withContactOrigins(async () => {
|
|
const loaded = loadContactConfirmationEndpoint();
|
|
const res = createRes();
|
|
|
|
await loaded.mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, status);
|
|
assert.strictEqual(res.state.jsonBody.error.code, code);
|
|
assert.strictEqual(loaded.notifyCalls.length, 0);
|
|
});
|
|
};
|
|
|
|
test("Contact Us link removal preserves text while removing recognised destinations", async () => {
|
|
const removeLinks = loadContactLinkUtility();
|
|
const input = [
|
|
"Please review https://example.invalid/path?token=abc#details.",
|
|
"Also HTTP://OTHER.INVALID/login and www.third.invalid/help.",
|
|
"Bare planning.example.invalid/application/123 remains represented safely.",
|
|
"Email applicant@example.com and reference APP/123456 must remain.",
|
|
"Markdown [review this](https://markdown.invalid/login).",
|
|
'HTML <a href="https://html.invalid/login">review this</a>.',
|
|
'Tag <img src="https://images.invalid/pixel"> remains non-actionable.',
|
|
"Obfuscated h\u200btt\u200bps://obfuscated.invalid/login is removed."
|
|
].join("\n");
|
|
|
|
const output = removeLinks(input);
|
|
|
|
assert.strictEqual(
|
|
output,
|
|
[
|
|
"Please review [web link removed].",
|
|
"Also [web link removed] and [web link removed].",
|
|
"Bare [web link removed] remains represented safely.",
|
|
"Email applicant@example.com and reference APP/123456 must remain.",
|
|
"Markdown review this [web link removed].",
|
|
"HTML review this [web link removed].",
|
|
"Tag [web link removed] remains non-actionable.",
|
|
"Obfuscated [web link removed] is removed."
|
|
].join("\n")
|
|
);
|
|
assert.strictEqual(input.includes("https://example.invalid"), true);
|
|
});
|
|
|
|
test("Contact Us confirmation is POST-only with Allow header", async () => {
|
|
const req = validContactConfirmationRequest({ method: "GET" });
|
|
await withContactOrigins(async () => {
|
|
const loaded = loadContactConfirmationEndpoint();
|
|
const res = createRes();
|
|
|
|
await loaded.mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 405);
|
|
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
|
|
assert.strictEqual(res.state.headers.allow, "POST");
|
|
assert.strictEqual(loaded.notifyCalls.length, 0);
|
|
});
|
|
});
|
|
|
|
for (const contentType of [undefined, "text/plain", "application/x-www-form-urlencoded"]) {
|
|
test(`Contact Us confirmation rejects content type ${String(contentType)}`, async () => {
|
|
const req = validContactConfirmationRequest({
|
|
headers: {
|
|
origin: trustedContactOrigin,
|
|
...(contentType ? { "content-type": contentType } : {})
|
|
}
|
|
});
|
|
await assertContactConfirmationRejected({
|
|
req,
|
|
status: 415,
|
|
code: "UNSUPPORTED_MEDIA_TYPE"
|
|
});
|
|
});
|
|
}
|
|
|
|
for (const origin of [undefined, "null", "https://attacker.example", `${trustedContactOrigin}/path`]) {
|
|
test(`Contact Us confirmation rejects origin ${String(origin)}`, async () => {
|
|
const req = validContactConfirmationRequest({
|
|
headers: {
|
|
...(origin ? { origin } : {}),
|
|
"content-type": "application/json"
|
|
}
|
|
});
|
|
await assertContactConfirmationRejected({ req, status: 403, code: "FORBIDDEN" });
|
|
});
|
|
}
|
|
|
|
test("Contact Us confirmation accepts each configured exact origin", async () => {
|
|
await withContactOrigins(async () => {
|
|
for (const origin of [trustedContactOrigin, trustedWelshContactOrigin]) {
|
|
const loaded = loadContactConfirmationEndpoint();
|
|
const req = validContactConfirmationRequest({
|
|
headers: { origin, "content-type": "application/json" }
|
|
});
|
|
const res = createRes();
|
|
|
|
await loaded.mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 200);
|
|
assert.strictEqual(loaded.notifyCalls.length, 1);
|
|
}
|
|
});
|
|
});
|
|
|
|
for (const body of [null, [], "not-an-object", new Date()]) {
|
|
test(`Contact Us confirmation rejects non-plain body ${Object.prototype.toString.call(body)}`, async () => {
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({ body }),
|
|
status: 400,
|
|
code: "INVALID_REQUEST_BODY"
|
|
});
|
|
});
|
|
}
|
|
|
|
for (const bodyOverride of [
|
|
{ extra: "field" },
|
|
{ language: undefined },
|
|
{ contactSubject: undefined }
|
|
]) {
|
|
test(`Contact Us confirmation enforces exact fields ${Object.keys(bodyOverride).join(",")}`, async () => {
|
|
const body = { ...validContactConfirmationRequest().body, ...bodyOverride };
|
|
if (Object.values(bodyOverride).includes(undefined)) {
|
|
for (const key of Object.keys(bodyOverride)) delete body[key];
|
|
}
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({ body }),
|
|
status: 400,
|
|
code: "INVALID_REQUEST_FIELDS"
|
|
});
|
|
});
|
|
}
|
|
|
|
for (const contactEmail of [undefined, "invalid", `${"a".repeat(190)}@example.com`]) {
|
|
test(`Contact Us confirmation rejects invalid email ${String(contactEmail).slice(0, 30)}`, async () => {
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({
|
|
body: { ...validContactConfirmationRequest().body, contactEmail }
|
|
}),
|
|
status: 400,
|
|
code: "INVALID_CONTACT_EMAIL"
|
|
});
|
|
});
|
|
}
|
|
|
|
for (const typeOfContact of [undefined, "unsupported"]) {
|
|
test(`Contact Us confirmation rejects category ${String(typeOfContact)}`, async () => {
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({
|
|
body: { ...validContactConfirmationRequest().body, typeOfContact }
|
|
}),
|
|
status: 400,
|
|
code: "INVALID_CONTACT_TYPE"
|
|
});
|
|
});
|
|
}
|
|
|
|
for (const contactbody of [undefined, " ", "a".repeat(801)]) {
|
|
test(`Contact Us confirmation rejects message ${String(contactbody).slice(0, 20)}`, async () => {
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({
|
|
body: { ...validContactConfirmationRequest().body, contactbody }
|
|
}),
|
|
status: 400,
|
|
code: "INVALID_CONTACT_BODY"
|
|
});
|
|
});
|
|
}
|
|
|
|
test("Contact Us confirmation rejects a subject not matching its category", async () => {
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({
|
|
body: {
|
|
...validContactConfirmationRequest().body,
|
|
contactSubject: contactSubjects.plq.en
|
|
}
|
|
}),
|
|
status: 400,
|
|
code: "INVALID_CONTACT_SUBJECT"
|
|
});
|
|
});
|
|
|
|
for (const language of [undefined, "CY", "fr", ""]) {
|
|
test(`Contact Us confirmation rejects language ${String(language)}`, async () => {
|
|
await assertContactConfirmationRejected({
|
|
req: validContactConfirmationRequest({
|
|
body: { ...validContactConfirmationRequest().body, language }
|
|
}),
|
|
status: 400,
|
|
code: "INVALID_LANGUAGE"
|
|
});
|
|
});
|
|
}
|
|
|
|
test("Contact Us confirmation selects English template and exact safe personalisation", async () => {
|
|
await withContactOrigins(async () => {
|
|
const loaded = loadContactConfirmationEndpoint();
|
|
const req = validContactConfirmationRequest({
|
|
body: {
|
|
...validContactConfirmationRequest().body,
|
|
contactbody:
|
|
"Please review (https://example.invalid/path?q=1#part), then email applicant@example.com about APP/123456."
|
|
}
|
|
});
|
|
const originalBody = JSON.parse(JSON.stringify(req.body));
|
|
const res = createRes();
|
|
|
|
await loaded.mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 200);
|
|
assert.strictEqual(loaded.notifyCalls.length, 1);
|
|
assert.strictEqual(loaded.notifyCalls[0][0], contactConfirmationTemplateEn);
|
|
assert.strictEqual(loaded.notifyCalls[0][1], "applicant@example.com");
|
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(loaded.notifyCalls[0][2])), {
|
|
personalisation: {
|
|
contactEmail: "applicant@example.com",
|
|
contactSubject: contactSubjects.ipq.en,
|
|
contactbody:
|
|
"Please review ([web link removed]), then email applicant@example.com about APP/123456.",
|
|
typeOfContact: "ipq"
|
|
},
|
|
reference: "PEDW-CONTACT-US"
|
|
});
|
|
assert.deepStrictEqual(req.body, originalBody);
|
|
});
|
|
});
|
|
|
|
test("Contact Us confirmation selects Welsh template and supports an 800-character message", async () => {
|
|
await withContactOrigins(async () => {
|
|
const loaded = loadContactConfirmationEndpoint();
|
|
const message = "a".repeat(800);
|
|
const req = validContactConfirmationRequest({
|
|
body: {
|
|
contactEmail: "applicant@example.com",
|
|
contactSubject: contactSubjects.plq.cy,
|
|
contactbody: message,
|
|
typeOfContact: "plq",
|
|
language: "cy"
|
|
}
|
|
});
|
|
const res = createRes();
|
|
|
|
await loaded.mod.default(req, res);
|
|
|
|
assert.strictEqual(res.state.statusCode, 200);
|
|
assert.strictEqual(loaded.notifyCalls[0][0], contactConfirmationTemplateCy);
|
|
assert.strictEqual(loaded.notifyCalls[0][2].personalisation.contactbody, message);
|
|
});
|
|
});
|
|
|
|
test("Contact Us confirmation returns a bounded error when Notify fails", async () => {
|
|
await withContactOrigins(async () => {
|
|
let sendCalls = 0;
|
|
const loaded = loadContactConfirmationEndpoint({
|
|
sendEmail: async () => {
|
|
sendCalls += 1;
|
|
throw new Error("notify failed");
|
|
}
|
|
});
|
|
const res = createRes();
|
|
|
|
await loaded.mod.default(validContactConfirmationRequest(), res);
|
|
|
|
assert.strictEqual(sendCalls, 1);
|
|
assert.strictEqual(res.state.statusCode, 400);
|
|
assert.strictEqual(
|
|
res.state.jsonBody.error.code,
|
|
"CONTACT_US_CONFIRMATION_FAILED"
|
|
);
|
|
});
|
|
});
|
|
|
|
const run = async () => {
|
|
let passed = 0;
|
|
for (const currentTest of tests) {
|
|
await currentTest.fn();
|
|
passed += 1;
|
|
}
|
|
console.log(
|
|
`Phase 21 email-handler contract tests passed (${passed}/${tests.length}).`
|
|
);
|
|
};
|
|
|
|
module.exports = run;
|
|
|
|
if (require.main === module) {
|
|
run().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
}
|