diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index c799ad68..5e27ea2f 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -80,7 +80,7 @@ jobs: group: ${{ github.event.repository.name }}-dev-env cancel-in-progress: false environment: "AWS DEV" - name: Deploy to DEV and Run Tests + name: Deploy to DEV and Run Live Tests needs: - test - build @@ -132,13 +132,9 @@ jobs: - name: Run health check run: make dev_health_check - - name: Run live testing - run: make test_live_integration - env: - JWT_KEY: ${{ secrets.JWT_KEY }} - - - name: Run E2E testing - run: make test_e2e + - name: Run post-deploy testing (Live and E2E) + run: make test_post_deploy -j 2 env: PLAYWRIGHT_USERNAME: ${{ secrets.PLAYWRIGHT_USERNAME }} PLAYWRIGHT_PASSWORD: ${{ secrets.PLAYWRIGHT_PASSWORD }} + JWT_KEY: ${{ secrets.JWT_KEY }} diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 00000000..1b50eaf0 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,33 @@ +name: Required Reviews +on: + pull_request_review: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + check: + name: Checking required reviews + runs-on: ubuntu-latest + permissions: {} + + if: github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name + + steps: + - uses: Automattic/action-required-review@v4.1.0 + with: + token: ${{ secrets.REQUIRED_REVIEWS_TOKEN }} + status: Required Reviews + requirements: | + - name: Cloud provider files + paths: + - '.github/' + - 'cloudformation/' + teams: + - "officers" + - "infra-chairs" + - name: Base Requirement + paths: unmatched + teams: + - "officers" + - "infra-chairs" + - "infra-leads" diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index b79618b2..00000000 --- a/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @acm-uiuc/infra-chairs @acm-uiuc/infra-leads diff --git a/Makefile b/Makefile index b24d8023..0c5b8824 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ clean: rm -rf dist_devel/ rm -rf coverage/ -build: src/ cloudformation/ docs/ +build: src/ cloudformation/ yarn -D VITE_BUILD_HASH=$(GIT_HASH) yarn build cp -r src/api/resources/ dist/api/resources @@ -86,6 +86,7 @@ invalidate_cloudfront: $(eval DISTRIBUTION_ID_2 := $(shell aws cloudformation describe-stacks --stack-name $(application_key) --query "Stacks[0].Outputs[?OutputKey=='CloudfrontIcalDistributionId'].OutputValue" --output text)) $(eval INVALIDATION_ID := $(shell aws cloudfront create-invalidation --distribution-id $(DISTRIBUTION_ID) --paths "/*" --query 'Invalidation.Id' --output text --no-cli-page)) $(eval INVALIDATION_ID_2 := $(shell aws cloudfront create-invalidation --distribution-id $(DISTRIBUTION_ID_2) --paths "/*" --query 'Invalidation.Id' --output text --no-cli-page)) + @echo "Triggered invalidation jobs $(INVALIDATION_ID) and $(INVALIDATION_ID_2)..." @echo "Waiting on job $(INVALIDATION_ID)..." aws cloudfront wait invalidation-completed --distribution-id $(DISTRIBUTION_ID) --id $(INVALIDATION_ID) @echo "Waiting on job $(INVALIDATION_ID_2)..." @@ -110,6 +111,8 @@ test_e2e: install yarn playwright install yarn test:e2e +test_post_deploy: test_live_integration test_e2e + dev_health_check: curl -f https://core.aws.qa.acmuiuc.org/api/v1/healthz && curl -f https://core.aws.qa.acmuiuc.org/ diff --git a/docs/swagger.yml b/docs/swagger.yml deleted file mode 100644 index c91ab572..00000000 --- a/docs/swagger.yml +++ /dev/null @@ -1,96 +0,0 @@ -openapi: 3.0.3 -info: - title: ACM UIUC Sample API - Node.js - version: "1.0.0" - contact: - name: ACM Infrastructure Team - email: infra@acm.illinois.edu - -paths: - /api/v1/healthz: - get: - summary: Ping the API - operationId: ping - - responses: - 200: - description: OK - - x-amazon-apigateway-auth: - type: NONE - - x-amazon-apigateway-integration: - responses: - default: - statusCode: 200 - passthroughBehavior: when_no_match - httpMethod: POST - contentHandling: CONVERT_TO_TEXT - type: aws_proxy - uri: - Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ApplicationPrefix}-lambda/invocations" - /api/v1/protected: - get: - summary: Returns the authenticated user's username. - operationId: pingAuthenticated - - responses: - 200: - description: OK - - x-amazon-apigateway-auth: - type: NONE - - x-amazon-apigateway-integration: - responses: - default: - statusCode: 200 - passthroughBehavior: when_no_match - httpMethod: POST - contentHandling: CONVERT_TO_TEXT - type: aws_proxy - uri: - Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ApplicationPrefix}-lambda/invocations" - /api/v1/events: - get: - summary: Get all ACM Events - operationId: getAllEvents - - responses: - 200: - description: OK - - x-amazon-apigateway-auth: - type: NONE - - x-amazon-apigateway-integration: - responses: - default: - statusCode: 200 - passthroughBehavior: when_no_match - httpMethod: POST - contentHandling: CONVERT_TO_TEXT - type: aws_proxy - uri: - Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ApplicationPrefix}-lambda/invocations" - post: - summary: Add an ACM event conforming to the Zod schema. - operationId: addEvent - - responses: - 200: - description: OK - - x-amazon-apigateway-auth: - type: NONE - - x-amazon-apigateway-integration: - responses: - default: - statusCode: 200 - passthroughBehavior: when_no_match - httpMethod: POST - contentHandling: CONVERT_TO_TEXT - type: aws_proxy - uri: - Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ApplicationPrefix}-lambda/invocations" \ No newline at end of file diff --git a/package.json b/package.json index 7ab57674..c03e6d63 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,17 @@ "scripts": { "postinstall": "npm run setup", "setup": "git config blame.ignoreRevsFile .git-blame-ignore-revs", - "build": "yarn workspaces run build && yarn lockfile-manage", + "build": "concurrently --names 'api,ui' 'yarn workspace infra-core-api run build' 'yarn workspace infra-core-ui run build'", + "postbuild": "yarn lockfile-manage", "dev": "concurrently --names 'api,ui' 'yarn workspace infra-core-api run dev' 'yarn workspace infra-core-ui run dev'", - "lockfile-manage": "synp --with-workspace --source-file yarn.lock && cp package-lock.json dist/lambda/ && cp package-lock.json dist/sqsConsumer/ && cp src/api/package.lambda.json dist/lambda/package.json && cp src/api/package.lambda.json dist/sqsConsumer/package.json && rm package-lock.json", + "lockfile-manage": "synp --with-workspace --source-file yarn.lock", + "postlockfile-manage": "cp package-lock.json dist/lambda/ && cp package-lock.json dist/sqsConsumer/ && cp src/api/package.lambda.json dist/lambda/package.json && cp src/api/package.lambda.json dist/sqsConsumer/package.json && rm package-lock.json", "prettier": "yarn workspaces run prettier && prettier --check tests/**/*.ts", "prettier:write": "yarn workspaces run prettier:write && prettier --write tests/**/*.ts", "lint": "yarn workspaces run lint", "prepare": "node .husky/install.mjs || true", "typecheck": "yarn workspaces run typecheck", - "test:unit": "cross-env RunEnvironment='dev' vitest run --coverage --config tests/unit/vitest.config.ts tests/unit && yarn workspace infra-core-ui run test:unit", + "test:unit": "cross-env RunEnvironment='dev' concurrently --names 'api,ui' 'vitest run --coverage --config tests/unit/vitest.config.ts tests/unit' 'yarn workspace infra-core-ui run test:unit'", "test:unit-ui": "yarn test:unit --ui", "test:unit-watch": "vitest tests/unit", "test:live": "vitest tests/live", @@ -89,4 +91,4 @@ "resolutions": { "pdfjs-dist": "^4.8.69" } -} \ No newline at end of file +} diff --git a/tests/live/membership.test.ts b/tests/live/membership.test.ts index 749cd24f..08868c58 100644 --- a/tests/live/membership.test.ts +++ b/tests/live/membership.test.ts @@ -4,7 +4,7 @@ import { getBaseEndpoint } from "./utils.js"; const baseEndpoint = getBaseEndpoint(); describe("Membership API basic checks", async () => { - test("Test that getting member succeeds", { timeout: 3000 }, async () => { + test("Test that getting member succeeds", { timeout: 10000 }, async () => { const response = await fetch(`${baseEndpoint}/api/v1/membership/dsingh14`, { method: "GET", }); @@ -23,7 +23,7 @@ describe("Membership API basic checks", async () => { }); test( "Test that getting member with non-standard casing succeeds", - { timeout: 3000 }, + { timeout: 10000 }, async () => { const response = await fetch( `${baseEndpoint}/api/v1/membership/DSingh14`, @@ -47,7 +47,7 @@ describe("Membership API basic checks", async () => { ); test( "Test that getting non-members succeeds", - { timeout: 3000 }, + { timeout: 10000 }, async () => { const response = await fetch(`${baseEndpoint}/api/v1/membership/zzzz`, { method: "GET", @@ -62,7 +62,7 @@ describe("Membership API basic checks", async () => { }); }, ); - test("Test that too long NetID is rejected", { timeout: 3000 }, async () => { + test("Test that too long NetID is rejected", { timeout: 10000 }, async () => { const response = await fetch( `${baseEndpoint}/api/v1/membership/dsafdsfdsfsdafsfsdfasfsfsfds`, { @@ -73,17 +73,21 @@ describe("Membership API basic checks", async () => { expect(response.status).toBe(400); expect(response.headers.get("x-acm-data-source")).toBeNull(); }); - test("Test that too short NetID is rejected", { timeout: 3000 }, async () => { - const response = await fetch(`${baseEndpoint}/api/v1/membership/ds`, { - method: "GET", - }); + test( + "Test that too short NetID is rejected", + { timeout: 10000 }, + async () => { + const response = await fetch(`${baseEndpoint}/api/v1/membership/ds`, { + method: "GET", + }); - expect(response.status).toBe(400); - expect(response.headers.get("x-acm-data-source")).toBeNull(); - }); + expect(response.status).toBe(400); + expect(response.headers.get("x-acm-data-source")).toBeNull(); + }, + ); test( "Test that getting external non-members succeeds", - { timeout: 3000 }, + { timeout: 10000 }, async () => { const response = await fetch( `${baseEndpoint}/api/v1/membership/zzzz?list=built`, @@ -104,7 +108,7 @@ describe("Membership API basic checks", async () => { ); test( "Test that getting external members succeeds", - { timeout: 3000 }, + { timeout: 10000 }, async () => { const response = await fetch( `${baseEndpoint}/api/v1/membership/zzzz?list=acmtesting`,