Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add new /template API path #45

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/lib/forms/dataStructures/formTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface FormTemplate {
jsonConfig: Record<string, unknown>;
}

export function formTemplateFromPostgreSqlResult(
response: Record<string, unknown>,
): FormTemplate {
return {
jsonConfig: response.jsonConfig as Record<string, unknown>,
};
}
32 changes: 32 additions & 0 deletions src/lib/forms/getFormTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DatabaseConnectorClient } from "@lib/connectors/databaseConnector.js";
import { logMessage } from "@src/lib/logger.js";
import {
type FormTemplate,
formTemplateFromPostgreSqlResult,
} from "./dataStructures/formTemplate.js";

export async function getFormTemplate(
formId: string,
): Promise<FormTemplate | undefined> {
return DatabaseConnectorClient.oneOrNone<Record<string, unknown>>(
'SELECT "jsonConfig" FROM "Template" WHERE id = $1',
[formId],
)
.then((result) => {
if (result === null) {
return undefined;
}

return formTemplateFromPostgreSqlResult(result);
})
.catch((error) => {
logMessage.error(
`[database] Failed to retrieve form template. FormId: ${formId}. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
)}`,
);

throw error;
});
craigzour marked this conversation as resolved.
Show resolved Hide resolved
}
3 changes: 2 additions & 1 deletion src/lib/support/notifySupportAboutFormSubmissionProblem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFreshdeskTicket } from "@lib/support/freshdeskApiClient.js";
import { EnvironmentMode } from "@src/config.js";
import { logMessage } from "@lib/logger.js";

export async function notifySupportAboutFormSubmissionProblem(
formId: string,
Expand All @@ -25,7 +26,7 @@ export async function notifySupportAboutFormSubmissionProblem(
preferredLanguage: preferredLanguage,
});
} catch (error) {
console.error(
logMessage.error(
`[support] Failed to notify support about form submission problem. FormId: ${formId} / SubmissionName: ${submissionName} / Contact email: ${contactEmail}. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
Expand Down
3 changes: 2 additions & 1 deletion src/lib/vault/getNewFormSubmissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
newFormSubmissionFromDynamoDbResponse,
type NewFormSubmission,
} from "@lib/vault/dataStructures/formSubmission.js";
import { logMessage } from "@lib/logger.js";

export async function getNewFormSubmissions(
formId: string,
Expand Down Expand Up @@ -48,7 +49,7 @@ export async function getNewFormSubmissions(

return newFormSubmissions;
} catch (error) {
console.error(
logMessage.error(
`[dynamodb] Failed to retrieve new form submissions. FormId: ${formId}. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
Expand Down
3 changes: 2 additions & 1 deletion src/lib/vault/reportProblemWithFormSubmission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "@lib/vault/dataStructures/exceptions.js";
import { getFormSubmission } from "@lib/vault/getFormSubmission.js";
import { FormSubmissionStatus } from "@lib/vault/dataStructures/formSubmission.js";
import { logMessage } from "@lib/logger.js";

export async function reportProblemWithFormSubmission(
formId: string,
Expand Down Expand Up @@ -43,7 +44,7 @@ export async function reportProblemWithFormSubmission(
}),
);
} catch (error) {
console.error(
logMessage.error(
`[dynamodb] Failed to report problem with form submission. FormId: ${formId} / SubmissionName: ${submissionName}. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
Expand Down
10 changes: 10 additions & 0 deletions src/routes/forms/formId/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Router } from "express";
import { templateApiRoute } from "@routes/forms/formId/template/router.js";
import { submissionApiRoute } from "@routes/forms/formId/submission/router.js";

export const formIdApiRoute = Router({
mergeParams: true,
});

formIdApiRoute.use("/template", templateApiRoute);
formIdApiRoute.use("/submission", submissionApiRoute);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logMessage } from "@src/lib/logger.js";
import { getNewFormSubmissions } from "@src/lib/vault/getNewFormSubmissions.js";
import { type Request, type Response, Router } from "express";

Expand All @@ -18,7 +19,7 @@ newApiRoute.get("/", async (request: Request, response: Response) => {

return response.json(newFormSubmissions);
} catch (error) {
console.error(
logMessage.error(
`[route] Internal error while serving request: /forms/${formId}/submission/new. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Router } from "express";
import { newApiRoute } from "@routes/forms/submission/new/router.js";
import { submissionNameApiRoute } from "@src/routes/forms/submission/submissionName/router.js";
import { newApiRoute } from "@routes/forms/formId/submission/new/router.js";
import { submissionNameApiRoute } from "@src/routes/forms/formId/submission/submissionName/router.js";

export const submissionApiRoute = Router({
mergeParams: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { notifySupportAboutFormSubmissionProblem } from "@src/lib/support/notify
import type { Schema } from "express-validator";
import { requestValidatorMiddleware } from "@src/middleware/requestValidator/middleware.js";
import { ENVIRONMENT_MODE, EnvironmentMode } from "@src/config.js";
import { logMessage } from "@src/lib/logger.js";

export const problemApiRoute = Router({
mergeParams: true,
Expand Down Expand Up @@ -58,7 +59,7 @@ problemApiRoute.post(
ENVIRONMENT_MODE,
);
} else {
console.debug(
logMessage.debug(
"[local] Will not notify support about submission problem.",
);
}
Expand All @@ -79,7 +80,7 @@ problemApiRoute.post(
.json({ info: "Form submission is already reported as problematic" });
}

console.error(
logMessage.error(
`[route] Internal error while serving request: /forms/${formId}/submission/${submissionName}/problem. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { type Request, type Response, Router } from "express";
import { getFormSubmission } from "@lib/vault/getFormSubmission.js";
import { encryptFormSubmission } from "@src/lib/vault/encryptFormSubmission.js";
import { logMessage } from "@src/lib/logger.js";
import { confirmApiRoute } from "@routes/forms/submission/submissionName/confirm/router.js";
import { problemApiRoute } from "@routes/forms/submission/submissionName/problem/router.js";
import { confirmApiRoute } from "@routes/forms/formId/submission/submissionName/confirm/router.js";
import { problemApiRoute } from "@routes/forms/formId/submission/submissionName/problem/router.js";

export const submissionNameApiRoute = Router({
mergeParams: true,
Expand Down
32 changes: 32 additions & 0 deletions src/routes/forms/formId/template/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getFormTemplate } from "@src/lib/forms/getFormTemplate.js";
import { logMessage } from "@src/lib/logger.js";
import { type Request, type Response, Router } from "express";

export const templateApiRoute = Router({
mergeParams: true,
});

templateApiRoute.get("/", async (request: Request, response: Response) => {
const formId = request.params.formId;

try {
const formTemplate = await getFormTemplate(formId);

if (formTemplate === undefined) {
return response
.status(404)
.json({ error: "Form template does not exist" });
}

return response.json(formTemplate.jsonConfig);
} catch (error) {
logMessage.error(
`[route] Internal error while serving request: /forms/${formId}/template. Reason: ${JSON.stringify(
error,
Object.getOwnPropertyNames(error),
)}`,
);

return response.sendStatus(500);
}
});
8 changes: 2 additions & 6 deletions src/routes/forms/router.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { Router } from "express";
import { submissionApiRoute } from "@routes/forms/submission/router.js";
import { authenticationMiddleware } from "@middleware/authentication/middleware.js";
import { rateLimiterMiddleware } from "@middleware/rateLimiter/middleware.js";
import { formIdApiRoute } from "@routes/forms/formId/router.js";

export const formsApiRoute = Router();

formsApiRoute
.use(rateLimiterMiddleware)
.use(
"/:formId(c[a-z0-9]{24})/submission",
authenticationMiddleware,
submissionApiRoute,
);
.use("/:formId(c[a-z0-9]{24})", authenticationMiddleware, formIdApiRoute);
61 changes: 61 additions & 0 deletions test/lib/forms/getFormTemplate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { vi, describe, it, expect, beforeEach } from "vitest";
import { getFormTemplate } from "@lib/forms/getFormTemplate.js";
import { DatabaseConnectorClient } from "@src/lib/connectors/databaseConnector.js";
import { logMessage } from "@src/lib/logger.js";

describe("getFormTemplate should", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("return an undefined form template if database was not able to find it", async () => {
vi.spyOn(DatabaseConnectorClient, "oneOrNone").mockResolvedValueOnce(null);

const formTemplate = await getFormTemplate("clzamy5qv0000115huc4bh90m");

expect(formTemplate).toBeUndefined();
});

it("return a form template if database was able to find it", async () => {
vi.spyOn(DatabaseConnectorClient, "oneOrNone").mockResolvedValueOnce({
jsonConfig: {
elements: [
{
id: 1,
type: "textField",
},
],
},
});

const formTemplate = await getFormTemplate("clzamy5qv0000115huc4bh90m");

expect(formTemplate).toEqual({
jsonConfig: {
elements: [
{
id: 1,
type: "textField",
},
],
},
});
});

it("throw an error if database has an internal failure", async () => {
vi.spyOn(DatabaseConnectorClient, "oneOrNone").mockRejectedValueOnce(
new Error("custom error"),
);
const logMessageSpy = vi.spyOn(logMessage, "error");

await expect(() =>
getFormTemplate("clzamy5qv0000115huc4bh90m"),
).rejects.toThrowError("custom error");

expect(logMessageSpy).toHaveBeenCalledWith(
expect.stringContaining(
"[database] Failed to retrieve form template. FormId: clzamy5qv0000115huc4bh90m. Reason:",
),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest";
import { notifySupportAboutFormSubmissionProblem } from "@lib/support/notifySupportAboutFormSubmissionProblem.js";
import { createFreshdeskTicket } from "@src/lib/support/freshdeskApiClient.js";
import { EnvironmentMode } from "@src/config.js";
import { logMessage } from "@src/lib/logger.js";

vi.mock("@src/lib/support/freshdeskApiClient");
const createFreshdeskTicketMock = vi.mocked(createFreshdeskTicket);
Expand Down Expand Up @@ -54,7 +55,7 @@ Here is my problem<br/>

it("throw an error if the createTicket function has an internal failure", async () => {
createFreshdeskTicketMock.mockRejectedValueOnce(new Error("custom error"));
const consoleErrorLogSpy = vi.spyOn(console, "error");
const logMessageSpy = vi.spyOn(logMessage, "error");

await expect(() =>
notifySupportAboutFormSubmissionProblem(
Expand All @@ -67,7 +68,7 @@ Here is my problem<br/>
),
).rejects.toThrowError("custom error");

expect(consoleErrorLogSpy).toHaveBeenCalledWith(
expect(logMessageSpy).toHaveBeenCalledWith(
expect.stringContaining(
"[support] Failed to notify support about form submission problem. FormId: clzamy5qv0000115huc4bh90m / SubmissionName: 01-08-a571 / Contact email: test@test.com. Reason:",
),
Expand Down
5 changes: 3 additions & 2 deletions test/lib/vault/getNewFormSubmissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { vi, describe, it, expect, beforeEach } from "vitest";
import { mockClient } from "aws-sdk-client-mock";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";
import { getNewFormSubmissions } from "@lib/vault/getNewFormSubmissions.js";
import { logMessage } from "@src/lib/logger.js";

const dynamoDbMock = mockClient(DynamoDBDocumentClient);

Expand Down Expand Up @@ -145,13 +146,13 @@ describe("getNewFormSubmissions should", () => {

it("throw an error if DynamoDB has an internal failure", async () => {
dynamoDbMock.on(QueryCommand).rejectsOnce("custom error");
const consoleErrorLogSpy = vi.spyOn(console, "error");
const logMessageSpy = vi.spyOn(logMessage, "error");

await expect(
getNewFormSubmissions("clzamy5qv0000115huc4bh90m", 100),
).rejects.toThrowError("custom error");

expect(consoleErrorLogSpy).toHaveBeenCalledWith(
expect(logMessageSpy).toHaveBeenCalledWith(
expect.stringContaining(
"[dynamodb] Failed to retrieve new form submissions. FormId: clzamy5qv0000115huc4bh90m. Reason:",
),
Expand Down
5 changes: 3 additions & 2 deletions test/lib/vault/reportProblemWithFormSubmission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
FormSubmissionNotFoundException,
} from "@src/lib/vault/dataStructures/exceptions.js";
import { buildMockedVaultItem } from "test/mocks/dynamodb.js";
import { logMessage } from "@src/lib/logger.js";

const dynamoDbMock = mockClient(DynamoDBDocumentClient);

Expand Down Expand Up @@ -88,7 +89,7 @@ describe("reportProblemWithFormSubmission should", () => {

it("throw an error if DynamoDB has an internal failure", async () => {
dynamoDbMock.on(GetCommand).rejectsOnce("custom error");
const consoleErrorLogSpy = vi.spyOn(console, "error");
const logMessageSpy = vi.spyOn(logMessage, "error");

await expect(() =>
reportProblemWithFormSubmission(
Expand All @@ -97,7 +98,7 @@ describe("reportProblemWithFormSubmission should", () => {
),
).rejects.toThrowError("custom error");

expect(consoleErrorLogSpy).toHaveBeenCalledWith(
expect(logMessageSpy).toHaveBeenCalledWith(
expect.stringContaining(
"[dynamodb] Failed to report problem with form submission. FormId: clzamy5qv0000115huc4bh90m / SubmissionName: 01-08-a571. Reason:",
),
Expand Down
39 changes: 39 additions & 0 deletions test/routes/forms/formId/router.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { vi, describe, it, expect, beforeAll } from "vitest";
import request from "supertest";
import express, { type Express, type Response, Router } from "express";
import { formIdApiRoute } from "@routes/forms/formId/router.js";

vi.mock("@routes/forms/formId/template/router", () => ({
templateApiRoute: Router().get("/", (_, response: Response) => {
return response.sendStatus(200);
}),
}));

vi.mock("@routes/forms/formId/submission/router", () => ({
submissionApiRoute: Router().get("/", (_, response: Response) => {
return response.sendStatus(200);
}),
}));

describe("/forms/:formId", () => {
let server: Express;

beforeAll(() => {
server = express();
server.use("/", formIdApiRoute);
});

describe("/template", () => {
it("Response to GET operation", async () => {
const response = await request(server).get("/template");
expect(response.status).toBe(200);
});
});

describe("/submission", () => {
it("Response to GET operation", async () => {
const response = await request(server).get("/submission");
expect(response.status).toBe(200);
});
});
});
Loading
Loading