Skip to content

Commit

Permalink
Merge branch 'main' into less-verbiage
Browse files Browse the repository at this point in the history
  • Loading branch information
benmartin-coforma committed Jan 28, 2025
2 parents 4953b8e + ff68b2e commit e8af219
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 15 deletions.
4 changes: 2 additions & 2 deletions run
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ if ! which yarn > /dev/null ; then
fi

# check serverless is installed globally.
if ! which serverless > /dev/null ; then
echo "installing serverless globally"
if ! which serverless > /dev/null || [[ "$(serverless --version | cut -d'.' -f1)" != "4" ]]; then
echo "installing serverless v4 globally"
yarn global add [email protected]
fi

Expand Down
92 changes: 92 additions & 0 deletions services/app-api/handlers/reports/submit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { StatusCodes } from "../../libs/response-lib";
import { proxyEvent } from "../../testing/proxyEvent";
import { APIGatewayProxyEvent, UserRoles } from "../../types/types";
import { canWriteState } from "../../utils/authorization";
import { submitReport } from "./submit";

jest.mock("../../utils/authentication", () => ({
authenticatedUser: jest.fn().mockResolvedValue({
role: UserRoles.STATE_USER,
state: "PA",
}),
}));

jest.mock("../../utils/authorization", () => ({
canWriteState: jest.fn().mockReturnValue(true),
}));

jest.mock("../../storage/reports", () => ({
putReport: () => jest.fn(),
}));

const reportObj = { type: "QMS", state: "PA", id: "QMSPA123" };
const report = JSON.stringify(reportObj);

const testEvent: APIGatewayProxyEvent = {
...proxyEvent,
pathParameters: { reportType: "QMS", state: "PA", id: "QMSPA123" },
headers: { "cognito-identity-id": "test" },
body: report,
};

describe("Test submit report handler", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("Test missing path params", async () => {
const badTestEvent = {
...proxyEvent,
pathParameters: {},
} as APIGatewayProxyEvent;
const res = await submitReport(badTestEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

it("should return 403 if user is not authorized", async () => {
(canWriteState as jest.Mock).mockReturnValueOnce(false);
const response = await submitReport(testEvent);
expect(response.statusCode).toBe(StatusCodes.Forbidden);
});

test("Test missing body", async () => {
const emptyBodyEvent = {
...proxyEvent,
pathParameters: { reportType: "QMS", state: "PA", id: "QMSPA123" },
body: null,
} as APIGatewayProxyEvent;
const res = await submitReport(emptyBodyEvent);
expect(res.statusCode).toBe(StatusCodes.BadRequest);
});

test("Test body + param mismatch", async () => {
const badType = {
...proxyEvent,
pathParameters: { reportType: "ZZ", state: "PA", id: "QMSPA123" },
body: report,
} as APIGatewayProxyEvent;
const badState = {
...proxyEvent,
pathParameters: { reportType: "QMS", state: "PA", id: "QMSPA123" },
body: JSON.stringify({ ...reportObj, state: "OR" }),
} as APIGatewayProxyEvent;
const badId = {
...proxyEvent,
pathParameters: { reportType: "QMS", state: "PA", id: "ZZOR1234" },
body: report,
} as APIGatewayProxyEvent;

const resType = await submitReport(badType);
expect(resType.statusCode).toBe(StatusCodes.BadRequest);
const resState = await submitReport(badState);
expect(resState.statusCode).toBe(StatusCodes.BadRequest);
const resId = await submitReport(badId);
expect(resId.statusCode).toBe(StatusCodes.BadRequest);
});

test("Test Successful submit", async () => {
const res = await submitReport(testEvent);

expect(res.statusCode).toBe(StatusCodes.Ok);
});
});
42 changes: 42 additions & 0 deletions services/app-api/handlers/reports/submit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { handler } from "../../libs/handler-lib";
import { parseReportParameters } from "../../libs/param-lib";
import { badRequest, forbidden, ok } from "../../libs/response-lib";
import { putReport } from "../../storage/reports";
import { Report, ReportStatus } from "../../types/reports";
import { canWriteState } from "../../utils/authorization";
import { error } from "../../utils/constants";

export const submitReport = handler(parseReportParameters, async (request) => {
const { reportType, state, id } = request.parameters;
const user = request.user;

if (!canWriteState(user, state)) {
return forbidden(error.UNAUTHORIZED);
}

if (!request?.body) {
return badRequest("Invalid request");
}

// get the report that's being submitted
const report = request.body as Report;
if (
reportType !== report.type ||
state !== report.state ||
id !== report.id
) {
return badRequest("Invalid request");
}

// collect the user info of the submitter
report.status = ReportStatus.SUBMITTED;
report.lastEdited = Date.now();
report.lastEditedBy = user.fullName;

// leave space for validating that the report can be submitted

// save the report that's being submitted (with the new information on top of it)
await putReport(report);

return ok();
});
21 changes: 19 additions & 2 deletions services/app-api/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ custom:
serverlessPluginTypescript:
tsConfigFileLocation: "./tsconfig.json"
stage: ${sls:stage}
region: ${aws:region}
region: ${self:provider.region}
wafPlugin:
name: ${self:service}-${self:custom.stage}-webacl-waf
name: ${self:custom.webAclName}
wafExcludeRules:
awsCommon:
- "SizeRestrictions_BODY"
Expand Down Expand Up @@ -54,6 +54,9 @@ provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stackTags:
PROJECT: ${self:custom.project}
SERVICE: ${self:service}
tracing:
apiGateway: true
logs:
Expand Down Expand Up @@ -150,6 +153,20 @@ functions:
reportType: true
state: true
id: true
submitReport:
handler: handlers/reports/submit.submitReport
events:
- http:
path: reports/submit/{reportType}/{state}/{id}
method: post
cors: true
authorizer: aws_iam
request:
parameters:
paths:
reportType: true
state: true
id: true
getReport:
handler: handlers/reports/get.getReport
events:
Expand Down
10 changes: 4 additions & 6 deletions services/ui-auth/handlers/createUsers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as cognitolib from "../libs/cognito-lib";
import * as cognitolib from "../libs/cognito-lib.js";
const userPoolId = process.env.userPoolId;
const users = require("../libs/users.json");
import users from "../libs/users.json" assert { type: "json" };

async function myHandler(_event, _context, _callback) {
export const handler = async (_event, _context, _callback) => {
for (var i = 0; i < users.length; i++) {
var poolData = {
UserPoolId: userPoolId,
Expand Down Expand Up @@ -43,6 +43,4 @@ async function myHandler(_event, _context, _callback) {
/* swallow this exception and continue */
}
}
}

exports.handler = myHandler;
};
1 change: 1 addition & 0 deletions services/ui-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "ui-auth",
"description": "",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
Expand Down
8 changes: 8 additions & 0 deletions services/ui-src/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,11 @@ custom:
export LOCAL_LOGIN=false
./scripts/configure-env.sh
cp public/env-config.js build/env-config.js

resources:
Resources:
Honk: # this is just because it won't deploy unless we have some resource being created
Type: AWS::SSM::Parameter
Properties:
Type: String
Value: 'honk'
25 changes: 22 additions & 3 deletions services/ui-src/src/components/report/StatusTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { StatusTableElement } from "./StatusTable";
import { MemoryRouter } from "react-router-dom";
import { useStore } from "utils";
import { mockUseReadOnlyUserStore } from "utils/testing/setupJest";
import { useStore, submitReport } from "utils";
import {
mockUseReadOnlyUserStore,
mockStateUserStore,
} from "utils/testing/setupJest";

jest.mock("utils", () => ({
useStore: jest.fn(),
submitReport: jest.fn(),
}));

const mockNavigate = jest.fn();
Expand Down Expand Up @@ -38,6 +42,7 @@ describe("StatusTable with state user", () => {
jest.clearAllMocks();

mockedUseStore.mockReturnValue({
...mockStateUserStore,
pageMap: mockPageMap,
report: report,
});
Expand Down Expand Up @@ -72,7 +77,6 @@ describe("StatusTable with state user", () => {

expect(editButton).toBeVisible();
});

test("when the Review PDF button is clicked, navigate to PDF", async () => {
render(
<MemoryRouter>
Expand All @@ -87,6 +91,21 @@ describe("StatusTable with state user", () => {
expect(reviewPdfButton).toHaveAttribute("target", "_blank");
});

test("when the Submit button is clicked, call the API", async () => {
render(
<MemoryRouter>
<StatusTableElement />
</MemoryRouter>
);

const submitButton = screen.getAllByRole("button", {
name: /Submit QMS Report/i,
})[0];
await userEvent.click(submitButton);

expect(submitReport).toBeCalled();
});

test("if pageMap is not defined return null", () => {
(useStore as unknown as jest.Mock).mockReturnValue({
pageMap: null,
Expand Down
7 changes: 5 additions & 2 deletions services/ui-src/src/components/report/StatusTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Tr,
Text,
} from "@chakra-ui/react";
import { useStore } from "utils";
import { useStore, submitReport } from "utils";
import editIconPrimary from "assets/icons/edit/icon_edit_primary.svg";
import lookupIconPrimary from "assets/icons/search/icon_search_primary.svg";
import { ParentPageTemplate } from "types/report";
Expand Down Expand Up @@ -97,8 +97,11 @@ export const StatusTableElement = () => {
</Button>
{user?.userIsEndUser && (
<Button
// onClick={() => SetPageIndex(parentPage.index - 1)}
alignSelf="flex-end"
onClick={async () => submitReport(report!)}
onBlur={(event) => {
event.stopPropagation();
}}
>
Submit QMS Report
</Button>
Expand Down
6 changes: 6 additions & 0 deletions services/ui-src/src/utils/api/requestMethods/report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getReport,
getReportsForState,
putReport,
submitReport,
} from "./report";
// types
import { Report, ReportOptions, ReportType } from "types/report";
Expand Down Expand Up @@ -53,4 +54,9 @@ describe("utils/report", () => {
await putReport(report);
expect(mockPut).toHaveBeenCalledTimes(1);
});

test("submitReport", async () => {
await submitReport(report);
expect(mockPost).toHaveBeenCalledTimes(1);
});
});
12 changes: 12 additions & 0 deletions services/ui-src/src/utils/api/requestMethods/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ export async function putReport(report: Report) {
);
}

export async function submitReport(report: Report) {
const requestHeaders = await getRequestHeaders();
const options = {
headers: { ...requestHeaders },
body: { ...report },
};
return await apiLib.post(
`/reports/submit/${report.type}/${report.state}/${report.id}`,
options
);
}

export async function getReportsForState(reportType: string, state: string) {
const requestHeaders = await getRequestHeaders();
const options = {
Expand Down
3 changes: 3 additions & 0 deletions services/ui/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ resources:
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
LoggingConfiguration:
DestinationBucketName: ${env:LOGGING_BUCKET, ssm:/configuration/${self:custom.stage}/s3/accessLogsBucket, ssm:/configuration/default/s3/accessLogsBucket}
LogFilePrefix: ${env:LOGGING_BUCKET, ssm:/configuration/${self:custom.stage}/s3/accessLogsPrefix, ssm:/configuration/default/s3/accessLogsPrefix}
DeletionPolicy: Delete
BucketPolicy:
Type: AWS::S3::BucketPolicy
Expand Down

0 comments on commit e8af219

Please sign in to comment.