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

main → val #756

Merged
merged 14 commits into from
Sep 20, 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
53 changes: 3 additions & 50 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- "!skipci*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
group: ${{ github.ref_name }}

permissions:
id-token: write
Expand Down Expand Up @@ -108,38 +108,8 @@ jobs:
echo "application_endpoint=$APPLICATION_ENDPOINT" >> $GITHUB_OUTPUT
echo "## Application Endpoint" >> $GITHUB_STEP_SUMMARY
echo "<$APPLICATION_ENDPOINT>" >> $GITHUB_STEP_SUMMARY
- id: api_name
run: |
API_NAME=$(./scripts/output.sh app-api ApiGatewayRestApiName $STAGE_PREFIX$branch_name)
echo "api_name=$API_NAME" >> $GITHUB_OUTPUT
- id: branch_name
run: |
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
- id: cognito_user_pool_client_id
run: |
COGNITO_USER_POOL_CLIENT_ID=$(./scripts/output.sh ui-auth UserPoolClientId $STAGE_PREFIX$branch_name)
echo "cognito_user_pool_client_id=$COGNITO_USER_POOL_CLIENT_ID" >> $GITHUB_OUTPUT
- id: cognito_user_pool_id_encrypted
run: |
COGNITO_USER_POOL_ID=$(./scripts/output.sh ui-auth UserPoolId $STAGE_PREFIX$branch_name)
COGNITO_USER_POOL_ID_ENCRYPTED=$(echo -n "$COGNITO_USER_POOL_ID" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "cognito_user_pool_id_encrypted<<EOF" >> $GITHUB_OUTPUT
echo $COGNITO_USER_POOL_ID_ENCRYPTED >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- id: region_encrypted
run: |
REGION=$(./scripts/output.sh app-api Region $STAGE_PREFIX$branch_name)
REGION_ENCRYPTED=$(echo -n "$REGION" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "region_encrypted<<EOF" >> $GITHUB_OUTPUT
echo $REGION_ENCRYPTED >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
outputs:
api_name: ${{ steps.api_name.outputs.api_name }}
application_endpoint: ${{ steps.endpoint.outputs.application_endpoint }}
branch_name: ${{ steps.branch_name.outputs.branch_name }}
cognito_user_pool_client_id: ${{ steps.cognito_user_pool_client_id.outputs.cognito_user_pool_client_id }}
cognito_user_pool_id_encrypted: ${{ steps.cognito_user_pool_id_encrypted.outputs.cognito_user_pool_id_encrypted }}
region_encrypted: ${{ steps.region_encrypted.outputs.region_encrypted }}
BRANCH_SPECIFIC_VARNAME_AWS_DEFAULT_REGION: ${{ steps.set_names.outputs.BRANCH_SPECIFIC_VARNAME_AWS_DEFAULT_REGION }}
BRANCH_SPECIFIC_VARNAME_AWS_OIDC_ROLE_TO_ASSUME: ${{ steps.set_names.outputs.BRANCH_SPECIFIC_VARNAME_AWS_OIDC_ROLE_TO_ASSUME }}

Expand Down Expand Up @@ -307,6 +277,8 @@ jobs:
needs:
- deploy
- register-runner
- e2e-test
- a11y-tests
if: ${{ always() && !cancelled() && needs.deploy.result == 'success' && github.ref_name != 'production' }}
timeout-minutes: 60
runs-on: ubuntu-latest
Expand All @@ -320,22 +292,6 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: Decrypt cognito_user_pool_id
run: |
COGNITO_USER_POOL_ID_ENCRYPTED=${{ needs.deploy.outputs.cognito_user_pool_id_encrypted }}
COGNITO_USER_POOL_ID_DECRYPTED=$(echo "$COGNITO_USER_POOL_ID_ENCRYPTED" | openssl base64 -d)
COGNITO_USER_POOL_ID=$(echo -n "$COGNITO_USER_POOL_ID_DECRYPTED" | openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "cognito_user_pool_id<<EOF" >> $GITHUB_ENV
echo $COGNITO_USER_POOL_ID >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Decrypt region
run: |
REGION_ENCRYPTED=${{ needs.deploy.outputs.region_encrypted }}
REGION_DECRYPTED=$(echo "$REGION_ENCRYPTED" | openssl base64 -d)
REGION=$(echo -n "$REGION_DECRYPTED" | openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -k "${{ secrets.CODE_CLIMATE_ID }}")
echo "region<<EOF" >> $GITHUB_ENV
echo $REGION >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: yarn install
run: yarn install
- name: Install Playwright Browsers
Expand All @@ -344,10 +300,7 @@ jobs:
run: yarn playwright test
continue-on-error: true
env:
API_URL: https://${{ needs.deploy.outputs.api_name }}.execute-api.${{ env.region }}.amazonaws.com/${{ needs.deploy.outputs.branch_name }}
BASE_URL: ${{ needs.deploy.outputs.application_endpoint }}
COGNITO_USER_POOL_CLIENT_ID: ${{ needs.deploy.outputs.cognito_user_pool_client_id }}
COGNITO_USER_POOL_ID: ${{ env.cognito_user_pool_id }}
CYPRESS_STATE_USER_EMAIL: ${{ secrets.CYPRESS_STATE_USER_EMAIL }}
CYPRESS_STATE_USER_PASSWORD: ${{ secrets.CYPRESS_STATE_USER_PASSWORD }}
CYPRESS_ADMIN_USER_EMAIL: ${{ secrets.CYPRESS_ADMIN_USER_EMAIL }}
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/destroy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
description: "Name of the environment to destroy:"
required: true

concurrency:
group: ${{ inputs.environment || github.event.ref }}

permissions:
id-token: write
contents: read
Expand Down Expand Up @@ -63,3 +66,17 @@ jobs:
run: |
./run destroy --stage $STAGE_PREFIX$branch_name --verify false --service app-api
./run destroy --stage $STAGE_PREFIX$branch_name --verify false

# Notify the integrations channel when a destroy action fails
notify_on_destroy_failure:
runs-on: ubuntu-latest
needs:
- destroy
if: ${{ failure() }}
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_TITLE: ":boom: A destroy action has failed on ${{ github.repository }}."
MSG_MINIMAL: true
SLACK_WEBHOOK: ${{ secrets.INTEGRATIONS_SLACK_WEBHOOK }}
5 changes: 2 additions & 3 deletions .github/workflows/scan_security-hub-jira-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ jobs:
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
role-to-assume: ${{ secrets.PRODUCTION_SYNC_OIDC_ROLE }}
- name: Sync Security Hub and Jira
uses: Enterprise-CMCS/mac-fc-security-hub-visibility@v1.0.7
uses: Enterprise-CMCS/mac-fc-security-hub-visibility@v2.0.9
with:
jira-username: "mdct_github_service_account"
jira-token: ${{ secrets.JIRA_ENT_USER_TOKEN }}
jira-host: jiraent.cms.gov
jira-project-key: CMDCT
jira-ignore-statuses: Done, Closed, Canceled
jira-custom-fields: '{ "customfield_10100": "CMDCT-2280", "customfield_26700" : [{"id": "40105", "value": "MFP"}] }'
aws-severities: CRITICAL, HIGH, MEDIUM
assign-jira-ticket-to: "MWTW"
jira-assignee: "MWTW"
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default defineConfig({
testDir: "./tests/e2e",
testMatch: ["**/*.spec.js", "**/*.spec.ts"],
/* Run tests in files in parallel */
fullyParallel: true,
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
Expand Down Expand Up @@ -47,7 +47,7 @@ export default defineConfig({
webServer: {
command: process.env.CI ? "" : "./run local",
url: process.env.BASE_URL || "http://localhost:3000",
reuseExistingServer: process.env.CI || false,
reuseExistingServer: !!process.env.CI,
stdout: "pipe",
},
});
2 changes: 1 addition & 1 deletion services/app-api/forms/wp.json
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@
"content": "Initiative close-out information, to be completed as appropriate during MFP Work Plan revisions",
"props": {
"style": {
"paddingTop": ".5rem"
"padding": ".5rem"
}
}
}
Expand Down
29 changes: 13 additions & 16 deletions services/app-api/handlers/banners/create.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import handler from "../handler-lib";
// utils
import dynamoDb from "../../utils/dynamo/dynamodb-lib";
import { hasPermissions } from "../../utils/auth/authorization";
import { error } from "../../utils/constants/constants";
// types
import { StatusCodes, UserRoles } from "../../utils/types";
import { number, object, string } from "yup";
import { validateData } from "../../utils/validation/validation";
import { putBanner } from "../../storage/banners";

export const createBanner = handler(async (event, _context) => {
if (!hasPermissions(event, [UserRoles.ADMIN])) {
Expand Down Expand Up @@ -34,22 +34,19 @@ export const createBanner = handler(async (event, _context) => {
);

if (validatedPayload) {
const params = {
TableName: process.env.BANNER_TABLE_NAME!,
Item: {
key: event.pathParameters.bannerId,
createdAt: Date.now(),
lastAltered: Date.now(),
lastAlteredBy: event?.headers["cognito-identity-id"],
title: validatedPayload.title,
description: validatedPayload.description,
link: validatedPayload.link,
startDate: validatedPayload.startDate,
endDate: validatedPayload.endDate,
},
const newBanner = {
key: event.pathParameters.bannerId,
createdAt: Date.now(),
lastAltered: Date.now(),
lastAlteredBy: event?.headers["cognito-identity-id"],
title: validatedPayload.title,
description: validatedPayload.description,
link: validatedPayload.link,
startDate: validatedPayload.startDate,
endDate: validatedPayload.endDate,
};
await dynamoDb.put(params);
return { status: StatusCodes.CREATED, body: params };
await putBanner(newBanner);
return { status: StatusCodes.CREATED, body: newBanner };
}
}
});
13 changes: 4 additions & 9 deletions services/app-api/handlers/banners/delete.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import handler from "../handler-lib";
// utils
import dynamoDb from "../../utils/dynamo/dynamodb-lib";
import { hasPermissions } from "../../utils/auth/authorization";
import { error } from "../../utils/constants/constants";
import { deleteBanner as deleteBannerById } from "../../storage/banners";
// types
import { StatusCodes, UserRoles } from "../../utils/types";

Expand All @@ -15,13 +15,8 @@ export const deleteBanner = handler(async (event, _context) => {
} else if (!event?.pathParameters?.bannerId!) {
throw new Error(error.NO_KEY);
} else {
const params = {
TableName: process.env.BANNER_TABLE_NAME!,
Key: {
key: event?.pathParameters?.bannerId!,
},
};
await dynamoDb.delete(params);
return { status: StatusCodes.SUCCESS, body: params };
const bannerId = event?.pathParameters?.bannerId!;
await deleteBannerById(bannerId);
return { status: StatusCodes.SUCCESS, body: { Key: bannerId } };
}
});
20 changes: 8 additions & 12 deletions services/app-api/handlers/banners/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { fetchBanner } from "./fetch";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
import { mockClient } from "aws-sdk-client-mock";
// utils
import { proxyEvent } from "../../utils/testing/proxyEvent";
import { error } from "../../utils/constants/constants";
import { mockBannerResponse } from "../../utils/testing/setupJest";
import { getBanner } from "../../storage/banners";
// types
import { APIGatewayProxyEvent, StatusCodes } from "../../utils/types";

const dynamoClientMock = mockClient(DynamoDBDocumentClient);

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

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

const testEvent: APIGatewayProxyEvent = {
...proxyEvent,
headers: { "cognito-identity-id": "test" },
Expand All @@ -31,15 +32,12 @@ let consoleSpy: {
describe("Test fetchBanner API method", () => {
beforeEach(() => {
jest.restoreAllMocks();
dynamoClientMock.reset();
consoleSpy.debug = jest.spyOn(console, "debug").mockImplementation();
consoleSpy.error = jest.spyOn(console, "error").mockImplementation();
});

test("Test Successful Banner Fetch", async () => {
dynamoClientMock.on(GetCommand).resolves({
Item: mockBannerResponse,
});
(getBanner as jest.Mock).mockResolvedValueOnce(mockBannerResponse);
const res = await fetchBanner(testEvent, null);

expect(consoleSpy.debug).toHaveBeenCalled();
Expand All @@ -49,13 +47,11 @@ describe("Test fetchBanner API method", () => {
});

test("Test successful empty banner found fetch", async () => {
dynamoClientMock.on(GetCommand).resolves({
Item: undefined,
});
(getBanner as jest.Mock).mockResolvedValueOnce(undefined);
const res = await fetchBanner(testEvent, null);

expect(consoleSpy.debug).toHaveBeenCalled();
expect(res.body).not.toContain("testTitle");
expect(res.body).not.toBeDefined();
expect(res.statusCode).toBe(StatusCodes.SUCCESS);
});

Expand Down
15 changes: 4 additions & 11 deletions services/app-api/handlers/banners/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import handler from "../handler-lib";
import dynamoDb from "../../utils/dynamo/dynamodb-lib";
// types
import { StatusCodes } from "../../utils/types";
// utils
import { error } from "../../utils/constants/constants";
import { getBanner } from "../../storage/banners";

export const fetchBanner = handler(async (event, _context) => {
if (!event?.pathParameters?.bannerId!) {
throw new Error(error.NO_KEY);
}
const params = {
TableName: process.env.BANNER_TABLE_NAME!,
Key: {
key: event?.pathParameters?.bannerId!,
},
};
const response = await dynamoDb.get(params);

const status = StatusCodes.SUCCESS;
return { status: status, body: response };
const bannerId = event?.pathParameters?.bannerId!;
const banner = await getBanner(bannerId);
return { status: StatusCodes.SUCCESS, body: banner };
});
28 changes: 25 additions & 3 deletions services/app-api/handlers/reports/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ import {
import { error } from "../../utils/constants/constants";
import * as authFunctions from "../../utils/auth/authorization";
import { getEligibleWorkPlan } from "../../utils/other/other";
import { putReportMetadata, putReportFieldData } from "../../storage/reports";
import {
queryReportMetadatasForState,
putReportMetadata,
putReportFieldData,
} from "../../storage/reports";
// types
import { APIGatewayProxyEvent, StatusCodes } from "../../utils/types";
import { copyFieldDataFromSource } from "../../utils/other/copy";
import { getOrCreateFormTemplate } from "../../utils/formTemplates/formTemplates";

jest.mock("../../storage/reports", () => ({
queryReportMetadatasForState: jest.fn(),
putReportFieldData: jest.fn(),
putReportMetadata: jest.fn(),
}));
Expand Down Expand Up @@ -66,7 +71,7 @@ const wpCreationEvent: APIGatewayProxyEvent = {
metadata: {
reportType: "WP",
reportYear: 2020,
reportPeriod: 2,
reportPeriod: 1,
submissionName: "submissionName",
status: "Not started",
lastAlteredBy: "Thelonious States",
Expand Down Expand Up @@ -198,6 +203,9 @@ describe("Test createReport API method", () => {
});

test("Test successful run of work plan report creation, not copied", async () => {
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: true },
]);
const res = await createReport(wpCreationEvent, null);
const body = JSON.parse(res.body);
expect(consoleSpy.debug).toHaveBeenCalled();
Expand All @@ -209,12 +217,23 @@ describe("Test createReport API method", () => {
mockWPReport.metadata.formTemplateId
);
expect(body.reportYear).toEqual(2020);
expect(body.reportPeriod).toEqual(2);
expect(body.reportPeriod).toEqual(1);
expect(putReportMetadata).toHaveBeenCalled();
expect(putReportFieldData).toHaveBeenCalled();
});

test("Test work plan report creation returns 400 if report in year and period exists", async () => {
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: undefined },
]);
const res = await createReport(wpCreationEvent, null);
expect(res.statusCode).toBe(400);
});

test("Test successful run of work plan report creation, copied", async () => {
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: undefined },
]);
(copyFieldDataFromSource as jest.Mock).mockResolvedValue(
mockReportFieldData
);
Expand Down Expand Up @@ -245,6 +264,9 @@ describe("Test createReport API method", () => {
workPlanMetadata: mockWPMetadata,
workPlanFieldData: mockWPFieldData,
});
(queryReportMetadatasForState as jest.Mock).mockResolvedValue([
{ reportYear: 2020, reportPeriod: 1, archived: true },
]);
const res = await createReport(sarCreationEvent, null);
const body = JSON.parse(res.body);
expect(consoleSpy.debug).toHaveBeenCalled();
Expand Down
Loading
Loading