diff --git a/.eslintrc.js b/.eslintrc.js index 22b94d79e369..ac4546567833 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -164,7 +164,7 @@ module.exports = { }, }, { - files: ['tests/**/*.{js,jsx,ts,tsx}', '.github/**/*.{js,jsx,ts,tsx}'], + files: ['workflow_tests/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}', '.github/**/*.{js,jsx,ts,tsx}'], rules: { '@lwc/lwc/no-async-await': 'off', 'no-await-in-loop': 'off', diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index 494326869cca..b6558b049647 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -13,7 +13,8 @@ jobs: outputs: IS_DEPLOYER: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' }} steps: - - id: isDeployer + - name: Check if user is deployer + id: isDeployer run: | if gh api /orgs/Expensify/teams/mobile-deployers/memberships/${{ github.actor }} --silent; then echo "IS_DEPLOYER=true" >> "$GITHUB_OUTPUT" @@ -39,7 +40,8 @@ jobs: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Set up git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 8943669c2ba8..54ae1048b57b 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -13,18 +13,20 @@ jobs: # It does not run for pull requests created by OSBotify if: ${{ github.event.issue.pull_request || (github.event_name == 'pull_request_target' && github.event.pull_request.user.login != 'OSBotify') }} steps: - - uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 + - name: CLA comment check + uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 id: sign with: text: ${{ github.event.comment.body }} regex: '\s*I have read the CLA Document and I hereby sign the CLA\s*' - - uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 + - name: CLA comment re-check + uses: actions-ecosystem/action-regex-match@9c35fe9ac1840239939c59e5db8839422eed8a73 id: recheck with: text: ${{ github.event.comment.body }} regex: '\s*recheck\s*' - name: CLA Assistant - if: ${{ steps.recheck.outputs.match != '' || steps.sign.outputs.match != '' }} || github.event_name == 'pull_request_target' + if: ${{ steps.recheck.outputs.match != '' || steps.sign.outputs.match != '' || github.event_name == 'pull_request_target' }} # Version: 2.1.2-beta uses: cla-assistant/github-action@948230deb0d44dd38957592f08c6bd934d96d0cf env: diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index b2703731df79..ba907334c595 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -54,18 +54,21 @@ jobs: NEW_VERSION: ${{ steps.bumpVersion.outputs.NEW_VERSION }} steps: - - uses: softprops/turnstyle@ca99add00ff0c9cbc697d22631d2992f377e5bd5 + - name: Run turnstyle + uses: softprops/turnstyle@ca99add00ff0c9cbc697d22631d2992f377e5bd5 with: poll-interval-seconds: 10 env: GITHUB_TOKEN: ${{ github.token }} - - uses: actions/checkout@v3 + - name: Check out + uses: actions/checkout@v3 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Setup git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} @@ -89,7 +92,8 @@ jobs: - name: Update main branch run: git push origin main - - if: ${{ failure() }} + - name: Announce failed workflow in Slack + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b3105ee05c2c..f2ff67680940 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,8 @@ jobs: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Setup git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} @@ -29,12 +30,14 @@ jobs: runs-on: ubuntu-latest if: github.ref == 'refs/heads/production' steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: ref: production token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Setup git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} diff --git a/.github/workflows/deployBlocker.yml b/.github/workflows/deployBlocker.yml index 8065a5c88cb2..f42d19ca8241 100644 --- a/.github/workflows/deployBlocker.yml +++ b/.github/workflows/deployBlocker.yml @@ -11,7 +11,8 @@ jobs: if: ${{ github.event.label.name == 'DeployBlockerCash' }} steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -63,7 +64,8 @@ jobs: 2. Find someone who can quickly fix the issue. 3. Fix the issue yourself. - - if: ${{ failure() }} + - name: Announce failed workflow in Slack + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/finishReleaseCycle.yml b/.github/workflows/finishReleaseCycle.yml index 7b71f6263c88..e2323af2486e 100644 --- a/.github/workflows/finishReleaseCycle.yml +++ b/.github/workflows/finishReleaseCycle.yml @@ -23,7 +23,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - - name: Reopen and comment on issue + - name: Reopen and comment on issue (not a team member) if: ${{ !fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) }} uses: Expensify/App/.github/actions/javascript/reopenIssueWithComment@main with: @@ -41,8 +41,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} ISSUE_NUMBER: ${{ github.event.issue.number }} - - name: Reopen and comment on issue - if: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) && fromJSON(steps.checkDeployBlockers.outputs.HAS_DEPLOY_BLOCKERS) }} + - name: Reopen and comment on issue (has blockers) + if: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) && fromJSON(steps.checkDeployBlockers.outputs.HAS_DEPLOY_BLOCKERS || 'false') }} uses: Expensify/App/.github/actions/javascript/reopenIssueWithComment@main with: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} @@ -51,7 +51,8 @@ jobs: This issue either has unchecked items or has not yet been marked with the `:shipit:` emoji of approval. Reopening! - - if: ${{ failure() }} + - name: Announce failed workflow in Slack + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} @@ -62,12 +63,14 @@ jobs: needs: validate if: ${{ fromJSON(needs.validate.outputs.isValid) }} steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: ref: staging token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Setup Git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} @@ -79,7 +82,8 @@ jobs: # Force-update the remote production branch. git push --force origin production - - if: ${{ failure() }} + - name: Announce failed workflow in Slack + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} @@ -98,12 +102,14 @@ jobs: runs-on: ubuntu-latest needs: [updateProduction, createNewPatchVersion] steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Setup Git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} @@ -115,7 +121,8 @@ jobs: # Force-update the remote staging branch git push --force origin staging - - if: ${{ failure() }} + - name: Announce failed workflow in Slack + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1983e406c77b..5953a4aa89e2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,9 +11,11 @@ jobs: if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: Checkout + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Lint JavaScript and Typescript with ESLint run: npm run lint diff --git a/.github/workflows/lockDeploys.yml b/.github/workflows/lockDeploys.yml index a49a5519f690..6ca025bb2a25 100644 --- a/.github/workflows/lockDeploys.yml +++ b/.github/workflows/lockDeploys.yml @@ -9,7 +9,8 @@ jobs: if: ${{ github.event.label.name == '🔐 LockCashDeploys 🔐' && contains(github.event.issue.labels.*.name, 'StagingDeployCash') && github.actor != 'OSBotify' }} runs-on: macos-12 steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} @@ -27,7 +28,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - - if: ${{ failure() }} + - name: Announce failed workflow + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index cd65b8451636..ad002e164837 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -18,7 +18,8 @@ jobs: outputs: IS_DEPLOYER: ${{ fromJSON(steps.isUserDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' }} steps: - - id: isUserDeployer + - name: Check if user is deployer + id: isUserDeployer run: | if gh api /orgs/Expensify/teams/mobile-deployers/memberships/${{ github.actor }} --silent; then echo "IS_DEPLOYER=true" >> "$GITHUB_OUTPUT" @@ -35,8 +36,10 @@ jobs: if: ${{ github.event_name != 'release' }} needs: validateActor steps: - - uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Set version id: getVersion @@ -54,14 +57,17 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} runs-on: ubuntu-latest-xl steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Configure MapBox SDK run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 + - name: Setup Ruby + uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 with: ruby-version: '2.7' bundler-cache: true @@ -128,9 +134,11 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} runs-on: macos-12-xl steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Decrypt Developer ID Certificate run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg @@ -165,14 +173,17 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} runs-on: macos-12-xl steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Configure MapBox SDK run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 + - name: Setup Ruby + uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 with: ruby-version: '2.7' bundler-cache: true @@ -267,9 +278,11 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} runs-on: ubuntu-latest-xl steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Setup Cloudflare CLI run: pip3 install cloudflare @@ -324,7 +337,8 @@ jobs: if: ${{ failure() }} needs: [android, desktop, iOS, web] steps: - - uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main + - name: Post Slack message on failure + uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} @@ -334,7 +348,8 @@ jobs: if: ${{ success() }} needs: [android, desktop, iOS, web] steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Set version run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" @@ -394,9 +409,11 @@ jobs: if: ${{ always() }} needs: [android, desktop, iOS, web] steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Set version run: echo "VERSION=$(npm run print-version --silent)" >> "$GITHUB_ENV" diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index e3977734fc50..186490c7baaf 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -2,7 +2,8 @@ name: Process new code merged to main on: push: - branches: [main] + branches: + - main jobs: typecheck: @@ -20,12 +21,14 @@ jobs: if: ${{ always() }} steps: - - if: ${{ needs.typecheck.result == 'failure' || needs.lint.result == 'failure' || needs.test.result == 'failure' }} + - name: Announce failed workflow in Slack + if: ${{ needs.typecheck.result == 'failure' || needs.lint.result == 'failure' || needs.test.result == 'failure' }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - - if: ${{ needs.typecheck.result == 'failure' || needs.lint.result == 'failure' || needs.test.result == 'failure' }} + - name: Exit failed workflow + if: ${{ needs.typecheck.result == 'failure' || needs.lint.result == 'failure' || needs.test.result == 'failure' }} run: exit 1 chooseDeployActions: @@ -33,7 +36,7 @@ jobs: needs: confirmPassingBuild outputs: MERGED_PR: ${{ steps.getMergedPullRequest.outputs.number }} - SHOULD_DEPLOY: ${{ steps.shouldDeploy.outputs.SHOULD_DEPLOY }} + SHOULD_DEPLOY: ${{ fromJSON(steps.shouldDeploy.outputs.SHOULD_DEPLOY) }} steps: - name: Get merged pull request @@ -75,18 +78,21 @@ jobs: needs: [chooseDeployActions, createNewVersion] runs-on: ubuntu-latest steps: - - uses: softprops/turnstyle@ca99add00ff0c9cbc697d22631d2992f377e5bd5 + - name: Run turnstyle + uses: softprops/turnstyle@ca99add00ff0c9cbc697d22631d2992f377e5bd5 with: poll-interval-seconds: 10 env: GITHUB_TOKEN: ${{ github.token }} - - uses: actions/checkout@v3 + - name: Checkout main + uses: actions/checkout@v3 with: ref: main token: ${{ secrets.OS_BOTIFY_TOKEN }} - - uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main + - name: Setup Git for OSBotify + uses: Expensify/App/.github/actions/composite/setupGitForOSBotify@main with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} @@ -98,7 +104,8 @@ jobs: # Force-update the remote staging branch git push --force origin staging - - if: ${{ failure() }} + - name: Announce failed workflow in Slack + if: ${{ failure() }} uses: Expensify/App/.github/actions/composite/announceFailedWorkflowInSlack@main with: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} @@ -134,7 +141,8 @@ jobs: if: ${{ github.actor != 'OSBotify' && !fromJSON(needs.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) }} steps: # Version: 2.3.4 - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: Checkout + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 with: token: ${{ secrets.OS_BOTIFY_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e79a02281ae0..72bdd0468fd2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,11 @@ jobs: chunk: [ 1, 2, 3 ] name: test (job ${{ fromJSON(matrix.chunk) }}) steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Get number of CPU cores id: cpu-cores @@ -53,9 +55,11 @@ jobs: runs-on: ubuntu-latest name: Shell tests steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Test CI git logic run: tests/unit/CIGitLogicTest.sh diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index fd8118895679..6ded44d7059f 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -19,7 +19,8 @@ jobs: outputs: READY_TO_BUILD: ${{ fromJSON(steps.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) && fromJSON(steps.hasReadyToBuildLabel.outputs.HAS_READY_TO_BUILD_LABEL) }} steps: - - id: isExpensifyEmployee + - name: Is Expensify employee + id: isExpensifyEmployee run: | if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ github.actor }} --silent; then echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" @@ -70,7 +71,8 @@ jobs: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} steps: # This action checks-out the repository, so the workflow can access it. - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: Checkout + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} @@ -80,9 +82,11 @@ jobs: sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 + - name: Setup Ruby + uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 with: ruby-version: '2.7' bundler-cache: true @@ -117,7 +121,8 @@ jobs: MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }} MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }} - - uses: actions/upload-artifact@v3 + - name: Upload Artifact + uses: actions/upload-artifact@v3 with: name: android path: ./android_paths.json @@ -131,7 +136,8 @@ jobs: runs-on: macos-12-xl steps: # This action checks-out the repository, so the workflow can access it. - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: Checkout + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} @@ -144,12 +150,14 @@ jobs: sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - - name: Setup Xcode + - name: Setup XCode run: sudo xcode-select -switch /Applications/Xcode_14.2.app - - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 + - name: Setup Ruby + uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 with: ruby-version: '2.7' bundler-cache: true @@ -198,7 +206,8 @@ jobs: S3_BUCKET: ad-hoc-expensify-cash S3_REGION: us-east-1 - - uses: actions/upload-artifact@v3 + - name: Upload Artifact + uses: actions/upload-artifact@v3 with: name: ios path: ./ios_paths.json @@ -211,7 +220,8 @@ jobs: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} runs-on: macos-12-xl steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} @@ -221,7 +231,8 @@ jobs: sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Decrypt Developer ID Certificate run: cd desktop && gpg --quiet --batch --yes --decrypt --passphrase="$DEVELOPER_ID_SECRET_PASSPHRASE" --output developer_id.p12 developer_id.p12.gpg @@ -252,7 +263,8 @@ jobs: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} runs-on: ubuntu-latest-xl steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} @@ -262,7 +274,8 @@ jobs: sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main - name: Configure AWS Credentials uses: Expensify/App/.github/actions/composite/configureAwsCredentials@main @@ -283,7 +296,7 @@ jobs: postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing - needs: [validateActor, getBranchRef, android, ios, desktop, web] + needs: [validateActor, getBranchRef, android, iOS, desktop, web] if: ${{ always() }} env: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} @@ -294,7 +307,8 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} - - uses: actions/download-artifact@v3 + - name: Download Artifact + uses: actions/download-artifact@v3 if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - name: Read JSONs with android paths @@ -310,7 +324,7 @@ jobs: - name: Read JSONs with iOS paths id: get_ios_path - if: ${{ needs.ios.result == 'success' }} + if: ${{ needs.iOS.result == 'success' }} run: | content_ios="$(cat ./ios/ios_paths.json)" content_ios="${content_ios//'%'/'%25'}" diff --git a/.github/workflows/validateGithubActions.yml b/.github/workflows/validateGithubActions.yml index f496c5e4b27e..bcda941e1b05 100644 --- a/.github/workflows/validateGithubActions.yml +++ b/.github/workflows/validateGithubActions.yml @@ -12,9 +12,11 @@ jobs: if: github.actor != 'OSBotify' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - - uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main # Rebuild all the actions on this branch and check for a diff. Fail if there is one, # because that would be a sign that the PR author did not rebuild the Github Actions diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml index 64188769f0bd..d8d931e476d1 100644 --- a/.github/workflows/verifyPodfile.yml +++ b/.github/workflows/verifyPodfile.yml @@ -14,8 +14,9 @@ jobs: if: github.actor != 'OSBotify' runs-on: macos-latest steps: - - uses: actions/checkout@v3 - - - uses: Expensify/App/.github/actions/composite/setupNode@main - - - run: ./.github/scripts/verifyPodfile.sh + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Node + uses: Expensify/App/.github/actions/composite/setupNode@main + - name: Verify podfile + run: ./.github/scripts/verifyPodfile.sh diff --git a/.github/workflows/verifySignedCommits.yml b/.github/workflows/verifySignedCommits.yml index e1068e71e041..ee1b0c4c78da 100644 --- a/.github/workflows/verifySignedCommits.yml +++ b/.github/workflows/verifySignedCommits.yml @@ -9,6 +9,7 @@ jobs: verifySignedCommits: runs-on: ubuntu-latest steps: - - uses: Expensify/App/.github/actions/javascript/verifySignedCommits@main + - name: Verify signed commits + uses: Expensify/App/.github/actions/javascript/verifySignedCommits@main with: GITHUB_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index 4919ddc1fdc9..aae9baad529f 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,9 @@ tests/e2e/results/ # Typescript tsconfig.tsbuildinfo + +# Mock-github +/repo/ + +# Workflow test logs +/workflow_tests/logs/ diff --git a/package-lock.json b/package-lock.json index 7c93a456cc5f..84fd66245b9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "@formatjs/intl-pluralrules": "^5.2.2", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", + "@kie/act-js": "^2.0.1", + "@kie/mock-github": "^1.0.0", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "7.4.0", "@react-native-async-storage/async-storage": "^1.17.10", @@ -37,6 +39,7 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", + "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", @@ -130,6 +133,8 @@ "@babel/runtime": "^7.20.0", "@electron/notarize": "^1.2.3", "@jest/globals": "^29.5.0", + "@kie/act-js": "^2.0.1", + "@kie/mock-github": "^1.0.0", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", @@ -223,7 +228,8 @@ "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.9.3", "webpack-font-preload-plugin": "^1.5.0", - "webpack-merge": "^5.8.0" + "webpack-merge": "^5.8.0", + "yaml": "^2.2.1" }, "engines": { "node": "16.15.1", @@ -5222,6 +5228,110 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@kie/act-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.3.0.tgz", + "integrity": "sha512-Q9k0b05uA46jXKWmVfoGDW+0xsCcE7QPiHi8IH7h41P36DujHKBj4k28RCeIEx3IwUCxYHKwubN8DH4Vzc9XcA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@kie/mock-github": "^2.0.0", + "adm-zip": "^0.5.10", + "ajv": "^8.12.0", + "bin-links": "^4.0.1", + "express": "^4.18.1", + "follow-redirects": "^1.15.2", + "tar": "^6.1.13", + "yaml": "^2.1.3" + }, + "bin": { + "act-js": "bin/act" + } + }, + "node_modules/@kie/act-js/node_modules/@kie/mock-github": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-2.0.0.tgz", + "integrity": "sha512-od6UyICJYKMnz9HgEWCQAFT/JsCpKkLp+JQH8fV23tf+ZmmQI1dK3C20k6aO5uJhAHA0yOcFtbKFVF4+8i3DTg==", + "dev": true, + "dependencies": { + "@octokit/openapi-types-ghec": "^18.0.0", + "ajv": "^8.11.0", + "express": "^4.18.1", + "fast-glob": "^3.2.12", + "fs-extra": "^10.1.0", + "nock": "^13.2.7", + "simple-git": "^3.8.0", + "totalist": "^3.0.0" + } + }, + "node_modules/@kie/act-js/node_modules/@octokit/openapi-types-ghec": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-18.0.0.tgz", + "integrity": "sha512-xY5FTR/DW2gUJdC5GyzkqrfMHhr3u3hg+dUG6bA5FvuuODw6A7+0JTTSS1ndLQEKGmFxP7chf1BKkhvhnqxCew==", + "dev": true + }, + "node_modules/@kie/act-js/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@kie/act-js/node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@kie/mock-github": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-1.1.0.tgz", + "integrity": "sha512-fD+utlOiyZSOutOcXL0G9jfjbtvOO44PLUyTfgfkrm1+575R/dbvU6AcJfjc1DtHeRv7FC7f4ebyU+a1wgL6CA==", + "dev": true, + "dependencies": { + "@octokit/openapi-types-ghec": "^14.0.0", + "ajv": "^8.11.0", + "express": "^4.18.1", + "fast-glob": "^3.2.12", + "fs-extra": "^10.1.0", + "nock": "^13.2.7", + "simple-git": "^3.8.0", + "totalist": "^3.0.0" + } + }, + "node_modules/@kie/mock-github/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@kie/mock-github/node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -5728,6 +5838,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@octokit/openapi-types-ghec": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-14.0.0.tgz", + "integrity": "sha512-xhd9oEvn2aroGn+sk09Ptx/76Y7aKU0EIgHukHPCU1+rGJreO36baEEk6k8ZPblieHNM39FcykJQmtDrETm0KA==", + "dev": true + }, "node_modules/@octokit/plugin-paginate-rest": { "version": "3.1.0", "dev": true, @@ -6958,14 +7074,6 @@ "node": ">=8" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "engines": { - "node": ">= 14" - } - }, "node_modules/@react-native-community/cli-hermes": { "version": "11.3.5", "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-11.3.5.tgz", @@ -19409,6 +19517,15 @@ "node": ">= 10.0.0" } }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "dev": true, @@ -21246,6 +21363,46 @@ "node": "*" } }, + "node_modules/bin-links": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.2.tgz", + "integrity": "sha512-jxJ0PbXR8eQyPlExCvCs3JFnikvs1Yp4gUJt6nmgathdOwvur+q22KWC3h20gvWl4T/14DXKj2IlkJwwZkZPOw==", + "dev": true, + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/bin-links/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/bin-links/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "devOptional": true, @@ -22679,6 +22836,15 @@ "node": ">=6" } }, + "node_modules/cmd-shim": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", + "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/co": { "version": "4.6.0", "license": "MIT", @@ -23333,6 +23499,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/cp-file": { "version": "7.0.0", "dev": true, @@ -28143,7 +28318,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.1", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true, "funding": [ { @@ -28151,7 +28328,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -28350,6 +28526,15 @@ "node": ">=8" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/form-data": { "version": "3.0.1", "dev": true, @@ -37585,6 +37770,21 @@ "node": ">=12.0.0" } }, + "node_modules/nock": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.3.tgz", + "integrity": "sha512-z+KUlILy9SK/RjpeXDiDUEAq4T94ADPHE3qaRkf66mpEhzc/ytOMm3Bwdrbq6k1tMWkbdujiKim3G2tfQARuJw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.21", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -37768,6 +37968,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "license": "MIT", @@ -38738,14 +38947,6 @@ "node": ">=0.6.0" } }, - "node_modules/patch-package/node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "engines": { - "node": ">= 14" - } - }, "node_modules/path-browserify": { "version": "0.0.1", "license": "MIT" @@ -39444,6 +39645,15 @@ "react-is": "^16.13.1" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/property-information": { "version": "5.6.0", "dev": true, @@ -41454,6 +41664,15 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -47753,11 +47972,11 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "dev": true, - "license": "ISC", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { @@ -51291,6 +51510,98 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "@kie/act-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@kie/act-js/-/act-js-2.3.0.tgz", + "integrity": "sha512-Q9k0b05uA46jXKWmVfoGDW+0xsCcE7QPiHi8IH7h41P36DujHKBj4k28RCeIEx3IwUCxYHKwubN8DH4Vzc9XcA==", + "dev": true, + "requires": { + "@kie/mock-github": "^2.0.0", + "adm-zip": "^0.5.10", + "ajv": "^8.12.0", + "bin-links": "^4.0.1", + "express": "^4.18.1", + "follow-redirects": "^1.15.2", + "tar": "^6.1.13", + "yaml": "^2.1.3" + }, + "dependencies": { + "@kie/mock-github": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-2.0.0.tgz", + "integrity": "sha512-od6UyICJYKMnz9HgEWCQAFT/JsCpKkLp+JQH8fV23tf+ZmmQI1dK3C20k6aO5uJhAHA0yOcFtbKFVF4+8i3DTg==", + "dev": true, + "requires": { + "@octokit/openapi-types-ghec": "^18.0.0", + "ajv": "^8.11.0", + "express": "^4.18.1", + "fast-glob": "^3.2.12", + "fs-extra": "^10.1.0", + "nock": "^13.2.7", + "simple-git": "^3.8.0", + "totalist": "^3.0.0" + } + }, + "@octokit/openapi-types-ghec": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-18.0.0.tgz", + "integrity": "sha512-xY5FTR/DW2gUJdC5GyzkqrfMHhr3u3hg+dUG6bA5FvuuODw6A7+0JTTSS1ndLQEKGmFxP7chf1BKkhvhnqxCew==", + "dev": true + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true + } + } + }, + "@kie/mock-github": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@kie/mock-github/-/mock-github-1.1.0.tgz", + "integrity": "sha512-fD+utlOiyZSOutOcXL0G9jfjbtvOO44PLUyTfgfkrm1+575R/dbvU6AcJfjc1DtHeRv7FC7f4ebyU+a1wgL6CA==", + "dev": true, + "requires": { + "@octokit/openapi-types-ghec": "^14.0.0", + "ajv": "^8.11.0", + "express": "^4.18.1", + "fast-glob": "^3.2.12", + "fs-extra": "^10.1.0", + "nock": "^13.2.7", + "simple-git": "^3.8.0", + "totalist": "^3.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true + } + } + }, "@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -51673,6 +51984,12 @@ "version": "12.11.0", "dev": true }, + "@octokit/openapi-types-ghec": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types-ghec/-/openapi-types-ghec-14.0.0.tgz", + "integrity": "sha512-xhd9oEvn2aroGn+sk09Ptx/76Y7aKU0EIgHukHPCU1+rGJreO36baEEk6k8ZPblieHNM39FcykJQmtDrETm0KA==", + "dev": true + }, "@octokit/plugin-paginate-rest": { "version": "3.1.0", "dev": true, @@ -52586,11 +52903,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==" } } }, @@ -61296,6 +61608,12 @@ "version": "1.2.1", "dev": true }, + "adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true + }, "agent-base": { "version": "6.0.2", "dev": true, @@ -62595,6 +62913,36 @@ "version": "5.2.2", "devOptional": true }, + "bin-links": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.2.tgz", + "integrity": "sha512-jxJ0PbXR8eQyPlExCvCs3JFnikvs1Yp4gUJt6nmgathdOwvur+q22KWC3h20gvWl4T/14DXKj2IlkJwwZkZPOw==", + "dev": true, + "requires": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + } + } + } + }, "binary-extensions": { "version": "2.2.0", "devOptional": true @@ -63570,6 +63918,12 @@ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" }, + "cmd-shim": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", + "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==", + "dev": true + }, "co": { "version": "4.6.0" }, @@ -64004,6 +64358,14 @@ "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } } }, "cp-file": { @@ -67369,7 +67731,9 @@ } }, "follow-redirects": { - "version": "1.15.1", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true }, "for-each": { @@ -67485,6 +67849,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true } } }, @@ -73801,6 +74171,18 @@ "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==" }, + "nock": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.3.tgz", + "integrity": "sha512-z+KUlILy9SK/RjpeXDiDUEAq4T94ADPHE3qaRkf66mpEhzc/ytOMm3Bwdrbq6k1tMWkbdujiKim3G2tfQARuJw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.21", + "propagate": "^2.0.0" + } + }, "node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -73934,6 +74316,12 @@ "version": "6.1.0", "dev": true }, + "npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true + }, "npm-run-path": { "version": "4.0.1", "requires": { @@ -74565,11 +74953,6 @@ "requires": { "os-tmpdir": "~1.0.2" } - }, - "yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==" } } }, @@ -75034,6 +75417,12 @@ "react-is": "^16.13.1" } }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, "property-information": { "version": "5.6.0", "dev": true, @@ -76324,6 +76713,12 @@ "memoize-one": ">=3.1.1 <6" } }, + "read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true + }, "read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -80659,8 +81054,9 @@ "version": "4.0.0" }, "yaml": { - "version": "1.10.2", - "dev": true + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==" }, "yargs": { "version": "13.3.2", diff --git a/package.json b/package.json index 6899bc70e9d2..3395ce7e3413 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,9 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "test:e2e": "node tests/e2e/testRunner.js --development" + "test:e2e": "node tests/e2e/testRunner.js --development", + "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", + "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js" }, "dependencies": { "@expensify/react-native-web": "0.18.15", @@ -60,6 +62,8 @@ "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", + "@kie/act-js": "^2.0.1", + "@kie/mock-github": "^1.0.0", "@onfido/react-native-sdk": "7.4.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", @@ -79,6 +83,7 @@ "@rnmapbox/maps": "^10.0.11", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", + "@types/node": "^18.14.0", "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -170,6 +175,8 @@ "@babel/runtime": "^7.20.0", "@electron/notarize": "^1.2.3", "@jest/globals": "^29.5.0", + "@kie/act-js": "^2.0.1", + "@kie/mock-github": "^1.0.0", "@octokit/core": "4.0.4", "@octokit/plugin-paginate-rest": "3.1.0", "@octokit/plugin-throttling": "4.1.0", @@ -263,7 +270,8 @@ "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.9.3", "webpack-font-preload-plugin": "^1.5.0", - "webpack-merge": "^5.8.0" + "webpack-merge": "^5.8.0", + "yaml": "^2.2.1" }, "overrides": { "react-native": "$react-native" diff --git a/workflow_tests/README.md b/workflow_tests/README.md new file mode 100644 index 000000000000..f5975a421120 --- /dev/null +++ b/workflow_tests/README.md @@ -0,0 +1,422 @@ +# Testing GitHub Actions workflows locally + +## Components +The workflow testing framework consists mainly of 3 components: +- [Jest](https://jestjs.io/) - testing framework, also used for [application tests](https://github.com/Expensify/App/tree/main/tests) +- [Mock-github](https://github.com/kiegroup/mock-github) - package allowing for creation of local repositories, which can be used to make sure that the workflow tests have access only to these files that they should and that they won't modify the actual repository +- [Act-js](https://github.com/kiegroup/act-js) - JS wrapper around [Act](https://github.com/nektos/act). Act is a tool that allows to run GitHub Actions workflows locally, and Act-js allows to configure and run Act from JS code, like Jest tests. It also provides additional tools like mocking workflow steps and retrieving the workflow output a JSON, which allows for comparison of the actual output with expected values + +## Setup +- Install dependencies from `package.json` file with `npm install` +- Make sure you have fulfilled the [prerequisites](https://github.com/nektos/act#necessary-prerequisites-for-running-act) for running `Act` +- Install `Act` with `brew install act` and follow the documentation on [first Act run](https://github.com/nektos/act#first-act-run) +- Set the environment variable `ACT_BINARY` to the path to your `Act` executable (`which act` if you're not sure what the path is) +- You should be ready to run the tests now with `npm run workflow-test` +- You can pre-generate new mocks/assertions/test files for a given workflow by running `npm run workflow-test:generate ` + +## Running +- To run the workflow tests simply use + - `npm run workflow-test` + - this will run all the tests sequentially, which can take some time +- To run a specific test suite you can use + - `npm run workflow-test -- -i ` + - this will run only the test from that specific test file +- To run a specific test or subset of tests use + - `npm run workflow-test -- -t ""` + - this will run only the tests having `` in their name/description +- You can combine these like `npm run workflow-test -- -i workflow_tests/preDeploy.test.js -t "single specific test"` +- You can also use all other options which are normally usable with `jest` + +## Limitations +Not all workflows can always be tested this way, for example: +- Act and Act-js do not support all the runner types available in GitHub Actions, like `macOS` runners or some specific version of `Ubuntu` runners like `ubuntu-20.04-64core`. In these cases the job will be omitted entirely and cannot be tested +- Testing more complex workflows in their entirety can be extremely time-consuming and cumbersome. It is often optimal to mock most of the steps with expressions printing the input and output conditions +- Due to the way `Act` and `Act-js` handle workflow output, not much can be checked in the test. What is available, namely whether the job/step executed or not, whether it was successful or not and what its printed output was, should be enough in most scenarios +- `Act` does not seem to support the conditions set on event parameters when determining whether to run the workflow or not, namely for a workflow defined with: +```yaml +on: + pull_request: + types: [opened, edited, reopened] +``` +running `act pull_request -e event_data.json` with `event_data.json` having `{"action": "opened"}` will execute the workflow (as expected), running for example `act push` will not execute it (as expected), but running `act pull_request -e event_data.json` with `event_data.json` having for example `{"action": "assigned"}` **will still execute the workflow** even though it should only be executed with `action` being `opened`, `edited` or `reopened`. This only applies to running the workflow with `Act`, in the GitHub environment it still works as expected + +## File structure +The testing framework file structure within the repository is as follows: +- `App/` - main application folder + - `.github/` - GitHub Actions folder + - `workflows/` - workflows folder + - `.yml` - workflow file + - `...` - other workflow files + - `...` - other GitHub Actions files + - `workflow_tests/` - workflow testing folder + - `jest.config.ts` - `Jest` configuration file + - `README.md` - this readme file + - `utils.js` - various utility functions used in multiple tests + - `.test.js` - test suite file for a GitHub Actions workflow named `` + - `mocks/` - folder with step mock definitions + - `Mocks.js` - file with step mock definitions for the `../.test.js` suite, or for the `` workflow + - `...` - other step mock definition files + - `assertions/` - folder with output assertions + - `Assertions.js` - file with output assertions for the `../.test.js` suite, or for the `` workflow + - `...` - other output assertion files + - `...` - other test suites + - `...` - other application files + +## Utility helpers +`utils.js` file provides several helper methods to speed up the tests development and maintenance + +### `setUpActParams` +`setUpActParams` allows for initiating the context in which Act will execute the workflow + +Parameters: +- `act` - instance of previously created `Act` object that will be updated with new params +- `event` - the name of the event, this can be any event name used by GitHub Actions, like `pull_request`, `push`, `workflow_dispatch`, etc. +- `event_options` - object with options of the event, allowing for customising it for different scenarios, for example `push` event can be customised for pushing to different branches with options `{head: {ref: ''}}` +- `secrets` - object with secret values provided, like `{: , ...}` +- `github_token` - value of the GitHub token, analogous to providing `GITHUB_TOKEN` secret + +Returns an updated `Act` object instance + +Example: +```javascript +let act = new kieActJs.Act(repoPath, workflowPath); +act = utils.setUpActParams( + act, + 'push', + {head: {ref: 'main'}}, + {OS_BOTIFY_TOKEN: 'dummy_token', GITHUB_ACTOR: 'Dummy Tester', SLACK_WEBHOOK: 'dummy_slack_webhook', LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_s3cr3t_p455word'}, + 'dummy_github_token', +); +``` + +### `getMockStep` +`getMockStep` allows for creating uniform mock step definitions compatible with `Act-js` and reduces time required, as well as possibility of errors/typos slipping in while developing tests. More complex behaviours have to be mocked manually + +Parameters: +- `name` - name of the step that **must correspond to the `name` in the `.yml` file**, otherwise the step cannot be found +- `message` - the message to be printed to default output when mock gets executed +- `job_id` - an optional id of the job that will be printed in `[]` square brackets along the `message`, useful when assessing the output with many steps from many jobs +- `inputs` - a list of input parameters to be printed, useful when checking if the step had been executed with expected inputs +- `in_envs` - a list of input environment variables, to be printed, useful when checking if the step had been executed in expected environment +- `outputs` - an object with values which should be printed by the mock to `$GITHUB_OUTPUT`, useful for setting the step output +- `out_envs` - an objects with values of environment variables set by the step in `$GITHUB_ENV`, useful for modifying the environment by the mock +- `isSuccessful` - a boolean value indicating whether the step succeeds or not, exits with status `0` (if successful) or `1` (if not) + +Returns an object with step mock definition, ready to be provided to the `Act` object instance + +Example: +```javascript +let mockStep = utils.getMockStep( + 'Name of the step from .yml', + 'Message to be printed', + 'TEST_JOB', + ['INPUT_1', 'INPUT_2'], + ['ENV_1', 'ENV_2'], + {output_1: true, output_2: 'Some Result'}, + {OUT_ENV: 'ENV_VALUE'}, + false, +); +``` +results in +```javascript +{ + name: 'Name of the step from .yml', + mockWith: 'echo [MOCK]' + + ' [TEST_JOB]' + + ' Message to be printed' + + ', INPUT_1="{{ inputs.INPUT_1 }}' + + ', INPUT_2="{{ inputs.INPUT_2 }}' + + ', ENV_1="{{ env.ENV_1 }}' + + ', ENV_1="{{ env.ENV_1 }}' + + '\necho "output_1=true" >> "$GITHUB_OUTPUT"', + + '\necho "output_2=Some Result" >> "$GITHUB_OUTPUT"', + + '\necho "OUT_ENV=ENV_VALUE" >> "$GITHUB_ENV"', + + '\nexit 1', +} +``` + +### `getStepAssertion` +`getStepAssertion` allows for creating uniform assertions for output from executed step, compatible with step mocks provided by `getMockStep` + +Parameters: +- `name` - name of the step, **has to correspond to the name from `.yml` file**, and the name in the step mock if applicable +- `isSuccessful` - boolean value for checking if the step should have exited successfully +- `expectedOutput` - an output that is expected from the step, compared directly - if provided the subsequent parameters are ignored +- `jobId` - an optional expected job identifier +- `message` - expected message printed by the step +- `inputs` - expected input values provided to the step +- `envs` - expected input environment variables for the step + +Returns an object with step expected output definition ready to be provided to `expect()` matcher + +Example: +```javascript +utils.getStepAssertion( + 'Name of the step from .yml', + false, + null, + 'TEST_JOB', + 'Message to be printed', + [{key: 'INPUT_1', value: true}, {key: 'INPUT_2', value: 'Some value'}], + [{key: 'PLAIN_ENV_VAR', value: 'Value'}, {key: 'SECRET_ENV_VAR', value: '***'}], +) +``` +results in +```javascript +{ + name: 'Name of the step from .yml', + status: 1, + output: '[MOCK]' + + ' [TEST_JOB]' + + ' Message to be printed' + + ', INPUT_1=true' + + ', INPUT_2=Some value' + + ', PLAIN_ENV_VAR=Value' + + ', SECRET_ENV_VAR=***', +} +``` + +### `setJobRunners` +`setJobRunners` overwrites the runner types for given jobs, helpful when the runner type in the workflow is not supported by `Act` + +Parameters: +- `act` - instance of previously created `Act` object +- `jobs` - object with keys being the IDs of the workflow jobs to be modified and values being the names of runners that should be used for them in the test +- `workflowPath` - path to the workflow file to be updated, **NOTE**: this will modify the file, use the one from the local test repo, not from `App/.github/workflows`! + +Returns an `Act` object instance + +Let's say you have a workflow with a job using `macos-12` runner, which is unsupported by `Act` - in this case that job will simply be skipped altogether, not allowing you to test it in any way. +```yaml +iOS: + name: Build and deploy iOS + needs: validateActor + if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} + runs-on: macos-12 + steps: +``` +You can use this method to change the runner to something that is supported, like +```javascript +act = utils.setJobRunners( + act, + { + iOS: 'ubuntu-latest', + }, + workflowPath, +); +``` +Now the test workflow will look as follows, which will allow you to run the job and do at least limited testing +```yaml +iOS: + name: Build and deploy iOS + needs: validateActor + if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} + runs-on: ubuntu-latest + steps: +``` + +## Typical test file +The following is the typical test file content, which will be followed by a detailed breakdown +```javascript +const path = require('path'); +const kieActJs = require('@kie/act-js'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils'); +const assertions = require('./assertions/Assertions'); +const mocks = require('./mocks/Mocks'); + +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + { + src: path.resolve(__dirname, '..', '.github', 'actions'), + dest: '.github/actions', + }, + { + src: path.resolve(__dirname, '..', '.github', 'libs'), + dest: '.github/libs', + }, + { + src: path.resolve(__dirname, '..', '.github', 'scripts'), + dest: '.github/scripts', + }, + { + src: path.resolve(__dirname, '..', '.github', 'workflows', '.yml'), + dest: '.github/workflows/.yml', + }, +]; + +beforeEach(async () => { + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testWorkflowsRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); +}); + +afterEach(async () => { + await mockGithub.teardown(); +}); + +describe('test some general behaviour', () => { + test('something happens - test if expected happened next', async () => { + // get path to the local test repo + const repoPath = mockGithub.repo.getPath('testWorkflowsRepo') || ''; + + // get path to the workflow file under test + const workflowPath = path.join(repoPath, '.github', 'workflows', '.yml'); + + // instantiate Act in the context of the test repo and given workflow file + let act = new kieActJs.Act(repoPath, workflowPath); + + // set run parameters + act = utils.setUpActParams( + act, + '', + {head: {ref: ''}}, + {'': '', + ); + + // set up mocks + const testMockSteps = { + '': [ + { + name: '', + mockWith: '', + }, + { + name: '', + mockWith: '', + }, + ], + '': [ + utils.getMockStep('', ''), + utils.getMockStep('', ''), + ], + }; + + // run an event and get the result + const result = await act + .runEvent('', { + workflowFile: path.join(repoPath, '.github', 'workflows'), + mockSteps: testMockSteps, + }); + + // assert results (some steps can run in parallel to each other so the order is not assured + // therefore we can check which steps have been executed, but not the set job order + assertions.assertSomethingHappened(result); + assertions.assertSomethingDidNotHappen(result, false); + }, timeout); +); +``` + +### Breakdown +Define which files should be copied into the test repo. In this case we copy `actions`, `libs`, `scripts` folders in their entirety and just the one workflow file we want to test +```javascript +const FILES_TO_COPY_INTO_TEST_REPO = [ + { + src: path.resolve(__dirname, '..', '.github', 'actions'), + dest: '.github/actions', + }, + { + src: path.resolve(__dirname, '..', '.github', 'libs'), + dest: '.github/libs', + }, + { + src: path.resolve(__dirname, '..', '.github', 'scripts'), + dest: '.github/scripts', + }, + { + src: path.resolve(__dirname, '..', '.github', 'workflows', '.yml'), + dest: '.github/workflows/.yml', + }, +]; +``` +`beforeEach` gets executed before each test. Here we create the local test repository with the files defined in the `FILES_TO_COPY_INTO_TEST_REPO` variable. `testWorkflowRepo` is the name of the test repo and can be changed to whichever name you choose, just remember to use it later when accessing this repo. _Note that we can't use `beforeAll()` method, because while mocking steps `Act-js` modifies the workflow file copied into the test repo and thus mocking could persist between tests_ +```javascript +beforeEach(async () => { + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testWorkflowsRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); +}); +``` +Similarly, `afterEach` gets executed after each test. In this case we remove the test repo after the test finishes +```javascript +afterEach(async () => { + await mockGithub.teardown(); +}); +``` +Get path to the local test repo, useful to have it in a variable +```javascript +const repoPath = mockGithub.repo.getPath('testWorkflowsRepo') || ''; +``` +Get path to the workflow under test. Note that it's the path **in the test repo** +```javascript +const workflowPath = path.join(repoPath, '.github', 'workflows', '.yml'); +``` +Instantiate `Act` object instance. Here we provide the constructor with the path to the test repo (so that `Act` can execute in its context) and the path the workflow file under test (so just the workflow we want to test would be executed) +```javascript +let act = new kieActJs.Act(repoPath, workflowPath); +``` +Set up initial parameters for `Act`. This is where we can set secrets, GitHub token and options for the events (like the name of the branch to which the push has been made, etc.) +```javascript +act = utils.setUpActParams( + act, + '', + {head: {ref: ''}}, + {'': '', +); +``` +Set up step mocks. Here we configure which steps in the workflow should be mocked, and with what behaviour. This takes form of an object with keys corresponding to the names of the jobs in the workflow, and values being mock definitions for specific steps. The steps can be identified either by `id`, `name`, `uses` or `run`. Step mock can be defined either by hand (``) or with the helper method `utils.getMockStep()` (``). Not mocked steps will be executed normally - **make sure this will not have unexpected consequences** +```javascript +const testMockSteps = { + '': [ + { + name: '', + mockWith: '', + }, + { + name: '', + mockWith: '', + }, + ], + '': [ + utils.getMockStep('', ''), + utils.getMockStep('', ''), + ], +}; +``` +Most important part - actually running the event with `Act`. This executes the specified `` in the context of the local test repo created before and with the workflow under test set up. `result` stores the output of `Act` execution, which can then be compared to what was expected. Note that the `workflowFile` is actually path to _workflow folder_ and not the file itself - `Act-js` determines the name of the workflow by itself, and tries to find it in the specified `workflowFile` path, so _providing the full path to the file will fail_ +```javascript +const result = await act + .runEvent('', { + workflowFile: path.join(repoPath, '.github', 'workflows'), + mockSteps: testMockSteps, + }); +``` +Assert results are as expected. This can, for example, include using `expect()` to check if the steps that should be executed have indeed been executed, steps that shouldn't run have not been executed, compare statuses (which steps succeeded, which failed) and step outputs. Outputs can include additional information, like input values, environmental variables, secrets (although these are usually not accessible and represented by `***`, this can still be useful to check if the value exists or not). Here it's usually done with the helper assertion methods defined in the assertions file. Step assertions can be created manually or with `getStepAssertion()` helper method +```javascript +assertions.assertSomethingHappened(result); +assertions.assertSomethingDidNotHappen(result, false); +``` + +## FAQ +### I'm positive that one of the jobs should run, but it doesn't - why? +#### Check the runner type (`runs-on`) it may not be set (which `Act` does not like) or it may be set to one of the unsupported types (primarily the `macos-...` runner types). You can always overwrite the runner type with `utils.setJobRunners()` helper method +### My workflow has many jobs, each with many steps, how do I start testing it without spending hours on setup? +#### First of all, consider splitting the workflow into several smaller pieces, with the main one acting as coordinator and calling the others. Secondly, you can bootstrap the test with `npm run workflow-test:generate .yml`, which will generate mocks and assertions for you, as well as the stub of the test file +### After using `workflow-test:generate` the files are incomplete, or they have errors. Why? +#### Make sure that the workflow file you want to test, has all steps with names, as the bootstrapping script uses step names to locate and mock them - same with assertions. After you've added the `name` properties to steps, remove the previously generated files and run the command again +### I want to just run the test that I am working on, without all the others - how can I do it? +#### You can pass parameters to the `npm run workflow-test` command as you would with `jest` or `npm test` - `npm run workflow-test -- -i ` will run just the tests within `testfile`. You can also filter further with `-t ` diff --git a/workflow_tests/assertions/authorChecklistAssertions.js b/workflow_tests/assertions/authorChecklistAssertions.js new file mode 100644 index 000000000000..c57fe922c754 --- /dev/null +++ b/workflow_tests/assertions/authorChecklistAssertions.js @@ -0,0 +1,17 @@ +const utils = require('../utils/utils'); + +const assertChecklistJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('authorChecklist.js', true, null, 'CHECKLIST', 'Running authorChecklist.js', [{key: 'GITHUB_TOKEN', value: '***'}], [])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertChecklistJobExecuted, +}; diff --git a/workflow_tests/assertions/cherryPickAssertions.js b/workflow_tests/assertions/cherryPickAssertions.js new file mode 100644 index 000000000000..42ecc3d64262 --- /dev/null +++ b/workflow_tests/assertions/cherryPickAssertions.js @@ -0,0 +1,119 @@ +const utils = require('../utils/utils'); + +const assertValidateActorJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Check if user is deployer', true, null, 'VALIDATEACTOR', 'Checking if user is a deployer', [], [{key: 'GITHUB_TOKEN', value: '***'}])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertCreateNewVersionJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Create new version', true, null, 'CREATENEWVERSION', 'Creating new version', [], [])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertCherryPickJobExecuted = (workflowResult, user = 'Dummy Author', pullRequestNumber = '1234', didExecute = true, hasConflicts = false, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion( + 'Checkout staging branch', + true, + null, + 'CHERRYPICK', + 'Checking out staging branch', + [ + {key: 'ref', value: 'staging'}, + {key: 'token', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Set up git for OSBotify', true, null, 'CHERRYPICK', 'Setting up git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}], []), + utils.createStepAssertion('Get previous app version', true, null, 'CHERRYPICK', 'Get previous app version', [{key: 'SEMVER_LEVEL', value: 'PATCH'}]), + utils.createStepAssertion('Fetch history of relevant refs', true, null, 'CHERRYPICK', 'Fetch history of relevant refs'), + utils.createStepAssertion('Get version bump commit', true, null, 'CHERRYPICK', 'Get version bump commit', [], []), + utils.createStepAssertion( + 'Get merge commit for pull request to CP', + true, + null, + 'CHERRYPICK', + 'Get merge commit for pull request to CP', + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'USER', value: user}, + {key: 'PULL_REQUEST_NUMBER', value: pullRequestNumber}, + ], + [], + ), + utils.createStepAssertion('Cherry-pick the version-bump to staging', true, null, 'CHERRYPICK', 'Cherry-picking the version-bump to staging', [], []), + utils.createStepAssertion('Cherry-pick the merge commit of target PR', true, null, 'CHERRYPICK', 'Cherry-picking the merge commit of target PR', [], []), + utils.createStepAssertion('Push changes', true, null, 'CHERRYPICK', 'Pushing changes', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const conflictSteps = [ + utils.createStepAssertion( + 'Create Pull Request to manually finish CP', + true, + null, + 'CHERRYPICK', + 'Creating Pull Request to manually finish CP', + [], + [{key: 'GITHUB_TOKEN', value: '***'}], + ), + ]; + + conflictSteps.forEach((step) => { + if (didExecute && hasConflicts) { + expect(workflowResult).toEqual(expect.arrayContaining([step])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([step])); + } + }); + + const failedSteps = [ + utils.createStepAssertion( + 'Announces a CP failure in the #announce Slack room', + true, + null, + 'CHERRYPICK', + 'Announcing a CP failure', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + ]; + + failedSteps.forEach((step) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([step])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([step])); + } + }); +}; + +module.exports = { + assertValidateActorJobExecuted, + assertCreateNewVersionJobExecuted, + assertCherryPickJobExecuted, +}; diff --git a/workflow_tests/assertions/claAssertions.js b/workflow_tests/assertions/claAssertions.js new file mode 100644 index 000000000000..b85eb263d838 --- /dev/null +++ b/workflow_tests/assertions/claAssertions.js @@ -0,0 +1,73 @@ +const utils = require('../utils/utils'); + +const assertCLAJobExecuted = (workflowResult, commentBody = '', githubRepository = '', didExecute = true, runAssistant = true) => { + const steps = [ + utils.createStepAssertion( + 'CLA comment check', + true, + null, + 'CLA', + 'CLA comment check', + [ + {key: 'text', value: commentBody}, + {key: 'regex', value: '\\s*I have read the CLA Document and I hereby sign the CLA\\s*'}, + ], + [], + ), + utils.createStepAssertion( + 'CLA comment re-check', + true, + null, + 'CLA', + 'CLA comment re-check', + [ + {key: 'text', value: commentBody}, + {key: 'regex', value: '\\s*recheck\\s*'}, + ], + [], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const assistantSteps = [ + utils.createStepAssertion( + 'CLA Assistant', + true, + null, + 'CLA', + 'CLA Assistant', + [ + {key: 'path-to-signatures', value: `${githubRepository}/cla.json`}, + {key: 'path-to-document', value: `https://github.com/${githubRepository}/blob/main/contributingGuides/CLA.md`}, + {key: 'branch', value: 'main'}, + {key: 'remote-organization-name', value: 'Expensify'}, + {key: 'remote-repository-name', value: 'CLA'}, + {key: 'lock-pullrequest-aftermerge', value: false}, + {key: 'allowlist', value: 'OSBotify,snyk-bot'}, + ], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'PERSONAL_ACCESS_TOKEN', value: '***'}, + ], + ), + ]; + + assistantSteps.forEach((step) => { + if (didExecute && runAssistant) { + expect(workflowResult).toEqual(expect.arrayContaining([step])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([step])); + } + }); +}; + +module.exports = { + assertCLAJobExecuted, +}; diff --git a/workflow_tests/assertions/createNewVersionAssertions.js b/workflow_tests/assertions/createNewVersionAssertions.js new file mode 100644 index 000000000000..e4526ae59be2 --- /dev/null +++ b/workflow_tests/assertions/createNewVersionAssertions.js @@ -0,0 +1,72 @@ +const utils = require('../utils/utils'); + +const assertValidateActorJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Get user permissions', true, null, 'VALIDATEACTOR', 'Get user permissions', [], [{key: 'GITHUB_TOKEN', value: '***'}])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertCreateNewVersionJobExecuted = (workflowResult, semverLevel = 'BUILD', didExecute = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion('Run turnstyle', true, null, 'CREATENEWVERSION', 'Run turnstyle', [{key: 'poll-interval-seconds', value: '10'}], [{key: 'GITHUB_TOKEN', value: '***'}]), + utils.createStepAssertion( + 'Check out', + true, + null, + 'CREATENEWVERSION', + 'Check out', + [ + {key: 'ref', value: 'main'}, + {key: 'token', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Setup git for OSBotify', true, null, 'CREATENEWVERSION', 'Setup git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}], []), + utils.createStepAssertion( + 'Generate version', + true, + null, + 'CREATENEWVERSION', + 'Generate version', + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SEMVER_LEVEL', value: semverLevel}, + ], + [], + ), + utils.createStepAssertion('Commit new version', true, null, 'CREATENEWVERSION', 'Commit new version', [], []), + utils.createStepAssertion('Update main branch', true, null, 'CREATENEWVERSION', 'Update main branch', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + if (isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failedSteps = [ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'CREATENEWVERSION', 'Announce failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + failedSteps.forEach((step) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([step])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([step])); + } + }); +}; + +module.exports = { + assertValidateActorJobExecuted, + assertCreateNewVersionJobExecuted, +}; diff --git a/workflow_tests/assertions/deployAssertions.js b/workflow_tests/assertions/deployAssertions.js new file mode 100644 index 000000000000..bff99298bde5 --- /dev/null +++ b/workflow_tests/assertions/deployAssertions.js @@ -0,0 +1,63 @@ +const utils = require('../utils/utils'); + +const assertDeployStagingJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout staging branch', true, null, 'DEPLOY_STAGING', 'Checking out staging branch', [ + {key: 'ref', value: 'staging'}, + {key: 'token', value: '***'}, + ]), + utils.createStepAssertion('Setup git for OSBotify', true, null, 'DEPLOY_STAGING', 'Setting up git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Tag version', true, null, 'DEPLOY_STAGING', 'Tagging new version'), + utils.createStepAssertion('🚀 Push tags to trigger staging deploy 🚀', true, null, 'DEPLOY_STAGING', 'Pushing tag to trigger staging deploy'), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertDeployProductionJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'DEPLOY_PRODUCTION', 'Checking out', [ + {key: 'ref', value: 'production'}, + {key: 'token', value: '***'}, + ]), + utils.createStepAssertion('Setup git for OSBotify', true, null, 'DEPLOY_PRODUCTION', 'Setting up git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Get current app version', true, null, 'DEPLOY_PRODUCTION', 'Getting current app version'), + utils.createStepAssertion('Get Release Pull Request List', true, null, 'DEPLOY_PRODUCTION', 'Getting release PR list', [ + {key: 'TAG', value: '1.2.3'}, + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'IS_PRODUCTION_DEPLOY', value: 'true'}, + ]), + utils.createStepAssertion('Generate Release Body', true, null, 'DEPLOY_PRODUCTION', 'Generating release body', [{key: 'PR_LIST', value: '[1.2.1, 1.2.2]'}]), + utils.createStepAssertion( + '🚀 Create release to trigger production deploy 🚀', + true, + null, + 'DEPLOY_PRODUCTION', + 'Creating release to trigger production deploy', + [ + {key: 'tag_name', value: '1.2.3'}, + {key: 'body', value: 'Release body'}, + ], + [{key: 'GITHUB_TOKEN', value: '***'}], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertDeployStagingJobExecuted, + assertDeployProductionJobExecuted, +}; diff --git a/workflow_tests/assertions/deployBlockerAssertions.js b/workflow_tests/assertions/deployBlockerAssertions.js new file mode 100644 index 000000000000..8d2d6039960e --- /dev/null +++ b/workflow_tests/assertions/deployBlockerAssertions.js @@ -0,0 +1,98 @@ +const utils = require('../utils/utils'); + +const assertDeployBlockerJobExecuted = (workflowResult, issueTitle, issueNumber, didExecute = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'DEPLOYBLOCKER', 'Checkout', [{key: 'token', value: '***'}], []), + utils.createStepAssertion( + 'Get URL, title, & number of new deploy blocker (issue)', + true, + null, + 'DEPLOYBLOCKER', + 'Get URL, title and number of new deploy blocker - issue', + [], + [{key: 'TITLE', value: issueTitle}], + ), + utils.createStepAssertion( + 'Update StagingDeployCash with new deploy blocker', + true, + null, + 'DEPLOYBLOCKER', + 'Update StagingDeployCash with new deploy blocker', + [{key: 'GITHUB_TOKEN', value: '***'}], + [], + ), + utils.createStepAssertion( + 'Give the issue/PR the Hourly, Engineering labels', + true, + null, + 'DEPLOYBLOCKER', + 'Give the issue/PR the Hourly, Engineering labels', + [ + {key: 'add-labels', value: 'Hourly, Engineering'}, + {key: 'remove-labels', value: 'Daily, Weekly, Monthly'}, + ], + [], + ), + utils.createStepAssertion( + 'Comment on deferred PR', + true, + null, + 'DEPLOYBLOCKER', + 'Comment on deferred PR', + [ + {key: 'github_token', value: '***'}, + {key: 'number', value: issueNumber}, + ], + [], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + if (isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const successSteps = [ + utils.createStepAssertion( + 'Post the issue in the #expensify-open-source slack room', + true, + null, + 'DEPLOYBLOCKER', + 'Post the issue in the expensify-open-source slack room', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + ]; + + successSteps.forEach((step) => { + if (didExecute && isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([step])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([step])); + } + }); + + const failedSteps = [ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'DEPLOYBLOCKER', 'Announce failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + failedSteps.forEach((step) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([step])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([step])); + } + }); +}; + +module.exports = { + assertDeployBlockerJobExecuted, +}; diff --git a/workflow_tests/assertions/finishReleaseCycleAssertions.js b/workflow_tests/assertions/finishReleaseCycleAssertions.js new file mode 100644 index 000000000000..12a8b9f2b05d --- /dev/null +++ b/workflow_tests/assertions/finishReleaseCycleAssertions.js @@ -0,0 +1,186 @@ +const utils = require('../utils/utils'); + +const assertValidateJobExecuted = (workflowResult, issueNumber = '', didExecute = true, isTeamMember = true, hasBlockers = false, isSuccessful = true) => { + const steps = [utils.createStepAssertion('Validate actor is deployer', true, null, 'VALIDATE', 'Validating if actor is deployer', [], [{key: 'GITHUB_TOKEN', value: '***'}])]; + if (isTeamMember) { + steps.push( + utils.createStepAssertion( + 'Check for any deploy blockers', + true, + null, + 'VALIDATE', + 'Checking for deploy blockers', + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'ISSUE_NUMBER', value: issueNumber}, + ], + [], + ), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + // eslint-disable-next-line rulesdir/no-negated-variables + const notTeamMemberSteps = [ + utils.createStepAssertion( + 'Reopen and comment on issue (not a team member)', + true, + null, + 'VALIDATE', + 'Reopening issue - not a team member', + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'ISSUE_NUMBER', value: issueNumber}, + {key: 'COMMENT', value: 'Sorry, only members of @Expensify/Mobile-Deployers can close deploy checklists.\nReopening!'}, + ], + [], + ), + ]; + + notTeamMemberSteps.forEach((expectedStep) => { + if (didExecute && !isTeamMember) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const blockerSteps = [ + utils.createStepAssertion( + 'Reopen and comment on issue (has blockers)', + true, + null, + 'VALIDATE', + 'Reopening issue - blockers', + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'ISSUE_NUMBER', value: issueNumber}, + ], + [], + ), + ]; + + blockerSteps.forEach((expectedStep) => { + if (didExecute && hasBlockers) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failedSteps = [ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'VALIDATE', 'Announce failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + failedSteps.forEach((expectedStep) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertUpdateProductionJobExecuted = (workflowResult, didExecute = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion( + 'Checkout', + true, + null, + 'UPDATEPRODUCTION', + 'Checkout', + [ + {key: 'ref', value: 'staging'}, + {key: 'token', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Setup Git for OSBotify', true, null, 'UPDATEPRODUCTION', 'Setup Git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}], []), + utils.createStepAssertion('Update production branch', true, null, 'UPDATEPRODUCTION', 'Updating production branch', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failedSteps = [ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'UPDATEPRODUCTION', 'Announce failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + failedSteps.forEach((expectedStep) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertCreateNewPatchVersionJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Create new version', true, null, 'CREATENEWPATCHVERSION', 'Creating new version', [{key: 'SEMVER_LEVEL', value: 'PATCH'}], [])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertUpdateStagingJobExecuted = (workflowResult, didExecute = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion( + 'Checkout', + true, + null, + 'UPDATESTAGING', + 'Checkout', + [ + {key: 'ref', value: 'main'}, + {key: 'token', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Setup Git for OSBotify', true, null, 'UPDATESTAGING', 'Setup Git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}], []), + utils.createStepAssertion('Update staging branch to trigger staging deploy', true, null, 'UPDATESTAGING', 'Updating staging branch', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failedSteps = [ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'UPDATESTAGING', 'Announce failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + failedSteps.forEach((expectedStep) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertValidateJobExecuted, + assertUpdateProductionJobExecuted, + assertCreateNewPatchVersionJobExecuted, + assertUpdateStagingJobExecuted, +}; diff --git a/workflow_tests/assertions/lintAssertions.js b/workflow_tests/assertions/lintAssertions.js new file mode 100644 index 000000000000..938f9b383464 --- /dev/null +++ b/workflow_tests/assertions/lintAssertions.js @@ -0,0 +1,22 @@ +const utils = require('../utils/utils'); + +const assertLintJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'LINT', 'Checkout', [], []), + utils.createStepAssertion('Setup Node', true, null, 'LINT', 'Setup Node', [], []), + utils.createStepAssertion('Lint JavaScript and Typescript with ESLint', true, null, 'LINT', 'Lint JavaScript with ESLint', [], [{key: 'CI', value: 'true'}]), + utils.createStepAssertion('Lint shell scripts with ShellCheck', true, null, 'LINT', 'Lint shell scripts with ShellCheck', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertLintJobExecuted, +}; diff --git a/workflow_tests/assertions/lockDeploysAssertions.js b/workflow_tests/assertions/lockDeploysAssertions.js new file mode 100644 index 000000000000..874076dc3a28 --- /dev/null +++ b/workflow_tests/assertions/lockDeploysAssertions.js @@ -0,0 +1,84 @@ +const utils = require('../utils/utils'); + +const assertlockStagingDeploysJobExecuted = (workflowResult, didExecute = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion( + 'Checkout', + true, + null, + 'LOCKSTAGINGDEPLOYS', + 'Checking out', + [ + {key: 'ref', value: 'main'}, + {key: 'token', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Wait for staging deploys to finish', true, null, 'LOCKSTAGINGDEPLOYS', 'Waiting for staging deploys to finish', [{key: 'GITHUB_TOKEN', value: '***'}], []), + utils.createStepAssertion( + 'Comment in StagingDeployCash to give Applause the 🟢 to begin QA', + true, + null, + 'LOCKSTAGINGDEPLOYS', + 'Commenting in StagingDeployCash', + [], + [{key: 'GITHUB_TOKEN', value: '***'}], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failProdSteps = [ + utils.createStepAssertion('Announce failed workflow', true, null, 'LOCKSTAGINGDEPLOYS', 'Announcing failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + failProdSteps.forEach((expectedStep) => { + if (didExecute && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertlockStagingDeploysJobFailedAfterFirstStep = (workflowResult) => { + const steps = [ + utils.createStepAssertion( + 'Checkout', + true, + null, + 'LOCKSTAGINGDEPLOYS', + 'Checking out', + [ + {key: 'ref', value: 'main'}, + {key: 'token', value: '***'}, + ], + [], + ), + utils.createStepAssertion( + 'Wait for staging deploys to finish', + false, + null, + 'LOCKSTAGINGDEPLOYS', + 'Waiting for staging deploys to finish', + [{key: 'GITHUB_TOKEN', value: '***'}], + [], + ), + utils.createStepAssertion('Announce failed workflow', true, null, 'LOCKSTAGINGDEPLOYS', 'Announcing failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}], []), + ]; + + steps.forEach((expectedStep) => { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + }); +}; + +module.exports = { + assertlockStagingDeploysJobExecuted, + assertlockStagingDeploysJobFailedAfterFirstStep, +}; diff --git a/workflow_tests/assertions/platformDeployAssertions.js b/workflow_tests/assertions/platformDeployAssertions.js new file mode 100644 index 000000000000..35242cd24d31 --- /dev/null +++ b/workflow_tests/assertions/platformDeployAssertions.js @@ -0,0 +1,371 @@ +const utils = require('../utils/utils'); + +const assertVerifyActorJobExecuted = (workflowResult, username, didExecute = true) => { + const steps = [utils.createStepAssertion('Check if user is deployer', true, null, 'VALIDATE_ACTOR', 'Checking if the user is a deployer', [], [{key: 'GITHUB_TOKEN', value: '***'}])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertDeployChecklistJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'DEPLOY_CHECKLIST', 'Checkout'), + utils.createStepAssertion('Setup Node', true, null, 'DEPLOY_CHECKLIST', 'Setup Node'), + utils.createStepAssertion('Set version', true, null, 'DEPLOY_CHECKLIST', 'Set version'), + utils.createStepAssertion('Create or update staging deploy', true, null, 'DEPLOY_CHECKLIST', 'Create or update staging deploy', [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'NPM_VERSION', value: '1.2.3'}, + ]), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertAndroidJobExecuted = (workflowResult, didExecute = true, isProduction = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'ANDROID', 'Checking out'), + utils.createStepAssertion('Configure MapBox SDK', true, null, 'ANDROID', 'Configure MapBox SDK'), + utils.createStepAssertion('Setup Node', true, null, 'ANDROID', 'Setting up Node'), + utils.createStepAssertion('Setup Ruby', true, null, 'ANDROID', 'Setting up Ruby', [ + {key: 'ruby-version', value: '2.7'}, + {key: 'bundler-cache', value: 'true'}, + ]), + utils.createStepAssertion('Decrypt keystore', true, null, 'ANDROID', 'Decrypting keystore', null, [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Decrypt json key', true, null, 'ANDROID', 'Decrypting JSON key', null, [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Set version in ENV', true, null, 'ANDROID', 'Setting version in ENV'), + ]; + if (!isProduction) { + steps.push( + utils.createStepAssertion('Run Fastlane beta', true, null, 'ANDROID', 'Running Fastlane beta', null, [ + {key: 'MYAPP_UPLOAD_STORE_PASSWORD', value: '***'}, + {key: 'MYAPP_UPLOAD_KEY_PASSWORD', value: '***'}, + ]), + ); + } else { + steps.push(utils.createStepAssertion('Run Fastlane production', true, null, 'ANDROID', 'Running Fastlane production', null, [{key: 'VERSION', value: '1.2.3'}])); + } + steps.push( + utils.createStepAssertion('Archive Android sourcemaps', true, null, 'ANDROID', 'Archiving Android sourcemaps', [ + {key: 'name', value: 'android-sourcemap'}, + {key: 'path', value: 'android/app/build/generated/sourcemaps/react/release/*.map'}, + ]), + ); + if (!isProduction) { + steps.push( + utils.createStepAssertion('Upload Android version to Browser Stack', true, null, 'ANDROID', 'Uploading Android version to Browser Stack', null, [ + {key: 'BROWSERSTACK', value: '***'}, + ]), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failProdSteps = [ + utils.createStepAssertion( + 'Warn deployers if Android production deploy failed', + true, + null, + 'ANDROID', + 'Warning deployers of failed production deploy', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + ]; + + failProdSteps.forEach((expectedStep) => { + if (didExecute && isProduction && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertDesktopJobExecuted = (workflowResult, didExecute = true, isProduction = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'DESKTOP', 'Checking out'), + utils.createStepAssertion('Setup Node', true, null, 'DESKTOP', 'Setting up Node'), + utils.createStepAssertion('Decrypt Developer ID Certificate', true, null, 'DESKTOP', 'Decrypting developer id certificate', null, [ + {key: 'DEVELOPER_ID_SECRET_PASSPHRASE', value: '***'}, + ]), + ]; + if (isProduction) { + steps.push( + utils.createStepAssertion('Build production desktop app', true, null, 'DESKTOP', 'Building production desktop app', null, [ + {key: 'CSC_LINK', value: '***'}, + {key: 'CSC_KEY_PASSWORD', value: '***'}, + {key: 'APPLE_ID', value: '***'}, + {key: 'APPLE_APP_SPECIFIC_PASSWORD', value: '***'}, + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ]), + ); + } else { + steps.push( + utils.createStepAssertion('Build staging desktop app', true, null, 'DESKTOP', 'Building staging desktop app', null, [ + {key: 'CSC_LINK', value: '***'}, + {key: 'CSC_KEY_PASSWORD', value: '***'}, + {key: 'APPLE_ID', value: '***'}, + {key: 'APPLE_APP_SPECIFIC_PASSWORD', value: '***'}, + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ]), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertIOSJobExecuted = (workflowResult, didExecute = true, isProduction = true, isSuccessful = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'IOS', 'Checking out'), + utils.createStepAssertion('Configure MapBox SDK', true, null, 'IOS', 'Configure MapBox SDK'), + utils.createStepAssertion('Setup Node', true, null, 'IOS', 'Setting up Node'), + utils.createStepAssertion('Setup Ruby', true, null, 'IOS', 'Setting up Ruby', [ + {key: 'ruby-version', value: '2.7'}, + {key: 'bundler-cache', value: 'true'}, + ]), + utils.createStepAssertion('Cache Pod dependencies', true, null, 'IOS', 'Cache Pod dependencies', [ + {key: 'path', value: 'ios/Pods'}, + {key: 'key', value: 'Linux-pods-cache-'}, + {key: 'restore-keys', value: 'Linux-pods-cache-'}, + ]), + utils.createStepAssertion('Compare Podfile.lock and Manifest.lock', true, null, 'IOS', 'Compare Podfile.lock and Manifest.lock'), + utils.createStepAssertion('Install cocoapods', true, null, 'IOS', 'Installing cocoapods', [ + {key: 'timeout_minutes', value: '10'}, + {key: 'max_attempts', value: '5'}, + {key: 'command', value: 'cd ios && bundle exec pod install'}, + ]), + utils.createStepAssertion('Decrypt profile', true, null, 'IOS', 'Decrypting profile', null, [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Decrypt certificate', true, null, 'IOS', 'Decrypting certificate', null, [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Decrypt App Store Connect API key', true, null, 'IOS', 'Decrypting App Store API key', null, [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + ]; + if (!isProduction) { + steps.push( + utils.createStepAssertion('Run Fastlane', true, null, 'IOS', 'Running Fastlane', null, [ + {key: 'APPLE_CONTACT_EMAIL', value: '***'}, + {key: 'APPLE_CONTACT_PHONE', value: '***'}, + {key: 'APPLE_DEMO_EMAIL', value: '***'}, + {key: 'APPLE_DEMO_PASSWORD', value: '***'}, + ]), + ); + } + steps.push( + utils.createStepAssertion('Archive iOS sourcemaps', true, null, 'IOS', 'Archiving sourcemaps', [ + {key: 'name', value: 'ios-sourcemap'}, + {key: 'path', value: 'main.jsbundle.map'}, + ]), + ); + if (!isProduction) { + steps.push(utils.createStepAssertion('Upload iOS version to Browser Stack', true, null, 'IOS', 'Uploading version to Browser Stack', null, [{key: 'BROWSERSTACK', value: '***'}])); + } else { + steps.push( + utils.createStepAssertion('Set iOS version in ENV', true, null, 'IOS', 'Setting iOS version'), + utils.createStepAssertion('Run Fastlane for App Store release', true, null, 'IOS', 'Running Fastlane for release', null, [{key: 'VERSION', value: '1.2.3'}]), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); + + const failProdSteps = [ + utils.createStepAssertion( + 'Warn deployers if iOS production deploy failed', + true, + null, + 'IOS', + 'Warning developers of failed deploy', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + ]; + + failProdSteps.forEach((expectedStep) => { + if (didExecute && isProduction && !isSuccessful) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertWebJobExecuted = (workflowResult, didExecute = true, isProduction = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'WEB', 'Checking out'), + utils.createStepAssertion('Setup Node', true, null, 'WEB', 'Setting up Node'), + utils.createStepAssertion('Setup Cloudflare CLI', true, null, 'WEB', 'Setting up Cloudflare CLI'), + utils.createStepAssertion('Configure AWS Credentials', true, null, 'WEB', 'Configuring AWS credentials', [ + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ]), + ]; + if (isProduction) { + steps.push( + utils.createStepAssertion('Build web for production', true, null, 'WEB', 'Building web for production'), + utils.createStepAssertion('Build storybook docs for production', true, null, 'WEB', 'Build storybook docs for production'), + utils.createStepAssertion('Deploy production to S3', true, null, 'WEB', 'Deploying production to S3'), + utils.createStepAssertion('Purge production Cloudflare cache', true, null, 'WEB', 'Purging production Cloudflare cache', null, [{key: 'CF_API_KEY', value: '***'}]), + ); + } else { + steps.push( + utils.createStepAssertion('Build web for staging', true, null, 'WEB', 'Building web for staging'), + utils.createStepAssertion('Build storybook docs for staging', true, null, 'WEB', 'Build storybook docs for staging'), + utils.createStepAssertion('Deploy staging to S3', true, null, 'WEB', 'Deploying staging to S3'), + utils.createStepAssertion('Purge staging Cloudflare cache', true, null, 'WEB', 'Purging staging Cloudflare cache', null, [{key: 'CF_API_KEY', value: '***'}]), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertPostSlackOnFailureJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Post Slack message on failure', true, null, 'POST_SLACK_FAIL', 'Posting Slack message on platform deploy failure', [{key: 'SLACK_WEBHOOK', value: '***'}]), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertPostSlackOnSuccessJobExecuted = (workflowResult, didExecute = true, isProduction = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'POST_SLACK_SUCCESS', 'Checking out'), + utils.createStepAssertion('Set version', true, null, 'POST_SLACK_SUCCESS', 'Setting version'), + utils.createStepAssertion( + 'Announces the deploy in the #announce Slack room', + true, + null, + 'POST_SLACK_SUCCESS', + 'Posting message to #announce channel', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + utils.createStepAssertion( + 'Announces the deploy in the #deployer Slack room', + true, + null, + 'POST_SLACK_SUCCESS', + 'Posting message to #deployer channel', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + ]; + if (isProduction) { + steps.push( + utils.createStepAssertion( + 'Announces the deploy in the #expensify-open-source Slack room', + true, + null, + 'POST_SLACK_SUCCESS', + 'Posting message to #expensify-open-source channel', + [{key: 'status', value: 'custom'}], + [ + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'SLACK_WEBHOOK_URL', value: '***'}, + ], + ), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertPostGithubCommentJobExecuted = (workflowResult, didExecute = true, isProduction = true, didDeploy = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'POST_GITHUB_COMMENT', 'Checking out'), + utils.createStepAssertion('Setup Node', true, null, 'POST_GITHUB_COMMENT', 'Setting up Node'), + utils.createStepAssertion('Set version', true, null, 'POST_GITHUB_COMMENT', 'Setting version'), + utils.createStepAssertion('Get Release Pull Request List', true, null, 'POST_GITHUB_COMMENT', 'Getting release pull request list', [ + {key: 'TAG', value: '1.2.3'}, + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'IS_PRODUCTION_DEPLOY', value: isProduction ? 'true' : 'false'}, + ]), + utils.createStepAssertion('Comment on issues', true, null, 'POST_GITHUB_COMMENT', 'Commenting on issues', [ + {key: 'PR_LIST', value: '[1.2.1, 1.2.2]'}, + {key: 'IS_PRODUCTION_DEPLOY', value: isProduction ? 'true' : 'false'}, + {key: 'DEPLOY_VERSION', value: '1.2.3'}, + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'ANDROID', value: didDeploy ? 'success' : ''}, + {key: 'DESKTOP', value: didDeploy ? 'success' : ''}, + {key: 'IOS', value: didDeploy ? 'success' : ''}, + {key: 'WEB', value: didDeploy ? 'success' : ''}, + ]), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertVerifyActorJobExecuted, + assertDeployChecklistJobExecuted, + assertAndroidJobExecuted, + assertDesktopJobExecuted, + assertIOSJobExecuted, + assertWebJobExecuted, + assertPostSlackOnFailureJobExecuted, + assertPostSlackOnSuccessJobExecuted, + assertPostGithubCommentJobExecuted, +}; diff --git a/workflow_tests/assertions/preDeployAssertions.js b/workflow_tests/assertions/preDeployAssertions.js new file mode 100644 index 000000000000..90d6f9febb75 --- /dev/null +++ b/workflow_tests/assertions/preDeployAssertions.js @@ -0,0 +1,219 @@ +const utils = require('../utils/utils'); + +const assertTypecheckJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Run typecheck workflow', true, null, 'TYPECHECK', 'Running typecheck workflow')]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertLintJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Run lint workflow', true, null, 'LINT', 'Running lint workflow')]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertTestJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Run test workflow', true, null, 'TEST', 'Running test workflow')]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertIsExpensifyEmployeeJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Get merged pull request', true, null, 'IS_EXPENSIFY_EMPLOYEE', 'Getting merged pull request', [{key: 'github_token', value: '***'}]), + utils.createStepAssertion( + 'Check whether the PR author is member of Expensify/expensify team', + true, + null, + 'IS_EXPENSIFY_EMPLOYEE', + 'Checking actors Expensify membership', + [], + [{key: 'GITHUB_TOKEN', value: '***'}], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertNewContributorWelcomeMessageJobExecuted = (workflowResult, didExecute = true, isOsBotify = false, isFirstPr = false) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', 'Checking out', [{key: 'token', value: '***'}]), + utils.createStepAssertion('Get merged pull request', true, null, 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', 'Getting merged pull request', [{key: 'github_token', value: '***'}]), + utils.createStepAssertion(isOsBotify ? 'Get PR count for OSBotify' : 'Get PR count for Dummy Author', true, null, 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', 'Getting PR count', [ + {key: 'GITHUB_TOKEN', value: '***'}, + ]), + ]; + const osBotifyBody = + '@OSBotify, Great job getting your first Expensify/App pull request over the finish line! ' + + ":tada:\n\nI know there's a lot of information in our " + + '[contributing guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md), ' + + 'so here are some points to take note of :memo::\n\n1. Now that your first PR has been merged, you can be ' + + "hired for another issue. Once you've completed a few issues, you may be eligible to work on more than one " + + 'job at a time.\n2. Once your PR is deployed to our staging servers, it will undergo quality assurance (QA) ' + + "testing. If we find that it doesn't work as expected or causes a regression, you'll be responsible for " + + 'fixing it. Typically, we would revert this PR and give you another chance to create a similar PR without ' + + 'causing a regression.\n3. Once your PR is deployed to _production_, we start a 7-day timer :alarm_clock:. ' + + 'After it has been on production for 7 days without causing any regressions, then we pay out the Upwork job. ' + + ":moneybag:\n\nSo it might take a while before you're paid for your work, but we typically post multiple " + + "new jobs every day, so there's plenty of opportunity. I hope you've had a positive experience " + + 'contributing to this repo! :blush:'; + const userBody = + '@Dummy Author, Great job getting your first Expensify/App pull request over the finish ' + + "line! :tada:\n\nI know there's a lot of information in our " + + '[contributing guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md), ' + + 'so here are some points to take note of :memo::\n\n1. Now that your first PR has been merged, you can be ' + + "hired for another issue. Once you've completed a few issues, you may be eligible to work on more than one " + + 'job at a time.\n2. Once your PR is deployed to our staging servers, it will undergo quality assurance (QA) ' + + "testing. If we find that it doesn't work as expected or causes a regression, you'll be responsible for " + + 'fixing it. Typically, we would revert this PR and give you another chance to create a similar PR without ' + + 'causing a regression.\n3. Once your PR is deployed to _production_, we start a 7-day timer :alarm_clock:. ' + + 'After it has been on production for 7 days without causing any regressions, then we pay out the Upwork ' + + "job. :moneybag:\n\nSo it might take a while before you're paid for your work, but we typically post " + + "multiple new jobs every day, so there's plenty of opportunity. I hope you've had a positive experience " + + 'contributing to this repo! :blush:'; + if (isFirstPr) { + steps.push( + utils.createStepAssertion( + isOsBotify ? "Comment on OSBotify\\'s first pull request!" : "Comment on Dummy Author\\'s first pull request!", + true, + null, + 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', + 'Creating comment', + [ + {key: 'github_token', value: '***'}, + {key: 'number', value: '12345'}, + {key: 'body', value: isOsBotify ? osBotifyBody : userBody}, + ], + ), + ); + } + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertChooseDeployActionsJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Get merged pull request', true, null, 'CHOOSE_DEPLOY_ACTIONS', 'Getting merged pull request', [{key: 'github_token', value: '***'}]), + utils.createStepAssertion('Check if StagingDeployCash is locked', true, null, 'CHOOSE_DEPLOY_ACTIONS', 'Checking StagingDeployCash', [{key: 'GITHUB_TOKEN', value: '***'}]), + utils.createStepAssertion('Check if merged pull request should trigger a deploy', true, ''), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertSkipDeployJobExecuted = (workflowResult, didExecute = true) => { + const body = ':hand: This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.'; + const steps = [ + utils.createStepAssertion('Comment on deferred PR', true, null, 'SKIP_DEPLOY', 'Skipping deploy', [ + {key: 'github_token', value: '***'}, + {key: 'number', value: '123'}, + {key: 'body', value: body}, + ]), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertCreateNewVersionJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Create new version', true, null, 'CREATE_NEW_VERSION', 'Creating new version')]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertUpdateStagingJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Run turnstyle', true, null, 'UPDATE_STAGING', 'Running turnstyle', [ + {key: 'poll-interval-seconds', value: '10'}, + {key: 'GITHUB_TOKEN', value: '***'}, + ]), + utils.createStepAssertion('Checkout main', true, null, 'UPDATE_STAGING', 'Checkout main', [ + {key: 'ref', value: 'main'}, + {key: 'token', value: '***'}, + ]), + utils.createStepAssertion('Setup Git for OSBotify', true, null, 'UPDATE_STAGING', 'Setup Git for OSBotify', [{key: 'GPG_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Update staging branch from main', true, null, 'UPDATE_STAGING', 'Update staging branch from main'), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertUpdateStagingJobFailed = (workflowResult, didFail = false) => { + const steps = [ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'UPDATE_STAGING', 'Announcing failed workflow in Slack', [{key: 'SLACK_WEBHOOK', value: '***'}]), + ]; + + steps.forEach((expectedStep) => { + if (didFail) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertTypecheckJobExecuted, + assertLintJobExecuted, + assertTestJobExecuted, + assertIsExpensifyEmployeeJobExecuted, + assertNewContributorWelcomeMessageJobExecuted, + assertChooseDeployActionsJobExecuted, + assertSkipDeployJobExecuted, + assertCreateNewVersionJobExecuted, + assertUpdateStagingJobExecuted, + assertUpdateStagingJobFailed, +}; diff --git a/workflow_tests/assertions/reviewerChecklistAssertions.js b/workflow_tests/assertions/reviewerChecklistAssertions.js new file mode 100644 index 000000000000..6154b1cd28ca --- /dev/null +++ b/workflow_tests/assertions/reviewerChecklistAssertions.js @@ -0,0 +1,17 @@ +const utils = require('../utils/utils'); + +const assertChecklistJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('reviewerChecklist.js', true, null, 'CHECKLIST', 'reviewerChecklist.js', [{key: 'GITHUB_TOKEN', value: '***'}], [])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertChecklistJobExecuted, +}; diff --git a/workflow_tests/assertions/testAssertions.js b/workflow_tests/assertions/testAssertions.js new file mode 100644 index 000000000000..cee379ad5903 --- /dev/null +++ b/workflow_tests/assertions/testAssertions.js @@ -0,0 +1,58 @@ +const utils = require('../utils/utils'); + +const assertJestJobExecuted = (workflowResult, didExecute = true, timesExecuted = 3) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'JEST', 'Checkout', [], []), + utils.createStepAssertion('Setup Node', true, null, 'JEST', 'Setup Node', [], []), + utils.createStepAssertion('Get number of CPU cores', true, null, 'JEST', 'Get number of CPU cores', [], []), + utils.createStepAssertion( + 'Cache Jest cache', + true, + null, + 'JEST', + 'Cache Jest cache', + [ + {key: 'path', value: '.jest-cache'}, + {key: 'key', value: 'Linux-jest'}, + ], + [], + ), + utils.createStepAssertion('Jest tests', true, null, 'JEST', 'Jest tests', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + let cnt = 0; + workflowResult.forEach((executedStep) => { + if (executedStep.name !== expectedStep.name || executedStep.output !== expectedStep.output || executedStep.status !== expectedStep.status) { + return; + } + cnt += 1; + }); + expect(cnt).toEqual(timesExecuted); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertShellTestsJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'SHELLTESTS', 'Checkout', [], []), + utils.createStepAssertion('Setup Node', true, null, 'SHELLTESTS', 'Setup Node', [], []), + utils.createStepAssertion('Test CI git logic', true, null, 'SHELLTESTS', 'Test CI git logic', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertJestJobExecuted, + assertShellTestsJobExecuted, +}; diff --git a/workflow_tests/assertions/testBuildAssertions.js b/workflow_tests/assertions/testBuildAssertions.js new file mode 100644 index 000000000000..a4f7a46259ad --- /dev/null +++ b/workflow_tests/assertions/testBuildAssertions.js @@ -0,0 +1,413 @@ +const utils = require('../utils/utils'); + +const assertValidateActorJobExecuted = (workflowResult, pullRequestNumber = '1234', didExecute = true) => { + const steps = [ + utils.createStepAssertion('Is Expensify employee', true, null, 'VALIDATEACTOR', 'Is Expensify employee', [], [{key: 'GITHUB_TOKEN', value: '***'}]), + utils.createStepAssertion( + 'Set HAS_READY_TO_BUILD_LABEL flag', + true, + null, + 'VALIDATEACTOR', + 'Set HAS_READY_TO_BUILD_LABEL flag', + [], + [ + {key: 'PULL_REQUEST_NUMBER', value: pullRequestNumber}, + {key: 'GITHUB_TOKEN', value: '***'}, + ], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertGetBranchRefJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'GETBRANCHREF', 'Checkout', [], []), + utils.createStepAssertion( + 'Check if pull request number is correct', + true, + null, + 'GETBRANCHREF', + 'Check if pull request number is correct', + [], + [{key: 'GITHUB_TOKEN', value: '***'}], + ), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertAndroidJobExecuted = (workflowResult, ref = '', didExecute = true, failsAt = -1) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'ANDROID', 'Checkout', [{key: 'ref', value: ref}], []), + utils.createStepAssertion( + 'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', + true, + null, + 'ANDROID', + 'Creating .env.adhoc file based on staging', + [], + [], + ), + utils.createStepAssertion('Setup Node', true, null, 'ANDROID', 'Setup Node', [], []), + utils.createStepAssertion( + 'Setup Ruby', + true, + null, + 'ANDROID', + 'Setup Ruby', + [ + {key: 'ruby-version', value: '2.7'}, + {key: 'bundler-cache', value: true}, + ], + [], + ), + utils.createStepAssertion('Decrypt keystore', true, null, 'ANDROID', 'Decrypt keystore', [], [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Decrypt json key', true, null, 'ANDROID', 'Decrypt json key', [], [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion( + 'Configure AWS Credentials', + true, + null, + 'ANDROID', + 'Configure AWS Credentials', + [ + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Configure MapBox SDK', true, null, 'ANDROID', 'Configure MapBox SDK'), + utils.createStepAssertion( + 'Run Fastlane beta test', + true, + null, + 'ANDROID', + 'Run Fastlane beta test', + [], + [ + {key: 'S3_ACCESS_KEY', value: '***'}, + {key: 'S3_SECRET_ACCESS_KEY', value: '***'}, + {key: 'S3_BUCKET', value: 'ad-hoc-expensify-cash'}, + {key: 'S3_REGION', value: 'us-east-1'}, + {key: 'MYAPP_UPLOAD_STORE_PASSWORD', value: '***'}, + {key: 'MYAPP_UPLOAD_KEY_PASSWORD', value: '***'}, + ], + ), + utils.createStepAssertion( + 'Upload Artifact', + true, + null, + 'ANDROID', + 'Upload Artifact', + [ + {key: 'name', value: 'android'}, + {key: 'path', value: './android_paths.json'}, + ], + [], + ), + ]; + + steps.forEach((expectedStep, i) => { + if (didExecute) { + if (failsAt === -1 || i < failsAt) { + // either whole job is successful, or steps up to this point are successful + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else if (i === failsAt) { + // this is the failing step + steps[i].status = 1; + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + // steps after failed one do not execute + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertIOSJobExecuted = (workflowResult, ref = '', didExecute = true, failsAt = -1) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'IOS', 'Checkout', [{key: 'ref', value: ref}], []), + utils.createStepAssertion('Configure MapBox SDK', true, null, 'IOS', 'Configure MapBox SDK'), + utils.createStepAssertion('Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', true, null, 'IOS', 'Creating .env.adhoc file based on staging', [], []), + utils.createStepAssertion('Setup Node', true, null, 'IOS', 'Setup Node', [], []), + utils.createStepAssertion('Setup XCode', true, null, 'IOS', 'Setup XCode', [], []), + utils.createStepAssertion( + 'Setup Ruby', + true, + null, + 'IOS', + 'Setup Ruby', + [ + {key: 'ruby-version', value: '2.7'}, + {key: 'bundler-cache', value: true}, + ], + [], + ), + utils.createStepAssertion('Cache Pod dependencies', true, null, 'IOS', 'Cache Pod dependencies', [ + {key: 'path', value: 'ios/Pods'}, + {key: 'key', value: 'Linux-pods-cache-'}, + {key: 'restore-keys', value: 'Linux-pods-cache-'}, + ]), + utils.createStepAssertion('Compare Podfile.lock and Manifest.lock', true, null, 'IOS', 'Compare Podfile.lock and Manifest.lock'), + utils.createStepAssertion( + 'Install cocoapods', + true, + null, + 'IOS', + 'Install cocoapods', + [ + {key: 'timeout_minutes', value: '10'}, + {key: 'max_attempts', value: '5'}, + {key: 'command', value: 'cd ios && bundle exec pod install'}, + ], + [], + ), + utils.createStepAssertion('Decrypt profile', true, null, 'IOS', 'Decrypt profile', [], [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion('Decrypt certificate', true, null, 'IOS', 'Decrypt certificate', [], [{key: 'LARGE_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion( + 'Configure AWS Credentials', + true, + null, + 'IOS', + 'Configure AWS Credentials', + [ + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ], + [], + ), + utils.createStepAssertion( + 'Run Fastlane', + true, + null, + 'IOS', + 'Run Fastlane', + [], + [ + {key: 'S3_ACCESS_KEY', value: '***'}, + {key: 'S3_SECRET_ACCESS_KEY', value: '***'}, + {key: 'S3_BUCKET', value: 'ad-hoc-expensify-cash'}, + {key: 'S3_REGION', value: 'us-east-1'}, + ], + ), + utils.createStepAssertion( + 'Upload Artifact', + true, + null, + 'IOS', + 'Upload Artifact', + [ + {key: 'name', value: 'ios'}, + {key: 'path', value: './ios_paths.json'}, + ], + [], + ), + ]; + + steps.forEach((expectedStep, i) => { + if (didExecute) { + if (failsAt === -1 || i < failsAt) { + // either whole job is successful, or steps up to this point are successful + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else if (i === failsAt) { + // this is the failing step + steps[i].status = 1; + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + // steps after failed one do not execute + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertDesktopJobExecuted = (workflowResult, ref = '', didExecute = true, failsAt = -1) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'DESKTOP', 'Checkout', [{key: 'ref', value: ref}], []), + utils.createStepAssertion( + 'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', + true, + null, + 'DESKTOP', + 'Creating .env.adhoc file based on staging', + [], + [], + ), + utils.createStepAssertion('Setup Node', true, null, 'DESKTOP', 'Setup Node', [], []), + utils.createStepAssertion('Decrypt Developer ID Certificate', true, null, 'DESKTOP', 'Decrypt Developer ID Certificate', [], [{key: 'DEVELOPER_ID_SECRET_PASSPHRASE', value: '***'}]), + utils.createStepAssertion( + 'Configure AWS Credentials', + true, + null, + 'DESKTOP', + 'Configure AWS Credentials', + [ + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ], + [], + ), + utils.createStepAssertion( + 'Build desktop app for testing', + true, + null, + 'DESKTOP', + 'Build desktop app for testing', + [], + [ + {key: 'CSC_LINK', value: '***'}, + {key: 'CSC_KEY_PASSWORD', value: '***'}, + {key: 'APPLE_ID', value: '***'}, + {key: 'APPLE_APP_SPECIFIC_PASSWORD', value: '***'}, + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ], + ), + ]; + + steps.forEach((expectedStep, i) => { + if (didExecute) { + if (failsAt === -1 || i < failsAt) { + // either whole job is successful, or steps up to this point are successful + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else if (i === failsAt) { + // this is the failing step + steps[i].status = 1; + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + // steps after failed one do not execute + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; +const assertWebJobExecuted = (workflowResult, ref = '', didExecute = true, failsAt = -1) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'WEB', 'Checkout', [{key: 'ref', value: ref}], []), + utils.createStepAssertion('Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', true, null, 'WEB', 'Creating .env.adhoc file based on staging', [], []), + utils.createStepAssertion('Setup Node', true, null, 'WEB', 'Setup Node', [], []), + utils.createStepAssertion( + 'Configure AWS Credentials', + true, + null, + 'WEB', + 'Configure AWS Credentials', + [ + {key: 'AWS_ACCESS_KEY_ID', value: '***'}, + {key: 'AWS_SECRET_ACCESS_KEY', value: '***'}, + ], + [], + ), + utils.createStepAssertion('Build web for testing', true, null, 'WEB', 'Build web for testing', [], []), + utils.createStepAssertion('Build docs', true, null, 'WEB', 'Build docs', [], []), + utils.createStepAssertion('Deploy to S3 for internal testing', true, null, 'WEB', 'Deploy to S3 for internal testing', [], []), + ]; + + steps.forEach((expectedStep, i) => { + if (didExecute) { + if (failsAt === -1 || i < failsAt) { + // either whole job is successful, or steps up to this point are successful + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else if (i === failsAt) { + // this is the failing step + steps[i].status = 1; + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + // steps after failed one do not execute + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +const assertPostGithubCommentJobExecuted = ( + workflowResult, + ref = '', + pullRequestNumber = '1234', + didExecute = true, + androidStatus = 'success', + iOSStatus = 'success', + desktopStatus = 'success', + webStatus = 'success', +) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'POSTGITHUBCOMMENT', 'Checkout', [{key: 'ref', value: ref}], []), + utils.createStepAssertion('Download Artifact', true, null, 'POSTGITHUBCOMMENT', 'Download Artifact', [], []), + ]; + if (androidStatus === 'success') { + steps.push(utils.createStepAssertion('Read JSONs with android paths', true, null, 'POSTGITHUBCOMMENT', 'Read JSONs with android paths', [], [])); + } + if (iOSStatus === 'success') { + steps.push(utils.createStepAssertion('Read JSONs with iOS paths', true, null, 'POSTGITHUBCOMMENT', 'Read JSONs with iOS paths', [], [])); + } + steps.push( + utils.createStepAssertion( + 'maintain-comment', + true, + null, + 'POSTGITHUBCOMMENT', + 'maintain-comment', + [ + {key: 'token', value: '***'}, + {key: 'body-include', value: 'Use the links below to test this build in android and iOS. Happy testing!'}, + {key: 'number', value: pullRequestNumber}, + {key: 'delete', value: true}, + ], + [], + ), + utils.createStepAssertion( + 'Publish links to apps for download', + true, + null, + 'POSTGITHUBCOMMENT', + 'Publish links to apps for download', + [ + {key: 'PR_NUMBER', value: pullRequestNumber}, + {key: 'GITHUB_TOKEN', value: '***'}, + {key: 'ANDROID', value: androidStatus}, + {key: 'DESKTOP', value: desktopStatus}, + {key: 'IOS', value: iOSStatus}, + {key: 'WEB', value: webStatus}, + {key: 'ANDROID_LINK', value: androidStatus === 'success' ? 'http://dummy.android.link' : ''}, + {key: 'DESKTOP_LINK', value: `https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${pullRequestNumber}/NewExpensify.dmg`}, + {key: 'IOS_LINK', value: iOSStatus === 'success' ? 'http://dummy.ios.link' : ''}, + {key: 'WEB_LINK', value: `https://${pullRequestNumber}.pr-testing.expensify.com`}, + ], + [], + ), + ); + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertValidateActorJobExecuted, + assertGetBranchRefJobExecuted, + assertAndroidJobExecuted, + assertIOSJobExecuted, + assertDesktopJobExecuted, + assertWebJobExecuted, + assertPostGithubCommentJobExecuted, +}; diff --git a/workflow_tests/assertions/validateGithubActionsAssertions.js b/workflow_tests/assertions/validateGithubActionsAssertions.js new file mode 100644 index 000000000000..fb5f58d2b5ed --- /dev/null +++ b/workflow_tests/assertions/validateGithubActionsAssertions.js @@ -0,0 +1,22 @@ +const utils = require('../utils/utils'); + +const assertVerifyJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'VERIFY', 'Checkout'), + utils.createStepAssertion('Setup Node', true, null, 'VERIFY', 'Setup Node', [], []), + utils.createStepAssertion('Verify Javascript Action Builds', true, null, 'VERIFY', 'Verify Javascript Action Builds', [], []), + utils.createStepAssertion('Validate actions and workflows', true, null, 'VERIFY', 'Validate actions and workflows', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertVerifyJobExecuted, +}; diff --git a/workflow_tests/assertions/verifyPodfileAssertions.js b/workflow_tests/assertions/verifyPodfileAssertions.js new file mode 100644 index 000000000000..e0cc50570fec --- /dev/null +++ b/workflow_tests/assertions/verifyPodfileAssertions.js @@ -0,0 +1,21 @@ +const utils = require('../utils/utils'); + +const assertVerifyJobExecuted = (workflowResult, didExecute = true) => { + const steps = [ + utils.createStepAssertion('Checkout', true, null, 'VERIFY', 'Checkout'), + utils.createStepAssertion('Setup Node', true, null, 'VERIFY', 'Setup Node', [], []), + utils.createStepAssertion('Verify podfile', true, null, 'VERIFY', 'Verify podfile', [], []), + ]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertVerifyJobExecuted, +}; diff --git a/workflow_tests/assertions/verifySignedCommitsAssertions.js b/workflow_tests/assertions/verifySignedCommitsAssertions.js new file mode 100644 index 000000000000..458bc684363f --- /dev/null +++ b/workflow_tests/assertions/verifySignedCommitsAssertions.js @@ -0,0 +1,17 @@ +const utils = require('../utils/utils'); + +const assertVerifySignedCommitsJobExecuted = (workflowResult, didExecute = true) => { + const steps = [utils.createStepAssertion('Verify signed commits', true, null, 'VERIFYSIGNEDCOMMITS', 'Verify signed commits', [{key: 'GITHUB_TOKEN', value: '***'}], [])]; + + steps.forEach((expectedStep) => { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + }); +}; + +module.exports = { + assertVerifySignedCommitsJobExecuted, +}; diff --git a/workflow_tests/authorChecklist.test.js b/workflow_tests/authorChecklist.test.js new file mode 100644 index 000000000000..64a25c8fe1b8 --- /dev/null +++ b/workflow_tests/authorChecklist.test.js @@ -0,0 +1,181 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/authorChecklistAssertions'); +const mocks = require('./mocks/authorChecklistMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'authorChecklist.yml'), + dest: '.github/workflows/authorChecklist.yml', + }, +]; + +describe('test workflow authorChecklist', () => { + const githubToken = 'dummy_github_token'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testAuthorChecklistWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('pull request opened', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'opened', + }; + describe('actor is not OSBotify', () => { + const actor = 'Dummy Author'; + it('executes workflow', async () => { + const repoPath = mockGithub.repo.getPath('testAuthorChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('authorChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result); + }); + }); + describe('actor is OSBotify', () => { + const actor = 'OSBotify'; + it('does not execute workflow', async () => { + const repoPath = mockGithub.repo.getPath('testAuthorChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('authorChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result, false); + }); + }); + }); + describe('pull request edited', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'edited', + }; + describe('actor is not OSBotify', () => { + const actor = 'Dummy Author'; + it('executes workflow', async () => { + const repoPath = mockGithub.repo.getPath('testAuthorChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('authorChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result); + }); + }); + describe('actor is OSBotify', () => { + const actor = 'OSBotify'; + it('does not execute workflow', async () => { + const repoPath = mockGithub.repo.getPath('testAuthorChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('authorChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result, false); + }); + }); + }); + describe('pull request reopened', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'reopened', + }; + describe('actor is not OSBotify', () => { + const actor = 'Dummy Author'; + it('executes workflow', async () => { + const repoPath = mockGithub.repo.getPath('testAuthorChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('authorChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result); + }); + }); + describe('actor is OSBotify', () => { + const actor = 'OSBotify'; + it('does not execute workflow', async () => { + const repoPath = mockGithub.repo.getPath('testAuthorChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'authorChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('authorChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result, false); + }); + }); + }); +}); diff --git a/workflow_tests/cherryPick.test.js b/workflow_tests/cherryPick.test.js new file mode 100644 index 000000000000..592170a7c64e --- /dev/null +++ b/workflow_tests/cherryPick.test.js @@ -0,0 +1,391 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/cherryPickAssertions'); +const mocks = require('./mocks/cherryPickMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'cherryPick.yml'), + dest: '.github/workflows/cherryPick.yml', + }, +]; + +describe('test workflow cherryPick', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testCherryPickWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('manual workflow dispatch', () => { + const event = 'workflow_dispatch'; + describe('actor is not deployer', () => { + const actor = 'Dummy Author'; + it('workflow ends after validate job', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS, + cherryPick: mocks.getCherryPickMockSteps(true, false), + }; + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertCherryPickJobExecuted(result, actor, '1234', false); + }); + }); + describe('actor is OSBotify', () => { + const actor = 'OSBotify'; + const mergeConflicts = false; + const versionsMatch = true; + it('behaviour is the same as with actor being the deployer', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS, + cherryPick: mocks.getCherryPickMockSteps(versionsMatch, mergeConflicts), + }; + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertCherryPickJobExecuted(result, actor, '1234', true); + }); + }); + describe('actor is a deployer', () => { + const actor = 'Dummy Author'; + describe('no merge conflicts', () => { + const mergeConflicts = false; + describe('version match', () => { + const versionsMatch = true; + it('workflow executes, PR approved and merged automatically', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, + }; + testMockSteps.cherryPick = mocks.getCherryPickMockSteps(versionsMatch, mergeConflicts); + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertCherryPickJobExecuted(result, actor, '1234', true); + }); + }); + describe('version does not match', () => { + const versionsMatch = false; + it('workflow executes, PR auto-assigned and commented, approved and merged automatically', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, + }; + testMockSteps.cherryPick = mocks.getCherryPickMockSteps(versionsMatch, mergeConflicts); + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertCherryPickJobExecuted(result, actor, '1234', true); + }); + }); + }); + describe('merge conflicts', () => { + const mergeConflicts = true; + describe('version match', () => { + const versionsMatch = true; + it('workflow executes, PR auto-assigned and commented, not merged automatically', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, + }; + testMockSteps.cherryPick = mocks.getCherryPickMockSteps(versionsMatch, mergeConflicts); + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertCherryPickJobExecuted(result, actor, '1234', true, true); + }); + }); + describe('version does not match', () => { + const versionsMatch = false; + it('workflow executes, PR auto-assigned and commented, not merged automatically', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, + }; + testMockSteps.cherryPick = mocks.getCherryPickMockSteps(versionsMatch, mergeConflicts); + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertCherryPickJobExecuted(result, actor, '1234', true, true); + }); + }); + }); + }); + }); + describe('automatic trigger', () => { + const event = 'pull_request'; + it('workflow does not execute', async () => { + const repoPath = mockGithub.repo.getPath('testCherryPickWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + event, + null, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + null, + { + PULL_REQUEST_NUMBER: '1234', + }, + ); + const testMockSteps = { + validateActor: mocks.CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, + cherryPick: mocks.getCherryPickMockSteps(true, false), + }; + const testMockJobs = { + createNewVersion: { + steps: mocks.CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cherryPick.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('cherryPick', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + assertions.assertValidateActorJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertCherryPickJobExecuted(result, 'Dummy Author', '1234', false); + }); + }); +}); diff --git a/workflow_tests/cla.test.js b/workflow_tests/cla.test.js new file mode 100644 index 000000000000..301aa7587f53 --- /dev/null +++ b/workflow_tests/cla.test.js @@ -0,0 +1,196 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/claAssertions'); +const mocks = require('./mocks/claMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'cla.yml'), + dest: '.github/workflows/cla.yml', + }, +]; + +describe('test workflow cla', () => { + const secrets = { + CLA_BOTIFY_TOKEN: 'dummy_cla_botify_token', + }; + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Author'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testClaWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('event is issue_comment', () => { + const event = 'issue_comment'; + describe('no regex matches', () => { + const commentBody = 'Comment body'; + const eventData = { + action: 'created', + issue: { + pull_request: { + number: 1234, + }, + }, + comment: { + body: commentBody, + }, + }; + it('workflow executes, CLA assistant step not run', async () => { + const repoPath = mockGithub.repo.getPath('testClaWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cla.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventData, secrets, githubToken); + const testMockSteps = { + CLA: mocks.CLA__CLA__NO_MATCHES__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cla.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cla', expect.getState().currentTestName), + }); + + assertions.assertCLAJobExecuted(result, commentBody, `${repoPath}/remote/origin`, true, false); + }); + }); + describe('check regex matches', () => { + const commentBody = 'I have read the CLA Document and I hereby sign the CLA'; + const eventData = { + action: 'created', + issue: { + pull_request: { + number: 1234, + }, + }, + comment: { + body: commentBody, + }, + }; + it('workflow executes, CLA assistant step run', async () => { + const repoPath = mockGithub.repo.getPath('testClaWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cla.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventData, secrets, githubToken); + const testMockSteps = { + CLA: mocks.CLA__CLA__CHECK_MATCH__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cla.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cla', expect.getState().currentTestName), + }); + + assertions.assertCLAJobExecuted(result, commentBody, `${repoPath}/remote/origin`, true, true); + }); + }); + describe('re-check regex matches', () => { + const commentBody = 'recheck'; + const eventData = { + action: 'created', + issue: { + pull_request: { + number: 1234, + }, + }, + comment: { + body: commentBody, + }, + }; + it('workflow executes, CLA assistant step run', async () => { + const repoPath = mockGithub.repo.getPath('testClaWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cla.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventData, secrets, githubToken); + const testMockSteps = { + CLA: mocks.CLA__CLA__RECHECK_MATCH__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cla.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cla', expect.getState().currentTestName), + }); + + assertions.assertCLAJobExecuted(result, commentBody, `${repoPath}/remote/origin`, true, true); + }); + }); + }); + describe('event is pull_request_target', () => { + const event = 'pull_request_target'; + describe("no regex matches - there's no comment", () => { + const eventData = { + action: 'opened', + issue: { + pull_request: { + number: 1234, + }, + }, + }; + it('workflow executes, CLA assistant step still run', async () => { + const repoPath = mockGithub.repo.getPath('testClaWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cla.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventData, secrets, githubToken); + const testMockSteps = { + CLA: mocks.CLA__CLA__NO_MATCHES__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cla.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cla', expect.getState().currentTestName), + }); + + assertions.assertCLAJobExecuted(result, '', `${repoPath}/remote/origin`, true, true); + }); + }); + }); + describe('different event', () => { + const event = 'push'; + it('workflow does not execute', async () => { + const eventData = { + ref: 'main', + }; + const repoPath = mockGithub.repo.getPath('testClaWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'cla.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventData, secrets, githubToken); + const testMockSteps = { + CLA: mocks.CLA__CLA__NO_MATCHES__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'cla.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('cla', expect.getState().currentTestName), + }); + + assertions.assertCLAJobExecuted(result, '', `${repoPath}/remote/origin`, false); + }); + }); +}); diff --git a/workflow_tests/createNewVersion.test.js b/workflow_tests/createNewVersion.test.js new file mode 100644 index 000000000000..259e06450325 --- /dev/null +++ b/workflow_tests/createNewVersion.test.js @@ -0,0 +1,166 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/createNewVersionAssertions'); +const mocks = require('./mocks/createNewVersionMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); // 90 sec +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'createNewVersion.yml'), + dest: '.github/workflows/createNewVersion.yml', + }, +]; + +describe('test workflow createNewVersion', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testCreateNewVersionWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + + describe('event is workflow_call', () => { + const event = 'workflow_call'; + const inputs = { + SEMVER_LEVEL: 'BUILD', + }; + const secrets = { + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + OS_BOTIFY_TOKEN: 'dummy_osbotify_token', + SLACK_WEBHOOK: 'dummy_webhook', + }; + const githubToken = 'dummy_github_token'; + + describe('actor is admin', () => { + const validateActorMockSteps = mocks.CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS; + it('executes full workflow', async () => { + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {createNewVersion: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: validateActorMockSteps, + createNewVersion: mocks.CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('createNewVersion', expect.getState().currentTestName), + }); + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + }); + }); + + describe('actor is writer', () => { + const validateActorMockSteps = mocks.CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS; + it('executes full workflow', async () => { + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {createNewVersion: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: validateActorMockSteps, + createNewVersion: mocks.CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('createNewVersion', expect.getState().currentTestName), + }); + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result); + }); + }); + + describe('actor is reader', () => { + const validateActorMockSteps = mocks.CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS; + it('stops after validation', async () => { + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {createNewVersion: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: validateActorMockSteps, + createNewVersion: mocks.CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('createNewVersion', expect.getState().currentTestName), + }); + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result, 'BUILD', false); + }); + }); + + describe('one step fails', () => { + it('announces failure on Slack', async () => { + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {createNewVersion: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS, + createNewVersion: utils.deepCopy(mocks.CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS), + }; + testMockSteps.createNewVersion[5] = utils.createMockStep('Commit new version', 'Commit new version', 'CREATENEWVERSION', [], [], [], [], false); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('createNewVersion', expect.getState().currentTestName), + }); + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result, 'BUILD', true, false); + }); + }); + + it('chooses source branch depending on the SEMVER_LEVEL', async () => { + const repoPath = mockGithub.repo.getPath('testCreateNewVersionWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, {SEMVER_LEVEL: 'MAJOR'}); + act = utils.setJobRunners(act, {createNewVersion: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS, + createNewVersion: mocks.CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'createNewVersion.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('createNewVersion', expect.getState().currentTestName), + }); + assertions.assertValidateActorJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result, 'MAJOR'); + }); + }); +}); diff --git a/workflow_tests/deploy.test.js b/workflow_tests/deploy.test.js new file mode 100644 index 000000000000..a2ccdebc0b31 --- /dev/null +++ b/workflow_tests/deploy.test.js @@ -0,0 +1,183 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/deployAssertions'); +const mocks = require('./mocks/deployMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'deploy.yml'), + dest: '.github/workflows/deploy.yml', + }, +]; + +describe('test workflow deploy', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testDeployWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + pushedBranches: ['staging', 'production'], + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('push', () => { + it('to main - nothing triggered', async () => { + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + { + ref: 'refs/heads/main', + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + deployStaging: mocks.DEPLOY_STAGING_STEP_MOCKS, + deployProduction: mocks.DEPLOY_PRODUCTION_STEP_MOCKS, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('deploy', expect.getState().currentTestName), + }); + assertions.assertDeployStagingJobExecuted(result, false); + assertions.assertDeployProductionJobExecuted(result, false); + }); + + it('to staging - deployStaging triggered', async () => { + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + { + ref: 'refs/heads/staging', + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + deployStaging: mocks.DEPLOY_STAGING_STEP_MOCKS, + deployProduction: mocks.DEPLOY_PRODUCTION_STEP_MOCKS, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('deploy', expect.getState().currentTestName), + }); + assertions.assertDeployStagingJobExecuted(result); + assertions.assertDeployProductionJobExecuted(result, false); + }); + + it('to production - deployProduction triggered', async () => { + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + { + ref: 'refs/heads/production', + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + deployStaging: mocks.DEPLOY_STAGING_STEP_MOCKS, + deployProduction: mocks.DEPLOY_PRODUCTION_STEP_MOCKS, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('deploy', expect.getState().currentTestName), + }); + assertions.assertDeployStagingJobExecuted(result, false); + assertions.assertDeployProductionJobExecuted(result); + }); + }); + + it('different event than push - workflow does not execute', async () => { + const repoPath = mockGithub.repo.getPath('testDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + deployStaging: mocks.DEPLOY_STAGING_STEP_MOCKS, + deployProduction: mocks.DEPLOY_PRODUCTION_STEP_MOCKS, + }; + + // pull_request + act = utils.setUpActParams( + act, + 'pull_request', + {head: {ref: 'main'}}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + let result = await act.runEvent('pull_request', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('deploy', expect.getState().currentTestName), + }); + assertions.assertDeployStagingJobExecuted(result, false); + assertions.assertDeployProductionJobExecuted(result, false); + + // workflow_dispatch + act = utils.setUpActParams( + act, + 'workflow_dispatch', + {}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + result = await act.runEvent('workflow_dispatch', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('deploy', expect.getState().currentTestName), + }); + assertions.assertDeployStagingJobExecuted(result, false); + assertions.assertDeployProductionJobExecuted(result, false); + }); +}); diff --git a/workflow_tests/deployBlocker.test.js b/workflow_tests/deployBlocker.test.js new file mode 100644 index 000000000000..1cfca11c90f5 --- /dev/null +++ b/workflow_tests/deployBlocker.test.js @@ -0,0 +1,135 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/deployBlockerAssertions'); +const mocks = require('./mocks/deployBlockerMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'deployBlocker.yml'), + dest: '.github/workflows/deployBlocker.yml', + }, +]; + +describe('test workflow deployBlocker', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Author'; + const secrets = { + OS_BOTIFY_TOKEN: 'dummy_osbotify_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testDeployBlockerWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + + // if any branches besides main are need add: pushedBranches: ['staging', 'production'], + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('issue labeled', () => { + const event = 'issues'; + const eventOptions = { + action: 'labeled', + label: { + name: 'DeployBlockerCash', + }, + issue: { + title: 'Labeled issue title', + number: '1234', + html_url: 'http://issue.html.url', + }, + }; + describe('label is DeployBlockerCash', () => { + const testEventOptions = utils.deepCopy(eventOptions); + testEventOptions.label = {name: 'DeployBlockerCash'}; + it('runs the workflow and announces success on Slack', async () => { + const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, testEventOptions, secrets, githubToken, {}, {}); + const testMockSteps = { + deployBlocker: mocks.DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('deployBlocker', expect.getState().currentTestName), + }); + + assertions.assertDeployBlockerJobExecuted(result, 'Labeled issue title', '1234'); + }); + describe('one step fails', () => { + it('announces failure on Slack', async () => { + const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, testEventOptions, secrets, githubToken, {}, {}); + const testMockSteps = { + deployBlocker: utils.deepCopy(mocks.DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS), + }; + testMockSteps.deployBlocker[2] = utils.createMockStep( + 'Update StagingDeployCash with new deploy blocker', + 'Update StagingDeployCash with new deploy blocker', + 'DEPLOYBLOCKER', + ['GITHUB_TOKEN'], + [], + {}, + {}, + false, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('deployBlocker', expect.getState().currentTestName), + }); + + assertions.assertDeployBlockerJobExecuted(result, 'Labeled issue title', '1234', true, false); + }); + }); + }); + describe('label is different', () => { + const testEventOptions = utils.deepCopy(eventOptions); + testEventOptions.label = {name: 'Different Label'}; + it('does not run workflow', async () => { + const repoPath = mockGithub.repo.getPath('testDeployBlockerWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, testEventOptions, secrets, githubToken, {}, {}); + const testMockSteps = { + deployBlocker: mocks.DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'deployBlocker.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('deployBlocker', expect.getState().currentTestName), + }); + + assertions.assertDeployBlockerJobExecuted(result, '', '', false); + }); + }); + }); +}); diff --git a/workflow_tests/finishReleaseCycle.test.js b/workflow_tests/finishReleaseCycle.test.js new file mode 100644 index 000000000000..7c17ca8d4122 --- /dev/null +++ b/workflow_tests/finishReleaseCycle.test.js @@ -0,0 +1,247 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/finishReleaseCycleAssertions'); +const mocks = require('./mocks/finishReleaseCycleMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'finishReleaseCycle.yml'), + dest: '.github/workflows/finishReleaseCycle.yml', + }, +]; + +describe('test workflow finishReleaseCycle', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testFinishReleaseCycleWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('issue closed', () => { + describe('issue has StagingDeployCash', () => { + describe('actor is a team member', () => { + describe('no deploy blockers', () => { + it('production updated, new version created', async () => { + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'closed', + type: 'closed', + issue: { + labels: [{name: 'StagingDeployCash'}], + number: '1234', + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + const testMockSteps = { + validate: mocks.FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS, + updateProduction: mocks.FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS, + updateStaging: mocks.FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS, + }; + const testMockJobs = { + createNewPatchVersion: { + steps: mocks.FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('finishReleaseCycle', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertValidateJobExecuted(result, '1234'); + assertions.assertUpdateProductionJobExecuted(result); + assertions.assertCreateNewPatchVersionJobExecuted(result); + assertions.assertUpdateStagingJobExecuted(result); + }); + }); + describe('deploy blockers', () => { + it('production not updated, new version not created, issue reopened', async () => { + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'closed', + type: 'closed', + issue: { + labels: [{name: 'StagingDeployCash'}], + number: '1234', + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + const testMockSteps = { + validate: mocks.FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS, + updateProduction: mocks.FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS, + updateStaging: mocks.FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS, + }; + const testMockJobs = { + createNewPatchVersion: { + steps: mocks.FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('finishReleaseCycle', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertValidateJobExecuted(result, '1234', true, true, true); + assertions.assertUpdateProductionJobExecuted(result, false); + assertions.assertCreateNewPatchVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + }); + }); + describe('actor is not a team member', () => { + it('production not updated, new version not created, issue reopened', async () => { + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'closed', + type: 'closed', + issue: { + labels: [{name: 'StagingDeployCash'}], + number: '1234', + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + const testMockSteps = { + validate: mocks.FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS, + updateProduction: mocks.FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS, + updateStaging: mocks.FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS, + }; + const testMockJobs = { + createNewPatchVersion: { + steps: mocks.FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('finishReleaseCycle', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertValidateJobExecuted(result, '1234', true, false, false); + assertions.assertUpdateProductionJobExecuted(result, false); + assertions.assertCreateNewPatchVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + }); + }); + describe('issue does not have StagingDeployCash', () => { + it('validate job not run', async () => { + const repoPath = mockGithub.repo.getPath('testFinishReleaseCycleWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'closed', + type: 'closed', + issue: { + labels: [{name: 'Some'}, {name: 'Other'}, {name: 'Labels'}], + number: '1234', + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + const testMockSteps = { + validate: mocks.FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS, + updateProduction: mocks.FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS, + updateStaging: mocks.FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS, + }; + const testMockJobs = { + createNewPatchVersion: { + steps: mocks.FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'finishReleaseCycle.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('finishReleaseCycle', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertValidateJobExecuted(result, '1234', false); + assertions.assertUpdateProductionJobExecuted(result, false); + assertions.assertCreateNewPatchVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + }); + }); +}); diff --git a/workflow_tests/jest.config.js b/workflow_tests/jest.config.js new file mode 100644 index 000000000000..c8a4534764e3 --- /dev/null +++ b/workflow_tests/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + verbose: true, + transform: { + '^.+\\.jsx?$': 'babel-jest', + }, + clearMocks: true, + resetMocks: true, +}; diff --git a/workflow_tests/lint.test.js b/workflow_tests/lint.test.js new file mode 100644 index 000000000000..bc51f31b657c --- /dev/null +++ b/workflow_tests/lint.test.js @@ -0,0 +1,154 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/lintAssertions'); +const mocks = require('./mocks/lintMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'lint.yml'), + dest: '.github/workflows/lint.yml', + }, +]; + +describe('test workflow lint', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testLintWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + + // if any branches besides main are need add: pushedBranches: ['staging', 'production'], + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('event is workflow_call', () => { + const event = 'workflow_call'; + const eventOptions = {}; + it('runs the lint', async () => { + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + lint: mocks.LINT__LINT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('lint', expect.getState().currentTestName), + }); + + assertions.assertLintJobExecuted(result); + }); + describe('actor is OSBotify', () => { + const testActor = 'OSBotify'; + it('runs the lint', async () => { + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + lint: mocks.LINT__LINT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), + mockSteps: testMockSteps, + actor: testActor, + logFile: utils.getLogFilePath('lint', expect.getState().currentTestName), + }); + + assertions.assertLintJobExecuted(result); + }); + }); + }); + describe('event is pull_request', () => { + const event = 'pull_request'; + describe('pull_request is opened', () => { + const eventOptions = { + action: 'opened', + }; + it('runs the lint', async () => { + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + lint: mocks.LINT__LINT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('lint', expect.getState().currentTestName), + }); + + assertions.assertLintJobExecuted(result); + }); + describe('actor is OSBotify', () => { + const testActor = 'OSBotify'; + it('does not run the lint', async () => { + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + lint: mocks.LINT__LINT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), + mockSteps: testMockSteps, + actor: testActor, + logFile: utils.getLogFilePath('lint', expect.getState().currentTestName), + }); + + assertions.assertLintJobExecuted(result, false); + }); + }); + }); + describe('pull_request is synchronized', () => { + const eventOptions = { + action: 'synchronize', + }; + it('runs the lint', async () => { + const repoPath = mockGithub.repo.getPath('testLintWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lint.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + lint: mocks.LINT__LINT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lint.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('lint', expect.getState().currentTestName), + }); + + assertions.assertLintJobExecuted(result); + }); + }); + }); +}); diff --git a/workflow_tests/lockDeploys.test.js b/workflow_tests/lockDeploys.test.js new file mode 100644 index 000000000000..a57ed8847fd3 --- /dev/null +++ b/workflow_tests/lockDeploys.test.js @@ -0,0 +1,458 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/lockDeploysAssertions'); +const mocks = require('./mocks/lockDeploysMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'lockDeploys.yml'), + dest: '.github/workflows/lockDeploys.yml', + }, +]; + +describe('test workflow lockDeploys', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testLockDeploysWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('issue labeled', () => { + describe('label is LockCashDeploys', () => { + describe('issue has StagingDeployCash', () => { + describe('actor is not OSBotify', () => { + it('job triggered, comment left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: '🔐 LockCashDeploys 🔐', + }, + issue: { + labels: [{name: 'StagingDeployCash'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result); + }); + + it('one step fails, comment not left in StagingDeployCash, announced failure in Slack', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: '🔐 LockCashDeploys 🔐', + }, + issue: { + labels: [{name: 'StagingDeployCash'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + testMockSteps.lockStagingDeploys[1] = utils.createMockStep( + 'Wait for staging deploys to finish', + 'Waiting for staging deploys to finish', + 'LOCKSTAGINGDEPLOYS', + ['GITHUB_TOKEN'], + [], + null, + null, + false, + ); + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobFailedAfterFirstStep(result); + }); + }); + + describe('actor is OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: '🔐 LockCashDeploys 🔐', + }, + issue: { + labels: [{name: 'StagingDeployCash'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + }); + + describe('issue does not have StagingDeployCash', () => { + describe('actor is not OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: '🔐 LockCashDeploys 🔐', + }, + issue: { + labels: [{name: 'Some'}, {name: 'Other'}, {name: 'Labels'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + + describe('actor is OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: '🔐 LockCashDeploys 🔐', + }, + issue: { + labels: [{name: 'Some'}, {name: 'Other'}, {name: 'Labels'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + }); + }); + + describe('label is not LockCashDeploys', () => { + describe('issue has StagingDeployCash', () => { + describe('actor is not OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: 'Some different label', + }, + issue: { + labels: [{name: 'StagingDeployCash'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + + describe('actor is OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: 'Some different label', + }, + issue: { + labels: [{name: 'StagingDeployCash'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + }); + + describe('issue does not have StagingDeployCash', () => { + describe('actor is not OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: 'Some other label', + }, + issue: { + labels: [{name: 'Some'}, {name: 'Other'}, {name: 'Labels'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + + describe('actor is OSBotify', () => { + it('job not triggered, comment not left in StagingDeployCash', async () => { + const repoPath = mockGithub.repo.getPath('testLockDeploysWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'issues', + { + action: 'labeled', + type: 'labeled', + label: { + name: 'Some other label', + }, + issue: { + labels: [{name: 'Some'}, {name: 'Other'}, {name: 'Labels'}], + }, + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + ); + act = utils.setJobRunners( + act, + { + lockStagingDeploys: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + lockStagingDeploys: mocks.LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, + }; + const result = await act.runEvent('issues', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'lockDeploys.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('lockDeploys', expect.getState().currentTestName), + }); + + assertions.assertlockStagingDeploysJobExecuted(result, false); + }); + }); + }); + }); + }); +}); diff --git a/workflow_tests/mocks/authorChecklistMocks.js b/workflow_tests/mocks/authorChecklistMocks.js new file mode 100644 index 000000000000..db211f5ec1e6 --- /dev/null +++ b/workflow_tests/mocks/authorChecklistMocks.js @@ -0,0 +1,9 @@ +const utils = require('../utils/utils'); + +// checklist +const AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK = utils.createMockStep('authorChecklist.js', 'Running authorChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []); +const AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS = [AUTHORCHECKLIST__CHECKLIST__AUTHORCHECKLIST_JS__STEP_MOCK]; + +module.exports = { + AUTHORCHECKLIST__CHECKLIST__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/cherryPickMocks.js b/workflow_tests/mocks/cherryPickMocks.js new file mode 100644 index 000000000000..778e6fd48ded --- /dev/null +++ b/workflow_tests/mocks/cherryPickMocks.js @@ -0,0 +1,114 @@ +const utils = require('../utils/utils'); + +// validateactor +const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK = utils.createMockStep( + 'Check if user is deployer', + 'Checking if user is a deployer', + 'VALIDATEACTOR', + [], + ['GITHUB_TOKEN'], + {IS_DEPLOYER: true}, +); +const CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK = utils.createMockStep( + 'Check if user is deployer', + 'Checking if user is a deployer', + 'VALIDATEACTOR', + [], + ['GITHUB_TOKEN'], + {IS_DEPLOYER: false}, +); +const CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_TRUE__STEP_MOCK]; +const CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS = [CHERRYPICK__VALIDATEACTOR__CHECK_IF_USER_IS_DEPLOYER_FALSE__STEP_MOCK]; + +// createnewversion +const CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK = utils.createMockStep( + 'Create new version', + 'Creating new version', + 'CREATENEWVERSION', + [], + [], + {NEW_VERSION: '1.2.3'}, + null, + true, + 'createNewVersion', +); +const CHERRYPICK__CREATENEWVERSION__STEP_MOCKS = [CHERRYPICK__CREATENEWVERSION__CREATE_NEW_VERSION__STEP_MOCK]; + +// cherrypick +const CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK = utils.createMockStep('Checkout staging branch', 'Checking out staging branch', 'CHERRYPICK', ['ref', 'token'], []); +const CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep('Set up git for OSBotify', 'Setting up git for OSBotify', 'CHERRYPICK', ['GPG_PASSPHRASE'], []); +const CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK = utils.createMockStep('Get previous app version', 'Get previous app version', 'CHERRYPICK', ['SEMVER_LEVEL']); +const CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK = utils.createMockStep('Fetch history of relevant refs', 'Fetch history of relevant refs', 'CHERRYPICK'); +const CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK = utils.createMockStep('Get version bump commit', 'Get version bump commit', 'CHERRYPICK', [], [], { + VERSION_BUMP_SHA: 'version_bump_sha', +}); +const CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK = utils.createMockStep( + 'Get merge commit for pull request to CP', + 'Get merge commit for pull request to CP', + 'CHERRYPICK', + ['GITHUB_TOKEN', 'USER', 'PULL_REQUEST_NUMBER'], + [], + {MERGE_ACTOR: '@dummyauthor'}, +); +const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK = utils.createMockStep( + 'Cherry-pick the version-bump to staging', + 'Cherry-picking the version-bump to staging', + 'CHERRYPICK', + [], + [], +); +const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK = utils.createMockStep( + 'Cherry-pick the merge commit of target PR', + 'Cherry-picking the merge commit of target PR', + 'CHERRYPICK', + [], + [], + {HAS_CONFLICTS: false}, +); +// eslint-disable-next-line rulesdir/no-negated-variables +const CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK = utils.createMockStep( + 'Cherry-pick the merge commit of target PR', + 'Cherry-picking the merge commit of target PR', + 'CHERRYPICK', + [], + [], + {HAS_CONFLICTS: true}, +); +const CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK = utils.createMockStep('Push changes', 'Pushing changes', 'CHERRYPICK', [], []); +const CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK = utils.createMockStep( + 'Create Pull Request to manually finish CP', + 'Creating Pull Request to manually finish CP', + 'CHERRYPICK', + [], + ['GITHUB_TOKEN'], +); +const CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK = utils.createMockStep( + 'Announces a CP failure in the #announce Slack room', + 'Announcing a CP failure', + 'CHERRYPICK', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); + +const getCherryPickMockSteps = (upToDate, hasConflicts) => [ + CHERRYPICK__CHERRYPICK__CHECKOUT_STAGING_BRANCH__STEP_MOCK, + CHERRYPICK__CHERRYPICK__SET_UP_GIT_FOR_OSBOTIFY__STEP_MOCK, + CHERRYPICK__CHERRYPICK__GET_PREVIOUS_APP_VERSION__STEP_MOCK, + CHERRYPICK__CHERRYPICK__FETCH_HISTORY_OF_RELEVANT_REFS__STEP_MOCK, + CHERRYPICK__CHERRYPICK__GET_VERSION_BUMP_COMMIT__STEP_MOCK, + CHERRYPICK__CHERRYPICK__GET_MERGE_COMMIT_FOR_PULL_REQUEST_TO_CP__STEP_MOCK, + CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_VERSION_BUMP_TO_STAGING__STEP_MOCK, + hasConflicts + ? CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_CONFLICTS__STEP_MOCK + : CHERRYPICK__CHERRYPICK__CHERRY_PICK_THE_MERGE_COMMIT_OF_TARGET_PR_TO_NEW_BRANCH__HAS_NO_CONFLICTS__STEP_MOCK, + CHERRYPICK__CHERRYPICK__PUSH_CHANGES__STEP_MOCK, + CHERRYPICK__CHERRYPICK__CREATE_PULL_REQUEST_TO_MANUALLY_FINISH_CP__STEP_MOCK, + CHERRYPICK__CHERRYPICK__ANNOUNCES_A_CP_FAILURE_IN_THE_ANNOUNCE_SLACK_ROOM__STEP_MOCK, +]; + +module.exports = { + CHERRYPICK__VALIDATEACTOR__TRUE__STEP_MOCKS, + CHERRYPICK__VALIDATEACTOR__FALSE__STEP_MOCKS, + CHERRYPICK__CREATENEWVERSION__STEP_MOCKS, + getCherryPickMockSteps, +}; diff --git a/workflow_tests/mocks/claMocks.js b/workflow_tests/mocks/claMocks.js new file mode 100644 index 000000000000..d0a6793b93e6 --- /dev/null +++ b/workflow_tests/mocks/claMocks.js @@ -0,0 +1,25 @@ +const utils = require('../utils/utils'); + +// cla +const CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK = utils.createMockStep('CLA comment check', 'CLA comment check', 'CLA', ['text', 'regex'], [], {match: ''}); +const CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK = utils.createMockStep('CLA comment check', 'CLA comment check', 'CLA', ['text', 'regex'], [], { + match: 'I have read the CLA Document and I hereby sign the CLA', +}); +const CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK = utils.createMockStep('CLA comment re-check', 'CLA comment re-check', 'CLA', ['text', 'regex'], [], {match: ''}); +const CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK = utils.createMockStep('CLA comment re-check', 'CLA comment re-check', 'CLA', ['text', 'regex'], [], {match: 'recheck'}); +const CLA__CLA__CLA_ASSISTANT__STEP_MOCK = utils.createMockStep( + 'CLA Assistant', + 'CLA Assistant', + 'CLA', + ['path-to-signatures', 'path-to-document', 'branch', 'remote-organization-name', 'remote-repository-name', 'lock-pullrequest-aftermerge', 'allowlist'], + ['GITHUB_TOKEN', 'PERSONAL_ACCESS_TOKEN'], +); +const CLA__CLA__NO_MATCHES__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK]; +const CLA__CLA__CHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK]; +const CLA__CLA__RECHECK_MATCH__STEP_MOCKS = [CLA__CLA__CLA_COMMENT_CHECK__NO_MATCH__STEP_MOCK, CLA__CLA__CLA_COMMENT_RE_CHECK__MATCH__STEP_MOCK, CLA__CLA__CLA_ASSISTANT__STEP_MOCK]; + +module.exports = { + CLA__CLA__NO_MATCHES__STEP_MOCKS, + CLA__CLA__CHECK_MATCH__STEP_MOCKS, + CLA__CLA__RECHECK_MATCH__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/createNewVersionMocks.js b/workflow_tests/mocks/createNewVersionMocks.js new file mode 100644 index 000000000000..a1f601aef47f --- /dev/null +++ b/workflow_tests/mocks/createNewVersionMocks.js @@ -0,0 +1,58 @@ +const utils = require('../utils/utils'); + +// validateactor +const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK = utils.createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], { + PERMISSION: 'admin', +}); +const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK = utils.createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], { + PERMISSION: 'write', +}); +const CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK = utils.createMockStep('Get user permissions', 'Get user permissions', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], { + PERMISSION: 'read', +}); +const CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__ADMIN__STEP_MOCK]; +const CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__WRITE__STEP_MOCK]; +const CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS = [CREATENEWVERSION__VALIDATEACTOR__GET_USER_PERMISSIONS__NONE__STEP_MOCK]; + +// createnewversion +const CREATENEWVERSION__CREATENEWVERSION__RUN_TURNSTYLE__STEP_MOCK = utils.createMockStep('Run turnstyle', 'Run turnstyle', 'CREATENEWVERSION', ['poll-interval-seconds'], ['GITHUB_TOKEN']); +const CREATENEWVERSION__CREATENEWVERSION__CHECK_OUT__STEP_MOCK = utils.createMockStep('Check out', 'Check out', 'CREATENEWVERSION', ['ref', 'token'], []); +const CREATENEWVERSION__CREATENEWVERSION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep( + 'Setup git for OSBotify', + 'Setup git for OSBotify', + 'CREATENEWVERSION', + ['GPG_PASSPHRASE'], + [], +); +const CREATENEWVERSION__CREATENEWVERSION__GENERATE_VERSION__STEP_MOCK = utils.createMockStep( + 'Generate version', + 'Generate version', + 'CREATENEWVERSION', + ['GITHUB_TOKEN', 'SEMVER_LEVEL'], + [], +); +const CREATENEWVERSION__CREATENEWVERSION__COMMIT_NEW_VERSION__STEP_MOCK = utils.createMockStep('Commit new version', 'Commit new version', 'CREATENEWVERSION', [], []); +const CREATENEWVERSION__CREATENEWVERSION__UPDATE_MAIN_BRANCH__STEP_MOCK = utils.createMockStep('Update main branch', 'Update main branch', 'CREATENEWVERSION', [], []); +const CREATENEWVERSION__CREATENEWVERSION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep( + 'Announce failed workflow in Slack', + 'Announce failed workflow in Slack', + 'CREATENEWVERSION', + ['SLACK_WEBHOOK'], + [], +); +const CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS = [ + CREATENEWVERSION__CREATENEWVERSION__RUN_TURNSTYLE__STEP_MOCK, + CREATENEWVERSION__CREATENEWVERSION__CHECK_OUT__STEP_MOCK, + CREATENEWVERSION__CREATENEWVERSION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK, + CREATENEWVERSION__CREATENEWVERSION__GENERATE_VERSION__STEP_MOCK, + CREATENEWVERSION__CREATENEWVERSION__COMMIT_NEW_VERSION__STEP_MOCK, + CREATENEWVERSION__CREATENEWVERSION__UPDATE_MAIN_BRANCH__STEP_MOCK, + CREATENEWVERSION__CREATENEWVERSION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; + +module.exports = { + CREATENEWVERSION__VALIDATEACTOR__ADMIN__STEP_MOCKS, + CREATENEWVERSION__VALIDATEACTOR__WRITER__STEP_MOCKS, + CREATENEWVERSION__VALIDATEACTOR__NO_PERMISSION__STEP_MOCKS, + CREATENEWVERSION__CREATENEWVERSION__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/deployBlockerMocks.js b/workflow_tests/mocks/deployBlockerMocks.js new file mode 100644 index 000000000000..fbfc676f7701 --- /dev/null +++ b/workflow_tests/mocks/deployBlockerMocks.js @@ -0,0 +1,68 @@ +const utils = require('../utils/utils'); + +// deployblocker +const DEPLOYBLOCKER__DEPLOYBLOCKER__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'DEPLOYBLOCKER', ['token'], []); +const DEPLOYBLOCKER__DEPLOYBLOCKER__GET_URL_TITLE_AND_NUMBER_OF_NEW_DEPLOY_BLOCKER_ISSUE__STEP_MOCK = utils.createMockStep( + 'Get URL, title, & number of new deploy blocker (issue)', + 'Get URL, title and number of new deploy blocker - issue', + 'DEPLOYBLOCKER', + [], + ['TITLE'], + {}, + { + // eslint-disable-next-line no-template-curly-in-string + DEPLOY_BLOCKER_URL: '${{ github.event.issue.html_url }}', + // eslint-disable-next-line no-template-curly-in-string + DEPLOY_BLOCKER_NUMBER: '${{ github.event.issue.number }}', + // eslint-disable-next-line no-template-curly-in-string + DEPLOY_BLOCKER_TITLE: '${{ github.event.issue.title }}', + }, +); +const DEPLOYBLOCKER__DEPLOYBLOCKER__UPDATE_STAGINGDEPLOYCASH_WITH_NEW_DEPLOY_BLOCKER__STEP_MOCK = utils.createMockStep( + 'Update StagingDeployCash with new deploy blocker', + 'Update StagingDeployCash with new deploy blocker', + 'DEPLOYBLOCKER', + ['GITHUB_TOKEN'], + [], +); +const DEPLOYBLOCKER__DEPLOYBLOCKER__GIVE_THE_ISSUE_OR_PR_THE_HOURLY_ENGINEERING_LABELS__STEP_MOCK = utils.createMockStep( + 'Give the issue/PR the Hourly, Engineering labels', + 'Give the issue/PR the Hourly, Engineering labels', + 'DEPLOYBLOCKER', + ['add-labels', 'remove-labels'], + [], +); +const DEPLOYBLOCKER__DEPLOYBLOCKER__POST_THE_ISSUE_IN_THE_EXPENSIFY_OPEN_SOURCE_SLACK_ROOM__STEP_MOCK = utils.createMockStep( + 'Post the issue in the #expensify-open-source slack room', + 'Post the issue in the expensify-open-source slack room', + 'DEPLOYBLOCKER', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); +const DEPLOYBLOCKER__DEPLOYBLOCKER__COMMENT_ON_DEFERRED_PR__STEP_MOCK = utils.createMockStep( + 'Comment on deferred PR', + 'Comment on deferred PR', + 'DEPLOYBLOCKER', + ['github_token', 'number'], + [], +); +const DEPLOYBLOCKER__DEPLOYBLOCKER__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep( + 'Announce failed workflow in Slack', + 'Announce failed workflow in Slack', + 'DEPLOYBLOCKER', + ['SLACK_WEBHOOK'], + [], +); +const DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS = [ + DEPLOYBLOCKER__DEPLOYBLOCKER__CHECKOUT__STEP_MOCK, + DEPLOYBLOCKER__DEPLOYBLOCKER__GET_URL_TITLE_AND_NUMBER_OF_NEW_DEPLOY_BLOCKER_ISSUE__STEP_MOCK, + DEPLOYBLOCKER__DEPLOYBLOCKER__UPDATE_STAGINGDEPLOYCASH_WITH_NEW_DEPLOY_BLOCKER__STEP_MOCK, + DEPLOYBLOCKER__DEPLOYBLOCKER__GIVE_THE_ISSUE_OR_PR_THE_HOURLY_ENGINEERING_LABELS__STEP_MOCK, + DEPLOYBLOCKER__DEPLOYBLOCKER__POST_THE_ISSUE_IN_THE_EXPENSIFY_OPEN_SOURCE_SLACK_ROOM__STEP_MOCK, + DEPLOYBLOCKER__DEPLOYBLOCKER__COMMENT_ON_DEFERRED_PR__STEP_MOCK, + DEPLOYBLOCKER__DEPLOYBLOCKER__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; + +module.exports = { + DEPLOYBLOCKER__DEPLOYBLOCKER__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/deployMocks.js b/workflow_tests/mocks/deployMocks.js new file mode 100644 index 000000000000..dfec48ca7dc3 --- /dev/null +++ b/workflow_tests/mocks/deployMocks.js @@ -0,0 +1,44 @@ +const utils = require('../utils/utils'); + +const DEPLOY_STAGING__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout staging branch', 'Checking out staging branch', 'DEPLOY_STAGING', ['ref', 'token']); +const DEPLOY_STAGING__SETUP_GIT__STEP_MOCK = utils.createMockStep('Setup git for OSBotify', 'Setting up git for OSBotify', 'DEPLOY_STAGING', ['GPG_PASSPHRASE']); +const DEPLOY_STAGING__TAG_VERSION__STEP_MOCK = utils.createMockStep('Tag version', 'Tagging new version', 'DEPLOY_STAGING'); +const DEPLOY_STAGING__PUSH_TAG__STEP_MOCK = utils.createMockStep('🚀 Push tags to trigger staging deploy 🚀', 'Pushing tag to trigger staging deploy', 'DEPLOY_STAGING'); +const DEPLOY_STAGING_STEP_MOCKS = [DEPLOY_STAGING__CHECKOUT__STEP_MOCK, DEPLOY_STAGING__SETUP_GIT__STEP_MOCK, DEPLOY_STAGING__TAG_VERSION__STEP_MOCK, DEPLOY_STAGING__PUSH_TAG__STEP_MOCK]; + +const DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'DEPLOY_PRODUCTION', ['ref', 'token']); +const DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK = utils.createMockStep('Setup git for OSBotify', 'Setting up git for OSBotify', 'DEPLOY_PRODUCTION', ['GPG_PASSPHRASE']); +const DEPLOY_PRODUCTION__CURRENT_APP_VERSION__STEP_MOCK = utils.createMockStep('Get current app version', 'Getting current app version', 'DEPLOY_PRODUCTION', null, null, null, { + PRODUCTION_VERSION: '1.2.3', +}); +const DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK = utils.createMockStep( + 'Get Release Pull Request List', + 'Getting release PR list', + 'DEPLOY_PRODUCTION', + ['TAG', 'GITHUB_TOKEN', 'IS_PRODUCTION_DEPLOY'], + null, + {PR_LIST: '["1.2.1", "1.2.2"]'}, +); +const DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK = utils.createMockStep('Generate Release Body', 'Generating release body', 'DEPLOY_PRODUCTION', ['PR_LIST'], null, { + RELEASE_BODY: 'Release body', +}); +const DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK = utils.createMockStep( + '🚀 Create release to trigger production deploy 🚀', + 'Creating release to trigger production deploy', + 'DEPLOY_PRODUCTION', + ['tag_name', 'body'], + ['GITHUB_TOKEN'], +); +const DEPLOY_PRODUCTION_STEP_MOCKS = [ + DEPLOY_PRODUCTION__CHECKOUT__STEP_MOCK, + DEPLOY_PRODUCTION__SETUP_GIT__STEP_MOCK, + DEPLOY_PRODUCTION__CURRENT_APP_VERSION__STEP_MOCK, + DEPLOY_PRODUCTION__RELEASE_PR_LIST__STEP_MOCK, + DEPLOY_PRODUCTION__GENERATE_RELEASE_BODY__STEP_MOCK, + DEPLOY_PRODUCTION__CREATE_RELEASE__STEP_MOCK, +]; + +module.exports = { + DEPLOY_STAGING_STEP_MOCKS, + DEPLOY_PRODUCTION_STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/finishReleaseCycleMocks.js b/workflow_tests/mocks/finishReleaseCycleMocks.js new file mode 100644 index 000000000000..e1bb0d112429 --- /dev/null +++ b/workflow_tests/mocks/finishReleaseCycleMocks.js @@ -0,0 +1,165 @@ +const utils = require('../utils/utils'); + +// validate +const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_TRUE__STEP_MOCK = utils.createMockStep( + 'Validate actor is deployer', + 'Validating if actor is deployer', + 'VALIDATE', + [], + ['GITHUB_TOKEN'], + {IS_DEPLOYER: true}, +); +const FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_FALSE__STEP_MOCK = utils.createMockStep( + 'Validate actor is deployer', + 'Validating if actor is deployer', + 'VALIDATE', + [], + ['GITHUB_TOKEN'], + {IS_DEPLOYER: false}, +); +// eslint-disable-next-line rulesdir/no-negated-variables +const FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK = utils.createMockStep( + 'Reopen and comment on issue (not a team member)', + 'Reopening issue - not a team member', + 'VALIDATE', + ['GITHUB_TOKEN', 'ISSUE_NUMBER', 'COMMENT'], + [], +); +const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK = utils.createMockStep( + 'Check for any deploy blockers', + 'Checking for deploy blockers', + 'VALIDATE', + ['GITHUB_TOKEN', 'ISSUE_NUMBER'], + [], + {HAS_DEPLOY_BLOCKERS: false}, +); +const FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK = utils.createMockStep( + 'Check for any deploy blockers', + 'Checking for deploy blockers', + 'VALIDATE', + ['GITHUB_TOKEN', 'ISSUE_NUMBER'], + [], + {HAS_DEPLOY_BLOCKERS: true}, +); +const FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK = utils.createMockStep( + 'Reopen and comment on issue (has blockers)', + 'Reopening issue - blockers', + 'VALIDATE', + ['GITHUB_TOKEN', 'ISSUE_NUMBER'], + [], +); +const FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep( + 'Announce failed workflow in Slack', + 'Announce failed workflow in Slack', + 'VALIDATE', + ['SLACK_WEBHOOK'], + [], +); +const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [ + FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_TRUE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; +const FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [ + FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_TRUE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; +// eslint-disable-next-line rulesdir/no-negated-variables +const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS = [ + FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_FALSE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_FALSE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; +// eslint-disable-next-line rulesdir/no-negated-variables +const FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_BLOCKERS__STEP_MOCKS = [ + FINISHRELEASECYCLE__VALIDATE__VALIDATE_ACTOR_IS_DEPLOYER_FALSE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_NOT_A_TEAM_MEMBER__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__CHECK_FOR_ANY_DEPLOY_BLOCKERS_TRUE__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__REOPEN_AND_COMMENT_ON_ISSUE_HAS_BLOCKERS__STEP_MOCK, + FINISHRELEASECYCLE__VALIDATE__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; + +// updateproduction +const FINISHRELEASECYCLE__UPDATEPRODUCTION__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'UPDATEPRODUCTION', ['ref', 'token'], []); +const FINISHRELEASECYCLE__UPDATEPRODUCTION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep( + 'Setup Git for OSBotify', + 'Setup Git for OSBotify', + 'UPDATEPRODUCTION', + ['GPG_PASSPHRASE'], + [], +); +const FINISHRELEASECYCLE__UPDATEPRODUCTION__UPDATE_PRODUCTION_BRANCH__STEP_MOCK = utils.createMockStep('Update production branch', 'Updating production branch', 'UPDATEPRODUCTION', [], []); +const FINISHRELEASECYCLE__UPDATEPRODUCTION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep( + 'Announce failed workflow in Slack', + 'Announce failed workflow in Slack', + 'UPDATEPRODUCTION', + ['SLACK_WEBHOOK'], + [], +); +const FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS = [ + FINISHRELEASECYCLE__UPDATEPRODUCTION__CHECKOUT__STEP_MOCK, + FINISHRELEASECYCLE__UPDATEPRODUCTION__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK, + FINISHRELEASECYCLE__UPDATEPRODUCTION__UPDATE_PRODUCTION_BRANCH__STEP_MOCK, + FINISHRELEASECYCLE__UPDATEPRODUCTION__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; + +// createnewpatchversion +const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK = utils.createMockStep( + 'Create new version', + 'Creating new version', + 'CREATENEWPATCHVERSION', + ['SEMVER_LEVEL'], + [], + {NEW_VERSION: '1.2.3'}, + null, + true, + 'createNewVersion', +); +const FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS = [FINISHRELEASECYCLE__CREATENEWPATCHVERSION__CREATE_NEW_VERSION__STEP_MOCK]; + +// updatestaging +const FINISHRELEASECYCLE__UPDATESTAGING__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'UPDATESTAGING', ['ref', 'token'], []); +const FINISHRELEASECYCLE__UPDATESTAGING__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK = utils.createMockStep( + 'Setup Git for OSBotify', + 'Setup Git for OSBotify', + 'UPDATESTAGING', + ['GPG_PASSPHRASE'], + [], +); +const FINISHRELEASECYCLE__UPDATESTAGING__UPDATE_STAGING_BRANCH_TO_TRIGGER_STAGING_DEPLOY__STEP_MOCK = utils.createMockStep( + 'Update staging branch to trigger staging deploy', + 'Updating staging branch', + 'UPDATESTAGING', + [], + [], +); +const FINISHRELEASECYCLE__UPDATESTAGING__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK = utils.createMockStep( + 'Announce failed workflow in Slack', + 'Announce failed workflow in Slack', + 'UPDATESTAGING', + ['SLACK_WEBHOOK'], + [], +); +const FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS = [ + FINISHRELEASECYCLE__UPDATESTAGING__CHECKOUT__STEP_MOCK, + FINISHRELEASECYCLE__UPDATESTAGING__SETUP_GIT_FOR_OSBOTIFY__STEP_MOCK, + FINISHRELEASECYCLE__UPDATESTAGING__UPDATE_STAGING_BRANCH_TO_TRIGGER_STAGING_DEPLOY__STEP_MOCK, + FINISHRELEASECYCLE__UPDATESTAGING__ANNOUNCE_FAILED_WORKFLOW_IN_SLACK__STEP_MOCK, +]; + +module.exports = { + FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS, + FINISHRELEASECYCLE__VALIDATE__TEAM_MEMBER_BLOCKERS__STEP_MOCKS, + FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_NO_BLOCKERS__STEP_MOCKS, + FINISHRELEASECYCLE__VALIDATE__NOT_TEAM_MEMBER_BLOCKERS__STEP_MOCKS, + FINISHRELEASECYCLE__UPDATEPRODUCTION__STEP_MOCKS, + FINISHRELEASECYCLE__CREATENEWPATCHVERSION__STEP_MOCKS, + FINISHRELEASECYCLE__UPDATESTAGING__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/lintMocks.js b/workflow_tests/mocks/lintMocks.js new file mode 100644 index 000000000000..ecf11074e20f --- /dev/null +++ b/workflow_tests/mocks/lintMocks.js @@ -0,0 +1,17 @@ +const utils = require('../utils/utils'); + +// lint +const LINT__LINT__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'LINT', [], []); +const LINT__LINT__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'LINT', [], []); +const LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK = utils.createMockStep('Lint JavaScript and Typescript with ESLint', 'Lint JavaScript with ESLint', 'LINT', [], ['CI']); +const LINT__LINT__LINT_SHELL_SCRIPTS_WITH_SHELLCHECK__STEP_MOCK = utils.createMockStep('Lint shell scripts with ShellCheck', 'Lint shell scripts with ShellCheck', 'LINT', [], []); +const LINT__LINT__STEP_MOCKS = [ + LINT__LINT__CHECKOUT__STEP_MOCK, + LINT__LINT__SETUP_NODE__STEP_MOCK, + LINT__LINT__LINT_JAVASCRIPT_WITH_ESLINT__STEP_MOCK, + LINT__LINT__LINT_SHELL_SCRIPTS_WITH_SHELLCHECK__STEP_MOCK, +]; + +module.exports = { + LINT__LINT__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/lockDeploysMocks.js b/workflow_tests/mocks/lockDeploysMocks.js new file mode 100644 index 000000000000..bb6246a2e1d9 --- /dev/null +++ b/workflow_tests/mocks/lockDeploysMocks.js @@ -0,0 +1,35 @@ +const utils = require('../utils/utils'); + +// lockstagingdeploys +const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'LOCKSTAGINGDEPLOYS', ['ref', 'token'], []); +const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__WAIT_FOR_STAGING_DEPLOYS_TO_FINISH__STEP_MOCK = utils.createMockStep( + 'Wait for staging deploys to finish', + 'Waiting for staging deploys to finish', + 'LOCKSTAGINGDEPLOYS', + ['GITHUB_TOKEN'], + [], +); +const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__COMMENT_IN_STAGINGDEPLOYCASH_TO_GIVE_APPLAUSE_THE_GREEN_LIGHT_TO_BEGIN_QA__STEP_MOCK = utils.createMockStep( + 'Comment in StagingDeployCash to give Applause the 🟢 to begin QA', + 'Commenting in StagingDeployCash', + 'LOCKSTAGINGDEPLOYS', + [], + ['GITHUB_TOKEN'], +); +const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__ANNOUNCE_FAILED_WORKFLOW__STEP_MOCK = utils.createMockStep( + 'Announce failed workflow', + 'Announcing failed workflow in Slack', + 'LOCKSTAGINGDEPLOYS', + ['SLACK_WEBHOOK'], + [], +); +const LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS = [ + LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__CHECKOUT__STEP_MOCK, + LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__WAIT_FOR_STAGING_DEPLOYS_TO_FINISH__STEP_MOCK, + LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__COMMENT_IN_STAGINGDEPLOYCASH_TO_GIVE_APPLAUSE_THE_GREEN_LIGHT_TO_BEGIN_QA__STEP_MOCK, + LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__ANNOUNCE_FAILED_WORKFLOW__STEP_MOCK, +]; + +module.exports = { + LOCKDEPLOYS__LOCKSTAGINGDEPLOYS__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/platformDeployMocks.js b/workflow_tests/mocks/platformDeployMocks.js new file mode 100644 index 000000000000..9e0b91b29156 --- /dev/null +++ b/workflow_tests/mocks/platformDeployMocks.js @@ -0,0 +1,281 @@ +const utils = require('../utils/utils'); + +// validateActor +const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK = utils.createMockStep( + 'Check if user is deployer', + 'Checking if the user is a deployer', + 'VALIDATE_ACTOR', + [], + ['GITHUB_TOKEN'], + {IS_DEPLOYER: true}, +); +const PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK = utils.createMockStep( + 'Check if user is deployer', + 'Checking if the user is a deployer', + 'VALIDATE_ACTOR', + [], + ['GITHUB_TOKEN'], + {IS_DEPLOYER: false}, +); +const PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__TEAM_MEMBER__STEP_MOCK]; +const PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS = [PLATFORM_DEPLOY__VALIDATE_ACTOR__CHECK_USER_DEPLOYER__OUTSIDER__STEP_MOCK]; + +// deployChecklist +const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'DEPLOY_CHECKLIST'); +const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'DEPLOY_CHECKLIST'); +const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version', 'Set version', 'DEPLOY_CHECKLIST', [], [], {VERSION: '1.2.3'}); +const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__CREATE_OR_UPDATE_STAGING_DEPLOY__STEP_MOCK = utils.createMockStep( + 'Create or update staging deploy', + 'Create or update staging deploy', + 'DEPLOY_CHECKLIST', + ['GITHUB_TOKEN', 'NPM_VERSION'], +); +const PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS = [ + PLATFORM_DEPLOY__DEPLOY_CHECKLIST__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__DEPLOY_CHECKLIST__SETUP_NODE__STEP_MOCK, + PLATFORM_DEPLOY__DEPLOY_CHECKLIST__SET_VERSION__STEP_MOCK, + PLATFORM_DEPLOY__DEPLOY_CHECKLIST__CREATE_OR_UPDATE_STAGING_DEPLOY__STEP_MOCK, +]; + +// android +const PLATFORM_DEPLOY__ANDROID__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'ANDROID'); +const PLATFORM_DEPLOY__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'ANDROID'); +const PLATFORM_DEPLOY__ANDROID__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'ANDROID'); +const PLATFORM_DEPLOY__ANDROID__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setting up Ruby', 'ANDROID', ['ruby-version', 'bundler-cache']); +const PLATFORM_DEPLOY__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK = utils.createMockStep('Decrypt keystore', 'Decrypting keystore', 'ANDROID', null, ['LARGE_SECRET_PASSPHRASE']); +const PLATFORM_DEPLOY__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK = utils.createMockStep('Decrypt json key', 'Decrypting JSON key', 'ANDROID', null, ['LARGE_SECRET_PASSPHRASE']); +const PLATFORM_DEPLOY__ANDROID__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version in ENV', 'Setting version in ENV', 'ANDROID', null, null, null, {VERSION_CODE: '1.2.3'}); +const PLATFORM_DEPLOY__ANDROID__FASTLANE_BETA__STEP_MOCK = utils.createMockStep('Run Fastlane beta', 'Running Fastlane beta', 'ANDROID', null, [ + 'MYAPP_UPLOAD_STORE_PASSWORD', + 'MYAPP_UPLOAD_KEY_PASSWORD', +]); +const PLATFORM_DEPLOY__ANDROID__FASTLANE_PRODUCTION__STEP_MOCK = utils.createMockStep('Run Fastlane production', 'Running Fastlane production', 'ANDROID', null, ['VERSION']); +const PLATFORM_DEPLOY__ANDROID__ARCHIVE_SOURCEMAPS__STEP_MOCK = utils.createMockStep('Archive Android sourcemaps', 'Archiving Android sourcemaps', 'ANDROID', ['name', 'path']); +const PLATFORM_DEPLOY__ANDROID__UPLOAD_TO_BROWSER_STACK__STEP_MOCK = utils.createMockStep( + 'Upload Android version to Browser Stack', + 'Uploading Android version to Browser Stack', + 'ANDROID', + null, + ['BROWSERSTACK'], +); +const PLATFORM_DEPLOY__ANDROID__WARN_DEPLOYERS__STEP_MOCK = utils.createMockStep( + 'Warn deployers if Android production deploy failed', + 'Warning deployers of failed production deploy', + 'ANDROID', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); +const PLATFORM_DEPLOY__ANDROID__STEP_MOCKS = [ + PLATFORM_DEPLOY__ANDROID__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__SETUP_NODE__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__SETUP_RUBY__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__SET_VERSION__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__FASTLANE_BETA__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__FASTLANE_PRODUCTION__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__ARCHIVE_SOURCEMAPS__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__UPLOAD_TO_BROWSER_STACK__STEP_MOCK, + PLATFORM_DEPLOY__ANDROID__WARN_DEPLOYERS__STEP_MOCK, +]; + +// desktop +const PLATFORM_DEPLOY__DESKTOP__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'DESKTOP'); +const PLATFORM_DEPLOY__DESKTOP__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'DESKTOP'); +const PLATFORM_DEPLOY__DESKTOP__DECRYPT_ID__STEP_MOCK = utils.createMockStep('Decrypt Developer ID Certificate', 'Decrypting developer id certificate', 'DESKTOP', null, [ + 'DEVELOPER_ID_SECRET_PASSPHRASE', +]); +const PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK = utils.createMockStep('Build production desktop app', 'Building production desktop app', 'DESKTOP', null, [ + 'CSC_LINK', + 'CSC_KEY_PASSWORD', + 'APPLE_ID', + 'APPLE_APP_SPECIFIC_PASSWORD', + 'AWS_ACCESS_KEY_ID', + 'AWS_SECRET_ACCESS_KEY', +]); +const PLATFORM_DEPLOY__DESKTOP__BUILD_STAGING__STEP_MOCK = utils.createMockStep('Build staging desktop app', 'Building staging desktop app', 'DESKTOP', null, [ + 'CSC_LINK', + 'CSC_KEY_PASSWORD', + 'APPLE_ID', + 'APPLE_APP_SPECIFIC_PASSWORD', + 'AWS_ACCESS_KEY_ID', + 'AWS_SECRET_ACCESS_KEY', +]); +const PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS = [ + PLATFORM_DEPLOY__DESKTOP__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__DESKTOP__SETUP_NODE__STEP_MOCK, + PLATFORM_DEPLOY__DESKTOP__DECRYPT_ID__STEP_MOCK, + PLATFORM_DEPLOY__DESKTOP__BUILD_PRODUCTION__STEP_MOCK, + PLATFORM_DEPLOY__DESKTOP__BUILD_STAGING__STEP_MOCK, +]; + +// ios +const PLATFORM_DEPLOY__IOS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'IOS'); +const PLATFORM_DEPLOY__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'IOS'); +const PLATFORM_DEPLOY__IOS__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'IOS'); +const PLATFORM_DEPLOY__IOS__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setting up Ruby', 'IOS', ['ruby-version', 'bundler-cache']); +const PLATFORM_DEPLOY__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK = utils.createMockStep('Cache Pod dependencies', 'Cache Pod dependencies', 'IOS', ['path', 'key', 'restore-keys'], [], { + 'cache-hit': false, +}); +const PLATFORM_DEPLOY__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK = utils.createMockStep( + 'Compare Podfile.lock and Manifest.lock', + 'Compare Podfile.lock and Manifest.lock', + 'IOS', + [], + [], + {IS_PODFILE_SAME_AS_MANIFEST: false}, +); +const PLATFORM_DEPLOY__IOS__COCOAPODS__STEP_MOCK = utils.createMockStep('Install cocoapods', 'Installing cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command']); +const PLATFORM_DEPLOY__IOS__DECRYPT_PROFILE__STEP_MOCK = utils.createMockStep('Decrypt profile', 'Decrypting profile', 'IOS', null, ['LARGE_SECRET_PASSPHRASE']); +const PLATFORM_DEPLOY__IOS__DECRYPT_CERTIFICATE__STEP_MOCK = utils.createMockStep('Decrypt certificate', 'Decrypting certificate', 'IOS', null, ['LARGE_SECRET_PASSPHRASE']); +const PLATFORM_DEPLOY__IOS__DECRYPT_APP_STORE_API_KEY__STEP_MOCK = utils.createMockStep('Decrypt App Store Connect API key', 'Decrypting App Store API key', 'IOS', null, [ + 'LARGE_SECRET_PASSPHRASE', +]); +const PLATFORM_DEPLOY__IOS__FASTLANE__STEP_MOCK = utils.createMockStep('Run Fastlane', 'Running Fastlane', 'IOS', null, [ + 'APPLE_CONTACT_EMAIL', + 'APPLE_CONTACT_PHONE', + 'APPLE_DEMO_EMAIL', + 'APPLE_DEMO_PASSWORD', +]); +const PLATFORM_DEPLOY__IOS__ARCHIVE_SOURCEMAPS__STEP_MOCK = utils.createMockStep('Archive iOS sourcemaps', 'Archiving sourcemaps', 'IOS', ['name', 'path']); +const PLATFORM_DEPLOY__IOS__UPLOAD_BROWSERSTACK__STEP_MOCK = utils.createMockStep('Upload iOS version to Browser Stack', 'Uploading version to Browser Stack', 'IOS', null, ['BROWSERSTACK']); +const PLATFORM_DEPLOY__IOS__SET_VERSION__STEP_MOCK = utils.createMockStep('Set iOS version in ENV', 'Setting iOS version', 'IOS', null, null, null, {IOS_VERSION: '1.2.3'}); +const PLATFORM_DEPLOY__IOS__RELEASE_FASTLANE__STEP_MOCK = utils.createMockStep('Run Fastlane for App Store release', 'Running Fastlane for release', 'IOS', null, ['VERSION']); +const PLATFORM_DEPLOY__IOS__WARN_FAIL__STEP_MOCK = utils.createMockStep( + 'Warn deployers if iOS production deploy failed', + 'Warning developers of failed deploy', + 'IOS', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); +const PLATFORM_DEPLOY__IOS__STEP_MOCKS = [ + PLATFORM_DEPLOY__IOS__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK, + PLATFORM_DEPLOY__IOS__SETUP_NODE__STEP_MOCK, + PLATFORM_DEPLOY__IOS__SETUP_RUBY__STEP_MOCK, + PLATFORM_DEPLOY__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK, + PLATFORM_DEPLOY__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK, + PLATFORM_DEPLOY__IOS__COCOAPODS__STEP_MOCK, + PLATFORM_DEPLOY__IOS__DECRYPT_PROFILE__STEP_MOCK, + PLATFORM_DEPLOY__IOS__DECRYPT_CERTIFICATE__STEP_MOCK, + PLATFORM_DEPLOY__IOS__DECRYPT_APP_STORE_API_KEY__STEP_MOCK, + PLATFORM_DEPLOY__IOS__FASTLANE__STEP_MOCK, + PLATFORM_DEPLOY__IOS__ARCHIVE_SOURCEMAPS__STEP_MOCK, + PLATFORM_DEPLOY__IOS__UPLOAD_BROWSERSTACK__STEP_MOCK, + PLATFORM_DEPLOY__IOS__SET_VERSION__STEP_MOCK, + PLATFORM_DEPLOY__IOS__RELEASE_FASTLANE__STEP_MOCK, + PLATFORM_DEPLOY__IOS__WARN_FAIL__STEP_MOCK, +]; + +// web +const PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'WEB'); +const PLATFORM_DEPLOY__WEB__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'WEB'); +const PLATFORM_DEPLOY__WEB__CLOUDFLARE__STEP_MOCK = utils.createMockStep('Setup Cloudflare CLI', 'Setting up Cloudflare CLI', 'WEB'); +const PLATFORM_DEPLOY__WEB__AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep('Configure AWS Credentials', 'Configuring AWS credentials', 'WEB', [ + 'AWS_ACCESS_KEY_ID', + 'AWS_SECRET_ACCESS_KEY', +]); +const PLATFORM_DEPLOY__WEB__BUILD_PRODUCTION__STEP_MOCK = utils.createMockStep('Build web for production', 'Building web for production', 'WEB'); +const PLATFORM_DEPLOY__WEB__BUILD_STAGING__STEP_MOCK = utils.createMockStep('Build web for staging', 'Building web for staging', 'WEB'); +const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_PRODUCTION__STEP_MOCK = utils.createMockStep('Build storybook docs for production', 'Build storybook docs for production', 'WEB'); +const PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_STAGING__STEP_MOCK = utils.createMockStep('Build storybook docs for staging', 'Build storybook docs for staging', 'WEB'); +const PLATFORM_DEPLOY__WEB__DEPLOY_PRODUCTION_S3__STEP_MOCK = utils.createMockStep('Deploy production to S3', 'Deploying production to S3', 'WEB'); +const PLATFORM_DEPLOY__WEB__DEPLOY_STAGING_S3__STEP_MOCK = utils.createMockStep('Deploy staging to S3', 'Deploying staging to S3', 'WEB'); +const PLATFORM_DEPLOY__WEB__PURGE_PRODUCTION_CACHE__STEP_MOCK = utils.createMockStep('Purge production Cloudflare cache', 'Purging production Cloudflare cache', 'WEB', null, ['CF_API_KEY']); +const PLATFORM_DEPLOY__WEB__PURGE_STAGING_CACHE__STEP_MOCK = utils.createMockStep('Purge staging Cloudflare cache', 'Purging staging Cloudflare cache', 'WEB', null, ['CF_API_KEY']); +const PLATFORM_DEPLOY__WEB__STEP_MOCKS = [ + PLATFORM_DEPLOY__WEB__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__WEB__SETUP_NODE__STEP_MOCK, + PLATFORM_DEPLOY__WEB__CLOUDFLARE__STEP_MOCK, + PLATFORM_DEPLOY__WEB__AWS_CREDENTIALS__STEP_MOCK, + PLATFORM_DEPLOY__WEB__BUILD_PRODUCTION__STEP_MOCK, + PLATFORM_DEPLOY__WEB__BUILD_STAGING__STEP_MOCK, + PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_PRODUCTION__STEP_MOCK, + PLATFORM_DEPLOY__WEB__BUILD_STORYBOOK_DOCS_FOR_STAGING__STEP_MOCK, + PLATFORM_DEPLOY__WEB__DEPLOY_PRODUCTION_S3__STEP_MOCK, + PLATFORM_DEPLOY__WEB__DEPLOY_STAGING_S3__STEP_MOCK, + PLATFORM_DEPLOY__WEB__PURGE_PRODUCTION_CACHE__STEP_MOCK, + PLATFORM_DEPLOY__WEB__PURGE_STAGING_CACHE__STEP_MOCK, +]; + +// post slack message on failure +const PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK = utils.createMockStep('Post Slack message on failure', 'Posting Slack message on platform deploy failure', 'POST_SLACK_FAIL', [ + 'SLACK_WEBHOOK', +]); +const PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS = [PLATFORM_DEPLOY__POST_SLACK_FAIL__POST_SLACK__STEP_MOCK]; + +// post slack message on success +const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'POST_SLACK_SUCCESS'); +const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version', 'Setting version', 'POST_SLACK_SUCCESS', null, null, null, {VERSION: '1.2.3'}); +const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__ANNOUNCE_CHANNEL__STEP_MOCK = utils.createMockStep( + 'Announces the deploy in the #announce Slack room', + 'Posting message to \\#announce channel', + 'POST_SLACK_SUCCESS', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); +const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__DEPLOYER_CHANNEL__STEP_MOCK = utils.createMockStep( + 'Announces the deploy in the #deployer Slack room', + 'Posting message to \\#deployer channel', + 'POST_SLACK_SUCCESS', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); +const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__EXPENSIFY_CHANNEL__STEP_MOCK = utils.createMockStep( + 'Announces a production deploy in the #expensify-open-source Slack room', + 'Posting message to \\#expensify-open-source channel', + 'POST_SLACK_SUCCESS', + ['status'], + ['GITHUB_TOKEN', 'SLACK_WEBHOOK_URL'], +); +const PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS = [ + PLATFORM_DEPLOY__POST_SLACK_SUCCESS__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__POST_SLACK_SUCCESS__SET_VERSION__STEP_MOCK, + PLATFORM_DEPLOY__POST_SLACK_SUCCESS__ANNOUNCE_CHANNEL__STEP_MOCK, + PLATFORM_DEPLOY__POST_SLACK_SUCCESS__DEPLOYER_CHANNEL__STEP_MOCK, + PLATFORM_DEPLOY__POST_SLACK_SUCCESS__EXPENSIFY_CHANNEL__STEP_MOCK, +]; + +// post github comment +const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checking out', 'POST_GITHUB_COMMENT'); +const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setting up Node', 'POST_GITHUB_COMMENT'); +const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SET_VERSION__STEP_MOCK = utils.createMockStep('Set version', 'Setting version', 'POST_GITHUB_COMMENT', null, null, null, {VERSION: '1.2.3'}); +const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK = utils.createMockStep( + 'Get Release Pull Request List', + 'Getting release pull request list', + 'POST_GITHUB_COMMENT', + ['TAG', 'GITHUB_TOKEN', 'IS_PRODUCTION_DEPLOY'], + null, + {PR_LIST: '[1.2.1, 1.2.2]'}, +); +const PLATFORM_DEPLOY__POST_GIHUB_COMMENT__COMMENT__STEP_MOCK = utils.createMockStep('Comment on issues', 'Commenting on issues', 'POST_GITHUB_COMMENT', [ + 'PR_LIST', + 'IS_PRODUCTION_DEPLOY', + 'DEPLOY_VERSION', + 'GITHUB_TOKEN', + 'ANDROID', + 'DESKTOP', + 'IOS', + 'WEB', +]); +const PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS = [ + PLATFORM_DEPLOY__POST_GIHUB_COMMENT__CHECKOUT__STEP_MOCK, + PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SETUP_NODE__STEP_MOCK, + PLATFORM_DEPLOY__POST_GIHUB_COMMENT__SET_VERSION__STEP_MOCK, + PLATFORM_DEPLOY__POST_GIHUB_COMMENT__GET_PR_LIST__STEP_MOCK, + PLATFORM_DEPLOY__POST_GIHUB_COMMENT__COMMENT__STEP_MOCK, +]; + +module.exports = { + PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS, + PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS, + PLATFORM_DEPLOY__ANDROID__STEP_MOCKS, + PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS, + PLATFORM_DEPLOY__IOS__STEP_MOCKS, + PLATFORM_DEPLOY__WEB__STEP_MOCKS, + PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS, + PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS, + PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS, + PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/preDeployMocks.js b/workflow_tests/mocks/preDeployMocks.js new file mode 100644 index 000000000000..3dc67a904cf9 --- /dev/null +++ b/workflow_tests/mocks/preDeployMocks.js @@ -0,0 +1,201 @@ +const utils = require('../utils/utils'); + +// typecheck +const TYPECHECK_WORKFLOW_MOCK_STEP = utils.createMockStep('Run typecheck workflow', 'Running typecheck workflow', 'TYPECHECK'); +const TYPECHECK_JOB_MOCK_STEPS = [TYPECHECK_WORKFLOW_MOCK_STEP]; + +// lint +const LINT_WORKFLOW_MOCK_STEP = utils.createMockStep('Run lint workflow', 'Running lint workflow', 'LINT'); +const LINT_JOB_MOCK_STEPS = [LINT_WORKFLOW_MOCK_STEP]; + +// test +const TEST_WORKFLOW_MOCK_STEP = utils.createMockStep('Run test workflow', 'Running test workflow', 'TEST'); +const TEST_JOB_MOCK_STEPS = [TEST_WORKFLOW_MOCK_STEP]; + +// confirm_passing_build +const ANNOUNCE_IN_SLACK_MOCK_STEP = utils.createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in slack', 'CONFIRM_PASSING_BUILD', ['SLACK_WEBHOOK']); +const CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS = [ + ANNOUNCE_IN_SLACK_MOCK_STEP, + + // 2nd step runs normally +]; + +// choose_deploy_actions +const GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY = utils.createMockStep('Get merged pull request', 'Getting merged pull request', 'CHOOSE_DEPLOY_ACTIONS', ['github_token'], null, { + number: '123', + labels: '[]', +}); +const CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED = utils.createMockStep( + 'Check if StagingDeployCash is locked', + 'Checking StagingDeployCash', + 'CHOOSE_DEPLOY_ACTIONS', + ['GITHUB_TOKEN'], + null, + {IS_LOCKED: true}, +); +const CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED = utils.createMockStep( + 'Check if StagingDeployCash is locked', + 'Checking StagingDeployCash', + 'CHOOSE_DEPLOY_ACTIONS', + ['GITHUB_TOKEN'], + null, + {IS_LOCKED: false}, +); +const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED = [ + GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY, + CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__LOCKED, + + // step 3 runs normally +]; +const CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED = [ + GET_MERGED_PULL_REQUEST_MOCK_STEP__CHOOSE_DEPLOY, + CHECK_IF_STAGINGDEPLOYCASH_IS_LOCKED_MOCK_STEP__UNLOCKED, + + // step 3 runs normally +]; + +// skip_deploy +const COMMENT_ON_DEFERRED_PR_MOCK_STEP = utils.createMockStep('Comment on deferred PR', 'Skipping deploy', 'SKIP_DEPLOY', ['github_token', 'number', 'body']); +const SKIP_DEPLOY_JOB_MOCK_STEPS = [COMMENT_ON_DEFERRED_PR_MOCK_STEP]; + +// create_new_version +const CREATE_NEW_VERSION_MOCK_STEP = utils.createMockStep( + 'Create new version', + 'Creating new version', + 'CREATE_NEW_VERSION', + null, + null, + {NEW_VERSION: '1.2.3'}, + null, + true, + 'createNewVersion', +); +const CREATE_NEW_VERSION_JOB_MOCK_STEPS = [CREATE_NEW_VERSION_MOCK_STEP]; + +// update_staging +const RUN_TURNSTYLE_MOCK_STEP = utils.createMockStep('Run turnstyle', 'Running turnstyle', 'UPDATE_STAGING', ['poll-interval-seconds'], ['GITHUB_TOKEN']); +const CHECKOUT_MAIN_MOCK_STEP = utils.createMockStep('Checkout main', 'Checkout main', 'UPDATE_STAGING', ['ref', 'token']); +const SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP = utils.createMockStep('Setup Git for OSBotify', 'Setup Git for OSBotify', 'UPDATE_STAGING', ['GPG_PASSPHRASE']); +const UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP = utils.createMockStep('Update staging branch from main', 'Update staging branch from main', 'UPDATE_STAGING'); +const ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP = utils.createMockStep('Announce failed workflow in Slack', 'Announcing failed workflow in Slack', 'UPDATE_STAGING', ['SLACK_WEBHOOK']); +const UPDATE_STAGING_JOB_MOCK_STEPS = [ + RUN_TURNSTYLE_MOCK_STEP, + CHECKOUT_MAIN_MOCK_STEP, + SETUP_GIT_FOR_OSBOTIFY_MOCK_STEP, + UPDATE_STAGING_BRANCH_FROM_MAIN_MOCK_STEP, + ANNOUNCE_FAILED_WORKFLOW_IN_SLACK_MOCK_STEP, +]; + +// is_expensify_employee +const GET_MERGED_PULL_REQUEST_MOCK_STEP__IS_EXPENSIFY_EMPLOYEE = utils.createMockStep( + 'Get merged pull request', + 'Getting merged pull request', + 'IS_EXPENSIFY_EMPLOYEE', + ['github_token'], + null, + {author: 'Dummy Author'}, +); +const CHECK_TEAM_MEMBERSHIP_MOCK_STEP__TRUE = utils.createMockStep( + 'Check whether the PR author is member of Expensify/expensify team', + 'Checking actors Expensify membership', + 'IS_EXPENSIFY_EMPLOYEE', + [], + ['GITHUB_TOKEN'], + {IS_EXPENSIFY_EMPLOYEE: true}, +); +const IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE = [GET_MERGED_PULL_REQUEST_MOCK_STEP__IS_EXPENSIFY_EMPLOYEE, CHECK_TEAM_MEMBERSHIP_MOCK_STEP__TRUE]; +const CHECK_TEAM_MEMBERSHIP_MOCK_STEP__FALSE = utils.createMockStep( + 'Check whether the PR author is member of Expensify/expensify team', + 'Checking actors Expensify membership', + 'IS_EXPENSIFY_EMPLOYEE', + [], + ['GITHUB_TOKEN'], + {IS_EXPENSIFY_EMPLOYEE: false}, +); +const IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__FALSE = [GET_MERGED_PULL_REQUEST_MOCK_STEP__IS_EXPENSIFY_EMPLOYEE, CHECK_TEAM_MEMBERSHIP_MOCK_STEP__FALSE]; + +// new_contributor_welcome_message +const CHECKOUT_MOCK_STEP = utils.createMockStep('Checkout', 'Checking out', 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', ['token'], null, {author: 'Dummy Author'}); +const CHECKOUT_MOCK_STEP__OSBOTIFY = utils.createMockStep('Checkout', 'Checking out', 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', ['token'], null, {author: 'OSBotify'}); +const GET_MERGED_PULL_REQUEST_MOCK_STEP__WELCOME_MESSAGE = utils.createMockStep( + 'Get merged pull request', + 'Getting merged pull request', + 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', + ['github_token'], + null, + {number: '12345', author: 'Dummy Author'}, +); +const GET_MERGED_PULL_REQUEST_MOCK_STEP__WELCOME_MESSAGE__OSBOTIFY = utils.createMockStep( + 'Get merged pull request', + 'Getting merged pull request', + 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', + ['github_token'], + null, + {number: '12345', author: 'OSBotify'}, +); +const GET_PR_COUNT_MOCK_STEP__1 = utils.createMockStep( + // eslint-disable-next-line no-template-curly-in-string + 'Get PR count for ${{ steps.getMergedPullRequest.outputs.author }}', + 'Getting PR count', + 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', + null, + ['GITHUB_TOKEN'], + null, + {PR_COUNT: '1'}, +); +const GET_PR_COUNT_MOCK_STEP__10 = utils.createMockStep( + // eslint-disable-next-line no-template-curly-in-string + 'Get PR count for ${{ steps.getMergedPullRequest.outputs.author }}', + 'Getting PR count', + 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', + null, + ['GITHUB_TOKEN'], + null, + {PR_COUNT: '10'}, +); +const COMMENT_ON_FIRST_PULL_REQUEST_MOCK_STEP = utils.createMockStep( + // eslint-disable-next-line no-template-curly-in-string + "Comment on ${{ steps.getMergedPullRequest.outputs.author }}\\'s first pull request!", + 'Creating comment', + 'NEW_CONTRIBUTOR_WELCOME_MESSAGE', + ['github_token', 'number', 'body'], +); +const NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS = [ + CHECKOUT_MOCK_STEP, + GET_MERGED_PULL_REQUEST_MOCK_STEP__WELCOME_MESSAGE, + GET_PR_COUNT_MOCK_STEP__10, + COMMENT_ON_FIRST_PULL_REQUEST_MOCK_STEP, +]; +const NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__ONE_PR = [ + CHECKOUT_MOCK_STEP, + GET_MERGED_PULL_REQUEST_MOCK_STEP__WELCOME_MESSAGE, + GET_PR_COUNT_MOCK_STEP__1, + COMMENT_ON_FIRST_PULL_REQUEST_MOCK_STEP, +]; +const NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__OSBOTIFY = [ + CHECKOUT_MOCK_STEP__OSBOTIFY, + GET_MERGED_PULL_REQUEST_MOCK_STEP__WELCOME_MESSAGE__OSBOTIFY, + GET_PR_COUNT_MOCK_STEP__10, + COMMENT_ON_FIRST_PULL_REQUEST_MOCK_STEP, +]; + +const PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP = utils.createMockStep('Perform E2E tests', 'Perform E2E tests', 'E2EPERFORMANCETESTS'); +const PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS = [PREDEPLOY__E2EPERFORMANCETESTS__PERFORM_E2E_TESTS__MOCK_STEP]; + +module.exports = { + TYPECHECK_JOB_MOCK_STEPS, + LINT_JOB_MOCK_STEPS, + TEST_JOB_MOCK_STEPS, + CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED, + CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + SKIP_DEPLOY_JOB_MOCK_STEPS, + CREATE_NEW_VERSION_JOB_MOCK_STEPS, + UPDATE_STAGING_JOB_MOCK_STEPS, + IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__FALSE, + NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__ONE_PR, + NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__OSBOTIFY, + PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, +}; diff --git a/workflow_tests/mocks/reviewerChecklistMocks.js b/workflow_tests/mocks/reviewerChecklistMocks.js new file mode 100644 index 000000000000..5f9ef67198a8 --- /dev/null +++ b/workflow_tests/mocks/reviewerChecklistMocks.js @@ -0,0 +1,9 @@ +const utils = require('../utils/utils'); + +// checklist +const REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK = utils.createMockStep('reviewerChecklist.js', 'reviewerChecklist.js', 'CHECKLIST', ['GITHUB_TOKEN'], []); +const REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS = [REVIEWERCHECKLIST__CHECKLIST__REVIEWERCHECKLIST_JS__STEP_MOCK]; + +module.exports = { + REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/testBuildMocks.js b/workflow_tests/mocks/testBuildMocks.js new file mode 100644 index 000000000000..bdff0ac57ae1 --- /dev/null +++ b/workflow_tests/mocks/testBuildMocks.js @@ -0,0 +1,263 @@ +const utils = require('../utils/utils'); + +// validateactor +const TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK = utils.createMockStep('Is Expensify employee', 'Is Expensify employee', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], { + IS_EXPENSIFY_EMPLOYEE: true, +}); +const TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK = utils.createMockStep('Is Expensify employee', 'Is Expensify employee', 'VALIDATEACTOR', [], ['GITHUB_TOKEN'], { + IS_EXPENSIFY_EMPLOYEE: false, +}); +const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK = utils.createMockStep( + 'Set HAS_READY_TO_BUILD_LABEL flag', + 'Set HAS_READY_TO_BUILD_LABEL flag', + 'VALIDATEACTOR', + [], + ['PULL_REQUEST_NUMBER', 'GITHUB_TOKEN'], + {HAS_READY_TO_BUILD_LABEL: true}, +); +const TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK = utils.createMockStep( + 'Set HAS_READY_TO_BUILD_LABEL flag', + 'Set HAS_READY_TO_BUILD_LABEL flag', + 'VALIDATEACTOR', + [], + ['PULL_REQUEST_NUMBER', 'GITHUB_TOKEN'], + {HAS_READY_TO_BUILD_LABEL: false}, +); +const TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS = [ + TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK, + TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK, +]; +const TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS = [ + TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__TRUE__STEP_MOCK, + TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK, +]; +const TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS = [ + TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK, + TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__TRUE__STEP_MOCK, +]; +const TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS = [ + TESTBUILD__VALIDATEACTOR__IS_TEAM_MEMBER__FALSE__STEP_MOCK, + TESTBUILD__VALIDATEACTOR__SET_HAS_READY_TO_BUILD_LABEL_FLAG__FALSE__STEP_MOCK, +]; + +// getbranchref +const TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'GETBRANCHREF', [], []); +const TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK = utils.createMockStep( + 'Check if pull request number is correct', + 'Check if pull request number is correct', + 'GETBRANCHREF', + [], + ['GITHUB_TOKEN'], + {REF: 'test-ref'}, +); +const TESTBUILD__GETBRANCHREF__STEP_MOCKS = [TESTBUILD__GETBRANCHREF__CHECKOUT__STEP_MOCK, TESTBUILD__GETBRANCHREF__CHECK_IF_PULL_REQUEST_NUMBER_IS_CORRECT__STEP_MOCK]; + +// android +const TESTBUILD__ANDROID__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'ANDROID', ['ref'], []); +const TESTBUILD__ANDROID__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep( + 'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', + 'Creating .env.adhoc file based on staging', + 'ANDROID', + [], + [], +); +const TESTBUILD__ANDROID__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'ANDROID', [], []); +const TESTBUILD__ANDROID__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setup Ruby', 'ANDROID', ['ruby-version', 'bundler-cache'], []); +const TESTBUILD__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK = utils.createMockStep('Decrypt keystore', 'Decrypt keystore', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE']); +const TESTBUILD__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK = utils.createMockStep('Decrypt json key', 'Decrypt json key', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE']); +const TESTBUILD__ANDROID__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep( + 'Configure AWS Credentials', + 'Configure AWS Credentials', + 'ANDROID', + ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'], + [], +); +const TESTBUILD__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'ANDROID'); +const TESTBUILD__ANDROID__RUN_FASTLANE_BETA_TEST__STEP_MOCK = utils.createMockStep( + 'Run Fastlane beta test', + 'Run Fastlane beta test', + 'ANDROID', + [], + ['S3_ACCESS_KEY', 'S3_SECRET_ACCESS_KEY', 'S3_BUCKET', 'S3_REGION', 'MYAPP_UPLOAD_STORE_PASSWORD', 'MYAPP_UPLOAD_KEY_PASSWORD'], +); +const TESTBUILD__ANDROID__UPLOAD_ARTIFACT__STEP_MOCK = utils.createMockStep('Upload Artifact', 'Upload Artifact', 'ANDROID', ['name', 'path'], []); +const TESTBUILD__ANDROID__STEP_MOCKS = [ + TESTBUILD__ANDROID__CHECKOUT__STEP_MOCK, + TESTBUILD__ANDROID__CREATE_ENV_ADHOC__STEP_MOCK, + TESTBUILD__ANDROID__SETUP_NODE__STEP_MOCK, + TESTBUILD__ANDROID__SETUP_RUBY__STEP_MOCK, + TESTBUILD__ANDROID__DECRYPT_KEYSTORE__STEP_MOCK, + TESTBUILD__ANDROID__DECRYPT_JSON_KEY__STEP_MOCK, + TESTBUILD__ANDROID__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK, + TESTBUILD__ANDROID__CONFIGURE_MAPBOX_SDK__STEP_MOCK, + TESTBUILD__ANDROID__RUN_FASTLANE_BETA_TEST__STEP_MOCK, + TESTBUILD__ANDROID__UPLOAD_ARTIFACT__STEP_MOCK, +]; + +// ios +const TESTBUILD__IOS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'IOS', ['ref'], []); +const TESTBUILD__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK = utils.createMockStep('Configure MapBox SDK', 'Configure MapBox SDK', 'IOS'); +const TESTBUILD__IOS__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep( + 'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', + 'Creating .env.adhoc file based on staging', + 'IOS', + [], + [], +); +const TESTBUILD__IOS__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'IOS', [], []); +const TESTBUILD__IOS__SETUP_XCODE__STEP_MOCK = utils.createMockStep('Setup XCode', 'Setup XCode', 'IOS', [], []); +const TESTBUILD__IOS__SETUP_RUBY__STEP_MOCK = utils.createMockStep('Setup Ruby', 'Setup Ruby', 'IOS', ['ruby-version', 'bundler-cache'], []); +const TESTBUILD__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK = utils.createMockStep('Cache Pod dependencies', 'Cache Pod dependencies', 'IOS', ['path', 'key', 'restore-keys'], [], { + 'cache-hit': false, +}); +const TESTBUILD__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK = utils.createMockStep('Compare Podfile.lock and Manifest.lock', 'Compare Podfile.lock and Manifest.lock', 'IOS', [], [], { + IS_PODFILE_SAME_AS_MANIFEST: false, +}); +const TESTBUILD__IOS__INSTALL_COCOAPODS__STEP_MOCK = utils.createMockStep('Install cocoapods', 'Install cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command'], []); +const TESTBUILD__IOS__DECRYPT_PROFILE__STEP_MOCK = utils.createMockStep('Decrypt profile', 'Decrypt profile', 'IOS', [], ['LARGE_SECRET_PASSPHRASE']); +const TESTBUILD__IOS__DECRYPT_CERTIFICATE__STEP_MOCK = utils.createMockStep('Decrypt certificate', 'Decrypt certificate', 'IOS', [], ['LARGE_SECRET_PASSPHRASE']); +const TESTBUILD__IOS__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep( + 'Configure AWS Credentials', + 'Configure AWS Credentials', + 'IOS', + ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'], + [], +); +const TESTBUILD__IOS__RUN_FASTLANE__STEP_MOCK = utils.createMockStep('Run Fastlane', 'Run Fastlane', 'IOS', [], ['S3_ACCESS_KEY', 'S3_SECRET_ACCESS_KEY', 'S3_BUCKET', 'S3_REGION']); +const TESTBUILD__IOS__UPLOAD_ARTIFACT__STEP_MOCK = utils.createMockStep('Upload Artifact', 'Upload Artifact', 'IOS', ['name', 'path'], []); +const TESTBUILD__IOS__STEP_MOCKS = [ + TESTBUILD__IOS__CHECKOUT__STEP_MOCK, + TESTBUILD__IOS__CONFIGURE_MAPBOX_SDK__STEP_MOCK, + TESTBUILD__IOS__CREATE_ENV_ADHOC__STEP_MOCK, + TESTBUILD__IOS__SETUP_NODE__STEP_MOCK, + TESTBUILD__IOS__SETUP_XCODE__STEP_MOCK, + TESTBUILD__IOS__SETUP_RUBY__STEP_MOCK, + TESTBUILD__IOS__CACHE_POD_DEPENDENCIES__STEP_MOCK, + TESTBUILD__IOS__COMPARE_PODFILE_AND_MANIFEST__STEP_MOCK, + TESTBUILD__IOS__INSTALL_COCOAPODS__STEP_MOCK, + TESTBUILD__IOS__DECRYPT_PROFILE__STEP_MOCK, + TESTBUILD__IOS__DECRYPT_CERTIFICATE__STEP_MOCK, + TESTBUILD__IOS__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK, + TESTBUILD__IOS__RUN_FASTLANE__STEP_MOCK, + TESTBUILD__IOS__UPLOAD_ARTIFACT__STEP_MOCK, +]; + +// desktop +const TESTBUILD__DESKTOP__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'DESKTOP', ['ref'], []); +const TESTBUILD__DESKTOP__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep( + 'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', + 'Creating .env.adhoc file based on staging', + 'DESKTOP', + [], + [], +); +const TESTBUILD__DESKTOP__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'DESKTOP', [], []); +const TESTBUILD__DESKTOP__DECRYPT_DEVELOPER_ID_CERTIFICATE__STEP_MOCK = utils.createMockStep( + 'Decrypt Developer ID Certificate', + 'Decrypt Developer ID Certificate', + 'DESKTOP', + [], + ['DEVELOPER_ID_SECRET_PASSPHRASE'], +); +const TESTBUILD__DESKTOP__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep( + 'Configure AWS Credentials', + 'Configure AWS Credentials', + 'DESKTOP', + ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'], + [], +); +const TESTBUILD__DESKTOP__BUILD_DESKTOP_APP_FOR_TESTING__STEP_MOCK = utils.createMockStep( + 'Build desktop app for testing', + 'Build desktop app for testing', + 'DESKTOP', + [], + ['CSC_LINK', 'CSC_KEY_PASSWORD', 'APPLE_ID', 'APPLE_APP_SPECIFIC_PASSWORD', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'], +); +const TESTBUILD__DESKTOP__STEP_MOCKS = [ + TESTBUILD__DESKTOP__CHECKOUT__STEP_MOCK, + TESTBUILD__DESKTOP__CREATE_ENV_ADHOC__STEP_MOCK, + TESTBUILD__DESKTOP__SETUP_NODE__STEP_MOCK, + TESTBUILD__DESKTOP__DECRYPT_DEVELOPER_ID_CERTIFICATE__STEP_MOCK, + TESTBUILD__DESKTOP__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK, + TESTBUILD__DESKTOP__BUILD_DESKTOP_APP_FOR_TESTING__STEP_MOCK, +]; + +// web +const TESTBUILD__WEB__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'WEB', ['ref'], []); +const TESTBUILD__WEB__CREATE_ENV_ADHOC__STEP_MOCK = utils.createMockStep( + 'Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it', + 'Creating .env.adhoc file based on staging', + 'WEB', + [], + [], +); +const TESTBUILD__WEB__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'WEB', [], []); +const TESTBUILD__WEB__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK = utils.createMockStep( + 'Configure AWS Credentials', + 'Configure AWS Credentials', + 'WEB', + ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'], + [], +); +const TESTBUILD__WEB__BUILD_WEB_FOR_TESTING__STEP_MOCK = utils.createMockStep('Build web for testing', 'Build web for testing', 'WEB', [], []); +const TESTBUILD__WEB__BUILD_DOCS__STEP_MOCK = utils.createMockStep('Build docs', 'Build docs', 'WEB', [], []); +const TESTBUILD__WEB__DEPLOY_TO_S3_FOR_INTERNAL_TESTING__STEP_MOCK = utils.createMockStep('Deploy to S3 for internal testing', 'Deploy to S3 for internal testing', 'WEB', [], []); +const TESTBUILD__WEB__STEP_MOCKS = [ + TESTBUILD__WEB__CHECKOUT__STEP_MOCK, + TESTBUILD__WEB__CREATE_ENV_ADHOC__STEP_MOCK, + TESTBUILD__WEB__SETUP_NODE__STEP_MOCK, + TESTBUILD__WEB__CONFIGURE_AWS_CREDENTIALS__STEP_MOCK, + TESTBUILD__WEB__BUILD_WEB_FOR_TESTING__STEP_MOCK, + TESTBUILD__WEB__BUILD_DOCS__STEP_MOCK, + TESTBUILD__WEB__DEPLOY_TO_S3_FOR_INTERNAL_TESTING__STEP_MOCK, +]; + +// postgithubcomment +const TESTBUILD__POSTGITHUBCOMMENT__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'POSTGITHUBCOMMENT', ['ref'], []); +const TESTBUILD__POSTGITHUBCOMMENT__DOWNLOAD_ARTIFACT__STEP_MOCK = utils.createMockStep('Download Artifact', 'Download Artifact', 'POSTGITHUBCOMMENT', [], []); +const TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_ANDROID_PATHS__STEP_MOCK = utils.createMockStep( + 'Read JSONs with android paths', + 'Read JSONs with android paths', + 'POSTGITHUBCOMMENT', + [], + [], + {android_path: 'http://dummy.android.link'}, +); +const TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_IOS_PATHS__STEP_MOCK = utils.createMockStep('Read JSONs with iOS paths', 'Read JSONs with iOS paths', 'POSTGITHUBCOMMENT', [], [], { + ios_path: 'http://dummy.ios.link', +}); +const TESTBUILD__POSTGITHUBCOMMENT__MAINTAIN_COMMENT__STEP_MOCK = utils.createMockStep( + 'maintain-comment', + 'maintain-comment', + 'POSTGITHUBCOMMENT', + ['token', 'body-include', 'number', 'delete'], + [], +); +const TESTBUILD__POSTGITHUBCOMMENT__PUBLISH_LINKS_TO_APPS_FOR_DOWNLOAD__STEP_MOCK = utils.createMockStep( + 'Publish links to apps for download', + 'Publish links to apps for download', + 'POSTGITHUBCOMMENT', + ['PR_NUMBER', 'GITHUB_TOKEN', 'ANDROID', 'DESKTOP', 'IOS', 'WEB', 'ANDROID_LINK', 'DESKTOP_LINK', 'IOS_LINK', 'WEB_LINK'], + [], +); +const TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS = [ + TESTBUILD__POSTGITHUBCOMMENT__CHECKOUT__STEP_MOCK, + TESTBUILD__POSTGITHUBCOMMENT__DOWNLOAD_ARTIFACT__STEP_MOCK, + TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_ANDROID_PATHS__STEP_MOCK, + TESTBUILD__POSTGITHUBCOMMENT__READ_JSONS_WITH_IOS_PATHS__STEP_MOCK, + TESTBUILD__POSTGITHUBCOMMENT__MAINTAIN_COMMENT__STEP_MOCK, + TESTBUILD__POSTGITHUBCOMMENT__PUBLISH_LINKS_TO_APPS_FOR_DOWNLOAD__STEP_MOCK, +]; + +module.exports = { + TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + TESTBUILD__GETBRANCHREF__STEP_MOCKS, + TESTBUILD__ANDROID__STEP_MOCKS, + TESTBUILD__IOS__STEP_MOCKS, + TESTBUILD__DESKTOP__STEP_MOCKS, + TESTBUILD__WEB__STEP_MOCKS, + TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/testMocks.js b/workflow_tests/mocks/testMocks.js new file mode 100644 index 000000000000..19011271bb47 --- /dev/null +++ b/workflow_tests/mocks/testMocks.js @@ -0,0 +1,26 @@ +const utils = require('../utils/utils'); + +// jest +const TEST__JEST__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'JEST', [], []); +const TEST__JEST__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'JEST', [], []); +const TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK = utils.createMockStep('Get number of CPU cores', 'Get number of CPU cores', 'JEST', [], [], {count: 8}); +const TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK = utils.createMockStep('Cache Jest cache', 'Cache Jest cache', 'JEST', ['path', 'key'], []); +const TEST__JEST__JEST_TESTS__STEP_MOCK = utils.createMockStep('Jest tests', 'Jest tests', 'JEST', [], []); +const TEST__JEST__STEP_MOCKS = [ + TEST__JEST__CHECKOUT__STEP_MOCK, + TEST__JEST__SETUP_NODE__STEP_MOCK, + TEST__JEST__GET_NUMBER_OF_CPU_CORES__STEP_MOCK, + TEST__JEST__CACHE_JEST_CACHE__STEP_MOCK, + TEST__JEST__JEST_TESTS__STEP_MOCK, +]; + +// shelltests +const TEST__SHELLTESTS__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'SHELLTESTS', [], []); +const TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'SHELLTESTS', [], []); +const TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK = utils.createMockStep('Test CI git logic', 'Test CI git logic', 'SHELLTESTS', [], []); +const TEST__SHELLTESTS__STEP_MOCKS = [TEST__SHELLTESTS__CHECKOUT__STEP_MOCK, TEST__SHELLTESTS__SETUP_NODE__STEP_MOCK, TEST__SHELLTESTS__TEST_CI_GIT_LOGIC__STEP_MOCK]; + +module.exports = { + TEST__JEST__STEP_MOCKS, + TEST__SHELLTESTS__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/validateGithubActionsMocks.js b/workflow_tests/mocks/validateGithubActionsMocks.js new file mode 100644 index 000000000000..e2d48932acf6 --- /dev/null +++ b/workflow_tests/mocks/validateGithubActionsMocks.js @@ -0,0 +1,23 @@ +const utils = require('../utils/utils'); + +// verify +const VALIDATEGITHUBACTIONS__VERIFY__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'VERIFY'); +const VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []); +const VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK = utils.createMockStep( + 'Verify Javascript Action Builds', + 'Verify Javascript Action Builds', + 'VERIFY', + [], + [], +); +const VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK = utils.createMockStep('Validate actions and workflows', 'Validate actions and workflows', 'VERIFY', [], []); +const VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS = [ + VALIDATEGITHUBACTIONS__VERIFY__CHECKOUT__STEP_MOCK, + VALIDATEGITHUBACTIONS__VERIFY__SETUP_NODE__STEP_MOCK, + VALIDATEGITHUBACTIONS__VERIFY__VERIFY_JAVASCRIPT_ACTION_BUILDS__STEP_MOCK, + VALIDATEGITHUBACTIONS__VERIFY__VALIDATE_ACTIONS_AND_WORKFLOWS__STEP_MOCK, +]; + +module.exports = { + VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/verifyPodfileMocks.js b/workflow_tests/mocks/verifyPodfileMocks.js new file mode 100644 index 000000000000..0a82eebcc748 --- /dev/null +++ b/workflow_tests/mocks/verifyPodfileMocks.js @@ -0,0 +1,11 @@ +const utils = require('../utils/utils'); + +// verify +const VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK = utils.createMockStep('Checkout', 'Checkout', 'VERIFY'); +const VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK = utils.createMockStep('Setup Node', 'Setup Node', 'VERIFY', [], []); +const VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK = utils.createMockStep('Verify podfile', 'Verify podfile', 'VERIFY', [], []); +const VERIFYPODFILE__VERIFY__STEP_MOCKS = [VERIFYPODFILE__VERIFY__CHECKOUT__STEP_MOCK, VERIFYPODFILE__VERIFY__SETUP_NODE__STEP_MOCK, VERIFYPODFILE__VERIFY__VERIFY_PODFILE__STEP_MOCK]; + +module.exports = { + VERIFYPODFILE__VERIFY__STEP_MOCKS, +}; diff --git a/workflow_tests/mocks/verifySignedCommitsMocks.js b/workflow_tests/mocks/verifySignedCommitsMocks.js new file mode 100644 index 000000000000..a19fac809e55 --- /dev/null +++ b/workflow_tests/mocks/verifySignedCommitsMocks.js @@ -0,0 +1,15 @@ +const utils = require('../utils/utils'); + +// verifysignedcommits +const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK = utils.createMockStep( + 'Verify signed commits', + 'Verify signed commits', + 'VERIFYSIGNEDCOMMITS', + ['GITHUB_TOKEN'], + [], +); +const VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS = [VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__VERIFY_SIGNED_COMMITS__STEP_MOCK]; + +module.exports = { + VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS, +}; diff --git a/workflow_tests/platformDeploy.test.js b/workflow_tests/platformDeploy.test.js new file mode 100644 index 000000000000..e74b8e873001 --- /dev/null +++ b/workflow_tests/platformDeploy.test.js @@ -0,0 +1,273 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/platformDeployAssertions'); +const mocks = require('./mocks/platformDeployMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'platformDeploy.yml'), + dest: '.github/workflows/platformDeploy.yml', + }, +]; + +describe('test workflow platformDeploy', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testPlatformDeployWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + pushedBranches: [], + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('push', () => { + describe('tag', () => { + it('as team member - platform deploy executes on staging', async () => { + const repoPath = mockGithub.repo.getPath('testPlatformDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'platformDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + { + ref: 'refs/tags/1.2.3', + ref_type: 'tag', + ref_name: '1.2.3', + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + MYAPP_UPLOAD_STORE_PASSWORD: 'dummy_store_password', + MYAPP_UPLOAD_KEY_PASSWORD: 'dummy_key_password', + BROWSERSTACK: 'dummy_browserstack', + SLACK_WEBHOOK: 'dummy_slack_webhook', + DEVELOPER_ID_SECRET_PASSPHRASE: 'dummy_secret_passphrase', + CSC_LINK: 'dummy_csc_link', + CSC_KEY_PASSWORD: 'dummy_csc_key_pass', + APPLE_ID: 'dummy_apple_id', + APPLE_ID_PASSWORD: 'dummy_apple_pass', + AWS_ACCESS_KEY_ID: 'dummy_aws_access_key_id', + AWS_SECRET_ACCESS_KEY: 'dummy_aws_secret_access_key', + APPLE_CONTACT_EMAIL: 'dummy@email.com', + APPLE_CONTACT_PHONE: '123456789', + APPLE_DEMO_EMAIL: 'dummy.demo@email.com', + APPLE_DEMO_PASSWORD: 'dummy_password', + CLOUDFLARE_TOKEN: 'dummy_cloudflare_token', + }, + 'dummy_github_token', + { + AS_REPO: 'App', + }, + ); + act = utils.setJobRunners( + act, + { + desktop: 'ubuntu-latest', + iOS: 'ubuntu-latest', + android: 'ubuntu-latest', + web: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + validateActor: mocks.PLATFORM_DEPLOY__VALIDATE_ACTOR__TEAM_MEMBER__STEP_MOCKS, + deployChecklist: mocks.PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS, + android: mocks.PLATFORM_DEPLOY__ANDROID__STEP_MOCKS, + desktop: mocks.PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS, + iOS: mocks.PLATFORM_DEPLOY__IOS__STEP_MOCKS, + web: mocks.PLATFORM_DEPLOY__WEB__STEP_MOCKS, + postSlackMessageOnFailure: mocks.PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS, + postSlackMessageOnSuccess: mocks.PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS, + postGithubComment: mocks.PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'platformDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('platformDeploy', expect.getState().currentTestName), + }); + + assertions.assertVerifyActorJobExecuted(result, 'Dummy Author'); + assertions.assertDeployChecklistJobExecuted(result, true); + assertions.assertAndroidJobExecuted(result, true, false, true); + assertions.assertDesktopJobExecuted(result, true, false); + assertions.assertIOSJobExecuted(result, true, false, true); + assertions.assertWebJobExecuted(result, true, false); + assertions.assertPostSlackOnFailureJobExecuted(result, false); + assertions.assertPostSlackOnSuccessJobExecuted(result, true, false); + assertions.assertPostGithubCommentJobExecuted(result, true, false); + }); + + it('as OSBotify - platform deploy executes on staging', async () => { + const repoPath = mockGithub.repo.getPath('testPlatformDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'platformDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + { + ref: 'refs/tags/1.2.3', + ref_type: 'tag', + ref_name: '1.2.3', + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + MYAPP_UPLOAD_STORE_PASSWORD: 'dummy_store_password', + MYAPP_UPLOAD_KEY_PASSWORD: 'dummy_key_password', + BROWSERSTACK: 'dummy_browserstack', + SLACK_WEBHOOK: 'dummy_slack_webhook', + DEVELOPER_ID_SECRET_PASSPHRASE: 'dummy_secret_passphrase', + CSC_LINK: 'dummy_csc_link', + CSC_KEY_PASSWORD: 'dummy_csc_key_pass', + APPLE_ID: 'dummy_apple_id', + APPLE_ID_PASSWORD: 'dummy_apple_pass', + AWS_ACCESS_KEY_ID: 'dummy_aws_access_key_id', + AWS_SECRET_ACCESS_KEY: 'dummy_aws_secret_access_key', + APPLE_CONTACT_EMAIL: 'dummy@email.com', + APPLE_CONTACT_PHONE: '123456789', + APPLE_DEMO_EMAIL: 'dummy.demo@email.com', + APPLE_DEMO_PASSWORD: 'dummy_password', + CLOUDFLARE_TOKEN: 'dummy_cloudflare_token', + }, + 'dummy_github_token', + { + AS_REPO: 'App', + }, + ); + act = utils.setJobRunners( + act, + { + desktop: 'ubuntu-latest', + iOS: 'ubuntu-latest', + android: 'ubuntu-latest', + web: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + validateActor: mocks.PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS, + deployChecklist: mocks.PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS, + android: mocks.PLATFORM_DEPLOY__ANDROID__STEP_MOCKS, + desktop: mocks.PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS, + iOS: mocks.PLATFORM_DEPLOY__IOS__STEP_MOCKS, + web: mocks.PLATFORM_DEPLOY__WEB__STEP_MOCKS, + postSlackMessageOnFailure: mocks.PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS, + postSlackMessageOnSuccess: mocks.PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS, + postGithubComment: mocks.PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'platformDeploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('platformDeploy', expect.getState().currentTestName), + }); + + assertions.assertVerifyActorJobExecuted(result, 'OSBotify'); + assertions.assertDeployChecklistJobExecuted(result, true); + assertions.assertAndroidJobExecuted(result, true, false, true); + assertions.assertDesktopJobExecuted(result, true, false); + assertions.assertIOSJobExecuted(result, true, false, true); + assertions.assertWebJobExecuted(result, true, false); + assertions.assertPostSlackOnFailureJobExecuted(result, false); + assertions.assertPostSlackOnSuccessJobExecuted(result, true, false); + assertions.assertPostGithubCommentJobExecuted(result, true, false); + }); + + it('as outsider - platform deploy does not execute', async () => { + const repoPath = mockGithub.repo.getPath('testPlatformDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'platformDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + { + ref: 'refs/tags/1.2.3', + ref_type: 'tag', + ref_name: '1.2.3', + }, + { + OS_BOTIFY_TOKEN: 'dummy_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + MYAPP_UPLOAD_STORE_PASSWORD: 'dummy_store_password', + MYAPP_UPLOAD_KEY_PASSWORD: 'dummy_key_password', + BROWSERSTACK: 'dummy_browserstack', + SLACK_WEBHOOK: 'dummy_slack_webhook', + DEVELOPER_ID_SECRET_PASSPHRASE: 'dummy_secret_passphrase', + CSC_LINK: 'dummy_csc_link', + CSC_KEY_PASSWORD: 'dummy_csc_key_pass', + APPLE_ID: 'dummy_apple_id', + APPLE_ID_PASSWORD: 'dummy_apple_pass', + AWS_ACCESS_KEY_ID: 'dummy_aws_access_key_id', + AWS_SECRET_ACCESS_KEY: 'dummy_aws_secret_access_key', + APPLE_CONTACT_EMAIL: 'dummy@email.com', + APPLE_CONTACT_PHONE: '123456789', + APPLE_DEMO_EMAIL: 'dummy.demo@email.com', + APPLE_DEMO_PASSWORD: 'dummy_password', + CLOUDFLARE_TOKEN: 'dummy_cloudflare_token', + }, + 'dummy_github_token', + { + AS_REPO: 'App', + }, + ); + act = utils.setJobRunners( + act, + { + desktop: 'ubuntu-latest', + iOS: 'ubuntu-latest', + android: 'ubuntu-latest', + web: 'ubuntu-latest', + }, + workflowPath, + ); + const testMockSteps = { + validateActor: mocks.PLATFORM_DEPLOY__VALIDATE_ACTOR__OUTSIDER__STEP_MOCKS, + deployChecklist: mocks.PLATFORM_DEPLOY__DEPLOY_CHECKLIST__STEP_MOCKS, + android: mocks.PLATFORM_DEPLOY__ANDROID__STEP_MOCKS, + desktop: mocks.PLATFORM_DEPLOY__DESKTOP__STEP_MOCKS, + iOS: mocks.PLATFORM_DEPLOY__IOS__STEP_MOCKS, + web: mocks.PLATFORM_DEPLOY__WEB__STEP_MOCKS, + postSlackMessageOnFailure: mocks.PLATFORM_DEPLOY__POST_SLACK_FAIL__STEP_MOCKS, + postSlackMessageOnSuccess: mocks.PLATFORM_DEPLOY__POST_SLACK_SUCCESS__STEP_MOCKS, + postGithubComment: mocks.PLATFORM_DEPLOY__POST_GITHUB_COMMENT__STEP_MOCKS, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'platformDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Author', + logFile: utils.getLogFilePath('platformDeploy', expect.getState().currentTestName), + }); + + assertions.assertVerifyActorJobExecuted(result, 'Dummy Author'); + assertions.assertDeployChecklistJobExecuted(result, true); + assertions.assertAndroidJobExecuted(result, false); + assertions.assertDesktopJobExecuted(result, false); + assertions.assertIOSJobExecuted(result, false); + assertions.assertWebJobExecuted(result, false); + assertions.assertPostSlackOnFailureJobExecuted(result, false); + assertions.assertPostSlackOnSuccessJobExecuted(result, false); + assertions.assertPostGithubCommentJobExecuted(result, true, false, false); + }); + }); + }); +}); diff --git a/workflow_tests/preDeploy.test.js b/workflow_tests/preDeploy.test.js new file mode 100644 index 000000000000..4a4d9dcc82bb --- /dev/null +++ b/workflow_tests/preDeploy.test.js @@ -0,0 +1,1087 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/preDeployAssertions'); +const mocks = require('./mocks/preDeployMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'preDeploy.yml'), + dest: '.github/workflows/preDeploy.yml', + }, +]; + +describe('test workflow preDeploy', () => { + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testPreDeployWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + pushedBranches: ['different_branch'], + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + it('push to main - workflow executes', async () => { + // get path to the local test repo + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + + // get path to the workflow file under test + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + + // instantiate Act in the context of the test repo and given workflow file + let act = new eAct.ExtendedAct(repoPath, workflowPath); + + // set run parameters + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + + // set up mocks + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + + // run an event and get the result + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + + // assert results (some steps can run in parallel to each other so the order is not assured + // therefore we can check which steps have been executed, but not the set job order + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertUpdateStagingJobExecuted(result); + }); + + it('different event than push - workflow does not execute', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + + // pull_request + act = utils.setUpActParams( + act, + 'pull_request', + {head: {ref: 'main'}}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + let result = await act.runEvent('pull_request', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result, false); + assertions.assertLintJobExecuted(result, false); + assertions.assertTestJobExecuted(result, false); + assertions.assertIsExpensifyEmployeeJobExecuted(result, false); + assertions.assertChooseDeployActionsJobExecuted(result, false); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + + // workflow_dispatch + act = utils.setUpActParams( + act, + 'workflow_dispatch', + {}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + result = await act.runEvent('workflow_dispatch', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result, false); + assertions.assertLintJobExecuted(result, false); + assertions.assertTestJobExecuted(result, false); + assertions.assertIsExpensifyEmployeeJobExecuted(result, false); + assertions.assertChooseDeployActionsJobExecuted(result, false); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + + describe('confirm passing build', () => { + it('typecheck job failed - workflow exits', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: [utils.createMockStep('Run typecheck workflow', 'Running typecheck workflow - Typecheck workflow failed', 'TYPECHECK', null, null, null, null, false)], + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + expect(result).toEqual( + expect.arrayContaining([utils.createStepAssertion('Run typecheck workflow', false, null, 'TYPECHECK', 'Running typecheck workflow - Typecheck workflow failed')]), + ); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + expect(result).toEqual( + expect.arrayContaining([ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'CONFIRM_PASSING_BUILD', 'Announcing failed workflow in slack', [ + {key: 'SLACK_WEBHOOK', value: '***'}, + ]), + utils.createStepAssertion('Exit failed workflow', false, ''), + ]), + ); + assertions.assertChooseDeployActionsJobExecuted(result, false); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + + it('lint job failed - workflow exits', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: [utils.createMockStep('Run lint workflow', 'Running lint workflow - Lint workflow failed', 'LINT', null, null, null, null, false)], + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + expect(result).toEqual(expect.arrayContaining([utils.createStepAssertion('Run lint workflow', false, null, 'LINT', 'Running lint workflow - Lint workflow failed')])); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + expect(result).toEqual( + expect.arrayContaining([ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'CONFIRM_PASSING_BUILD', 'Announcing failed workflow in slack', [ + {key: 'SLACK_WEBHOOK', value: '***'}, + ]), + utils.createStepAssertion('Exit failed workflow', false, ''), + ]), + ); + assertions.assertChooseDeployActionsJobExecuted(result, false); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + + it('test job failed - workflow exits', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: [utils.createMockStep('Run test workflow', 'Running test workflow - Test workflow failed', 'TEST', null, null, null, null, false)], + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + expect(result).toEqual(expect.arrayContaining([utils.createStepAssertion('Run test workflow', false, null, 'TEST', 'Running test workflow - Test workflow failed')])); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + expect(result).toEqual( + expect.arrayContaining([ + utils.createStepAssertion('Announce failed workflow in Slack', true, null, 'CONFIRM_PASSING_BUILD', 'Announcing failed workflow in slack', [ + {key: 'SLACK_WEBHOOK', value: '***'}, + ]), + utils.createStepAssertion('Exit failed workflow', false, ''), + ]), + ); + assertions.assertChooseDeployActionsJobExecuted(result, false); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + }); + + it('lint and test job succeed - workflow continues', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertUpdateStagingJobExecuted(result); + }); + }); + + describe('new contributor welcome message', () => { + it('actor is OSBotify - no comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__FALSE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__OSBOTIFY, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertNewContributorWelcomeMessageJobExecuted(result, false); + }); + + it('actor is Expensify employee - no comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertNewContributorWelcomeMessageJobExecuted(result, false); + }); + + it('actor is not Expensify employee, its not their first PR - job triggers, but no comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__FALSE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertNewContributorWelcomeMessageJobExecuted(result, true, false, false); + }); + + it('actor is not Expensify employee, and its their first PR - job triggers and comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__FALSE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__ONE_PR, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertNewContributorWelcomeMessageJobExecuted(result, true, false, true); + }); + }); + + describe('choose deploy actions', () => { + describe('staging locked', () => { + it('not automated PR - deploy skipped and comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, 'push', {ref: 'refs/heads/main'}, {OS_BOTIFY_TOKEN: 'dummy_token', SLACK_WEBHOOK: 'dummy_slack_webhook'}, 'dummy_github_token'); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + assertions.assertUpdateStagingJobFailed(result, false); + }); + + it('automated PR - deploy skipped, but no comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, 'push', {ref: 'refs/heads/main'}, {OS_BOTIFY_TOKEN: 'dummy_token', SLACK_WEBHOOK: 'dummy_slack_webhook'}, 'dummy_github_token'); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_LOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__OSBOTIFY, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + assertions.assertUpdateStagingJobFailed(result, false); + }); + }); + + describe('staging not locked', () => { + it('not automated PR - proceed with deploy', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertUpdateStagingJobExecuted(result, true); + assertions.assertUpdateStagingJobFailed(result, false); + }); + + it('automated PR - deploy skipped, but no comment left', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__OSBOTIFY, + }; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'OSBotify', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result, false); + assertions.assertUpdateStagingJobExecuted(result, false); + assertions.assertUpdateStagingJobFailed(result, false); + }); + }); + + it('one of updateStaging steps failed - failure announced in Slack', async () => { + const repoPath = mockGithub.repo.getPath('testPreDeployWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + 'push', + {ref: 'refs/heads/main'}, + { + OS_BOTIFY_TOKEN: 'dummy_token', + SLACK_WEBHOOK: 'dummy_slack_webhook', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + }, + 'dummy_github_token', + ); + const testMockSteps = { + confirmPassingBuild: mocks.CONFIRM_PASSING_BUILD_JOB_MOCK_STEPS, + chooseDeployActions: mocks.CHOOSE_DEPLOY_ACTIONS_JOB_MOCK_STEPS__STAGING_UNLOCKED, + skipDeploy: mocks.SKIP_DEPLOY_JOB_MOCK_STEPS, + updateStaging: mocks.UPDATE_STAGING_JOB_MOCK_STEPS, + isExpensifyEmployee: mocks.IS_EXPENSIFY_EMPLOYEE_JOB_MOCK_STEPS__TRUE, + newContributorWelcomeMessage: mocks.NEW_CONTRIBUTOR_WELCOME_MESSAGE_JOB_MOCK_STEPS__MANY_PRS, + }; + testMockSteps.updateStaging[3].mockWith = 'exit 1'; + const testMockJobs = { + typecheck: { + steps: mocks.TYPECHECK_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + lint: { + steps: mocks.LINT_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + test: { + steps: mocks.TEST_JOB_MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + createNewVersion: { + steps: mocks.CREATE_NEW_VERSION_JOB_MOCK_STEPS, + outputs: { + // eslint-disable-next-line no-template-curly-in-string + NEW_VERSION: '${{ steps.createNewVersion.outputs.NEW_VERSION }}', + }, + runsOn: 'ubuntu-latest', + }, + e2ePerformanceTests: { + steps: mocks.PREDEPLOY__E2EPERFORMANCETESTS__MOCK_STEPS, + runsOn: 'ubuntu-latest', + }, + }; + const result = await act.runEvent('push', { + workflowFile: path.join(repoPath, '.github', 'workflows', 'preDeploy.yml'), + mockSteps: testMockSteps, + actor: 'Dummy Tester', + logFile: utils.getLogFilePath('preDeploy', expect.getState().currentTestName), + mockJobs: testMockJobs, + }); + assertions.assertTypecheckJobExecuted(result); + assertions.assertLintJobExecuted(result); + assertions.assertTestJobExecuted(result); + assertions.assertIsExpensifyEmployeeJobExecuted(result); + assertions.assertChooseDeployActionsJobExecuted(result); + assertions.assertSkipDeployJobExecuted(result, false); + assertions.assertCreateNewVersionJobExecuted(result); + assertions.assertUpdateStagingJobFailed(result, true); + }); + }); +}); diff --git a/workflow_tests/reviewerChecklist.test.js b/workflow_tests/reviewerChecklist.test.js new file mode 100644 index 000000000000..9903c3eb4b8d --- /dev/null +++ b/workflow_tests/reviewerChecklist.test.js @@ -0,0 +1,84 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/reviewerChecklistAssertions'); +const mocks = require('./mocks/reviewerChecklistMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'reviewerChecklist.yml'), + dest: '.github/workflows/reviewerChecklist.yml', + }, +]; + +describe('test workflow reviewerChecklist', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testReviewerChecklistWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('event is pull_request_review', () => { + const event = 'pull_request_review'; + const eventOptions = {}; + it('runs the workflow', async () => { + const repoPath = mockGithub.repo.getPath('testReviewerChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'reviewerChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'reviewerChecklist.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('reviewerChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result); + }); + describe('actor is OSBotify', () => { + const osbotifyActor = 'OSBotify'; + it('does not run the workflow', async () => { + const repoPath = mockGithub.repo.getPath('testReviewerChecklistWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'reviewerChecklist.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + checklist: mocks.REVIEWERCHECKLIST__CHECKLIST__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'reviewerChecklist.yml'), + mockSteps: testMockSteps, + actor: osbotifyActor, + logFile: utils.getLogFilePath('reviewerChecklist', expect.getState().currentTestName), + }); + + assertions.assertChecklistJobExecuted(result, false); + }); + }); + }); +}); diff --git a/workflow_tests/scripts/runWorkflowTests.sh b/workflow_tests/scripts/runWorkflowTests.sh new file mode 100755 index 000000000000..71ddcdceffb5 --- /dev/null +++ b/workflow_tests/scripts/runWorkflowTests.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +source ./scripts/shellUtils.sh + +title 'GitHub Actions workflow tests' +printf '\n' + +# Check setup +info 'Checking environment setup' + +# Check if docker is installed +if ! docker --version > /dev/null 2>&1; then + error 'Docker is not installed' + info 'Act requires docker to be installed. Please install docker and try again' + exit 1 +fi +info 'Docker installed' + +# Check if docker is running +if ! docker info > /dev/null 2>&1; then + error 'Docker is not running' + info 'Act requires docker engine to be running. Enable docker engine and try again' + exit 1 +fi +info 'Docker engine running' + +# Check if act is installed +if ! act --version > /dev/null 2>&1; then + error 'Act not installed' + info 'Install Act with brew install act and follow the documentation on first Act run (https://github.com/nektos/act#first-act-run)' + exit 1 +fi +info 'Act installed' + +# Check if ACT_BINARY is set +if [[ -z ${ACT_BINARY} ]]; then + info 'ACT_BINARY not set, checking .env file' + if [ -f .env ]; then + set -a + source .env + set +a + else + info '.env file does not exist' + fi + if [[ -z ${ACT_BINARY} ]]; then + error 'ACT_BINARY variable not set' + info 'To make sure Act behaves in a predictable manner please set the ACT_BINARY environment variable to the path to your Act binary' + exit 1 + fi +fi +info 'ACT_BINARY environment variable set' + +if ! eval '${ACT_BINARY} --version' > /dev/null 2>&1; then + error 'ACT_BINARY variable not set properly' + info 'ACT_BINARY environment variable should be set to the path to your Act executable. Please set the variable correctly (try running "which act" to check the path)' + exit 1 +fi +info 'ACT_BINARY environment variable set to an Act executable' + +success 'Environment setup properly - running tests' + +# Run tests +npm test -- --config=workflow_tests/jest.config.js --runInBand "$@" diff --git a/workflow_tests/test.test.js b/workflow_tests/test.test.js new file mode 100644 index 000000000000..6efe8d260928 --- /dev/null +++ b/workflow_tests/test.test.js @@ -0,0 +1,184 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/testAssertions'); +const mocks = require('./mocks/testMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'test.yml'), + dest: '.github/workflows/test.yml', + }, +]; + +describe('test workflow test', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + const osbotifyActor = 'OSBotify'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testTestWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + + describe('pull request opened', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'opened', + }; + it('runs all tests', async () => { + const repoPath = mockGithub.repo.getPath('testTestWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'test.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + jest: mocks.TEST__JEST__STEP_MOCKS, + shellTests: mocks.TEST__SHELLTESTS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'test.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('test', expect.getState().currentTestName), + }); + + assertions.assertJestJobExecuted(result); + assertions.assertShellTestsJobExecuted(result); + }); + describe('actor is OSBotify', () => { + it('does not run tests', async () => { + const repoPath = mockGithub.repo.getPath('testTestWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'test.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + jest: mocks.TEST__JEST__STEP_MOCKS, + shellTests: mocks.TEST__SHELLTESTS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'test.yml'), + mockSteps: testMockSteps, + actor: osbotifyActor, + logFile: utils.getLogFilePath('test', expect.getState().currentTestName), + }); + + assertions.assertJestJobExecuted(result, false); + assertions.assertShellTestsJobExecuted(result, false); + }); + }); + }); + + describe('pull request synchronized', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'synchronize', + }; + it('runs all tests', async () => { + const repoPath = mockGithub.repo.getPath('testTestWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'test.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + jest: mocks.TEST__JEST__STEP_MOCKS, + shellTests: mocks.TEST__SHELLTESTS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'test.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('test', expect.getState().currentTestName), + }); + + assertions.assertJestJobExecuted(result); + assertions.assertShellTestsJobExecuted(result); + }); + describe('actor is OSBotify', () => { + it('does not run tests', async () => { + const repoPath = mockGithub.repo.getPath('testTestWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'test.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + jest: mocks.TEST__JEST__STEP_MOCKS, + shellTests: mocks.TEST__SHELLTESTS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'test.yml'), + mockSteps: testMockSteps, + actor: osbotifyActor, + logFile: utils.getLogFilePath('test', expect.getState().currentTestName), + }); + + assertions.assertJestJobExecuted(result, false); + assertions.assertShellTestsJobExecuted(result, false); + }); + }); + }); + + describe('event is workflow_call', () => { + const event = 'workflow_call'; + const eventOptions = {}; + it('runs all tests', async () => { + const repoPath = mockGithub.repo.getPath('testTestWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'test.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + jest: mocks.TEST__JEST__STEP_MOCKS, + shellTests: mocks.TEST__SHELLTESTS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'test.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('test', expect.getState().currentTestName), + }); + + assertions.assertJestJobExecuted(result); + assertions.assertShellTestsJobExecuted(result); + }); + describe('actor is OSBotify', () => { + it('runs all tests normally', async () => { + const repoPath = mockGithub.repo.getPath('testTestWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'test.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + jest: mocks.TEST__JEST__STEP_MOCKS, + shellTests: mocks.TEST__SHELLTESTS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'test.yml'), + mockSteps: testMockSteps, + actor: osbotifyActor, + logFile: utils.getLogFilePath('test', expect.getState().currentTestName), + }); + + assertions.assertJestJobExecuted(result); + assertions.assertShellTestsJobExecuted(result); + }); + }); + }); +}); diff --git a/workflow_tests/testBuild.test.js b/workflow_tests/testBuild.test.js new file mode 100644 index 000000000000..4288ea236ab2 --- /dev/null +++ b/workflow_tests/testBuild.test.js @@ -0,0 +1,753 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/testBuildAssertions'); +const mocks = require('./mocks/testBuildMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'testBuild.yml'), + dest: '.github/workflows/testBuild.yml', + }, +]; + +describe('test workflow testBuild', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + const secrets = { + OS_BOTIFY_TOKEN: 'dummy_osbotify_token', + LARGE_SECRET_PASSPHRASE: '3xtr3m3ly_53cr3t_p455w0rd', + AWS_ACCESS_KEY_ID: 'dummy_aws_access_kry_id', + AWS_SECRET_ACCESS_KEY: 'dummy_aws_secret_access_key', + DEVELOPER_ID_SECRET_PASSPHRASE: 'dummy_developer_id_secret_passphrase', + CSC_LINK: 'dummy_csc_link', + CSC_KEY_PASSWORD: 'dummy_csc_key_password', + APPLE_ID_PASSWORD: 'dummy_apple_id_password', + APPLE_ID: 'dummy_apple_id_value', + MYAPP_UPLOAD_STORE_PASSWORD: 'dummy_myapp_upload_store_password', + MYAPP_UPLOAD_KEY_PASSWORD: 'dummy_myapp_upload_key_password', + }; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testTestBuildWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('event is workflow_dispatch', () => { + const event = 'workflow_dispatch'; + const inputs = { + PULL_REQUEST_NUMBER: '1234', + }; + it('executes workflow', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref'); + }); + describe('actor is not a team member', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('actor is not a team member and PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('android fails', () => { + it('executes workflow, failure reflected', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: utils.deepCopy(mocks.TESTBUILD__ANDROID__STEP_MOCKS), + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + testMockSteps.android[4] = utils.createMockStep('Decrypt keystore', 'Decrypt keystore', 'ANDROID', [], ['LARGE_SECRET_PASSPHRASE'], {}, {}, false); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result); + assertions.assertAndroidJobExecuted(result, 'test-ref', true, 4); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', true, 'failure', 'success', 'success', 'success'); + }); + }); + describe('iOS fails', () => { + it('executes workflow, failure reflected', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: utils.deepCopy(mocks.TESTBUILD__IOS__STEP_MOCKS), + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + testMockSteps.iOS[8] = utils.createMockStep('Install cocoapods', 'Install cocoapods', 'IOS', ['timeout_minutes', 'max_attempts', 'command'], [], {}, {}, false); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref', true, 8); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', true, 'success', 'failure', 'success', 'success'); + }); + }); + describe('desktop fails', () => { + it('executes workflow, failure reflected', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: utils.deepCopy(mocks.TESTBUILD__DESKTOP__STEP_MOCKS), + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + testMockSteps.desktop[3] = utils.createMockStep( + 'Decrypt Developer ID Certificate', + 'Decrypt Developer ID Certificate', + 'DESKTOP', + [], + ['DEVELOPER_ID_SECRET_PASSPHRASE'], + {}, + {}, + false, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref', true, 3); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', true, 'success', 'success', 'failure', 'success'); + }); + }); + describe('web fails', () => { + it('executes workflow, failure reflected', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, {}, secrets, githubToken, {}, inputs); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: utils.deepCopy(mocks.TESTBUILD__WEB__STEP_MOCKS), + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + testMockSteps.web[3] = utils.createMockStep( + 'Configure AWS Credentials', + 'Configure AWS Credentials', + 'WEB', + ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY'], + [], + {}, + {}, + false, + ); + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref', true, 3); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', true, 'success', 'success', 'success', 'failure'); + }); + }); + }); + describe('pull request opened', () => { + const event = 'pull_request_target'; + const eventOptions = { + action: 'opened', + number: '1234', + pull_request: { + head: { + sha: 'test-ref', + }, + }, + }; + it('executes workflow, without getBranchRef', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref'); + }); + describe('actor is not a team member', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('actor is not a team member and PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + }); + describe('pull request synchronized', () => { + const event = 'pull_request_target'; + const eventOptions = { + action: 'synchronize', + number: '1234', + pull_request: { + head: { + sha: 'test-ref', + }, + }, + }; + it('executes workflow, without getBranchRef', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref'); + }); + describe('actor is not a team member', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('actor is not a team member and PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + }); + describe('pull request labeled', () => { + const event = 'pull_request_target'; + const eventOptions = { + action: 'labeled', + number: '1234', + pull_request: { + head: { + sha: 'test-ref', + }, + }, + }; + it('executes workflow, withuout getBranchRef', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref'); + assertions.assertIOSJobExecuted(result, 'test-ref'); + assertions.assertDesktopJobExecuted(result, 'test-ref'); + assertions.assertWebJobExecuted(result, 'test-ref'); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref'); + }); + describe('actor is not a team member', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_HAS_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + describe('actor is not a team member and PR does not have READY_TO_BUILD label', () => { + it('stops the workflow after validation', async () => { + const repoPath = mockGithub.repo.getPath('testTestBuildWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'testBuild.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, secrets, githubToken, {}); + act = utils.setJobRunners(act, {iOS: 'ubuntu-latest', desktop: 'ubuntu-latest', web: 'ubuntu-latest', android: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + validateActor: mocks.TESTBUILD__VALIDATEACTOR__NO_TEAM_MEMBER_NO_FLAG__STEP_MOCKS, + getBranchRef: mocks.TESTBUILD__GETBRANCHREF__STEP_MOCKS, + android: mocks.TESTBUILD__ANDROID__STEP_MOCKS, + iOS: mocks.TESTBUILD__IOS__STEP_MOCKS, + desktop: mocks.TESTBUILD__DESKTOP__STEP_MOCKS, + web: mocks.TESTBUILD__WEB__STEP_MOCKS, + postGithubComment: mocks.TESTBUILD__POSTGITHUBCOMMENT__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'testBuild.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('testBuild', expect.getState().currentTestName), + }); + + assertions.assertValidateActorJobExecuted(result, '1234'); + assertions.assertGetBranchRefJobExecuted(result, false); + assertions.assertAndroidJobExecuted(result, 'test-ref', false); + assertions.assertIOSJobExecuted(result, 'test-ref', false); + assertions.assertDesktopJobExecuted(result, 'test-ref', false); + assertions.assertWebJobExecuted(result, 'test-ref', false); + assertions.assertPostGithubCommentJobExecuted(result, 'test-ref', '1234', false); + }); + }); + }); +}); diff --git a/workflow_tests/utils/ExtendedAct.js b/workflow_tests/utils/ExtendedAct.js new file mode 100644 index 000000000000..9b4ab1bebda2 --- /dev/null +++ b/workflow_tests/utils/ExtendedAct.js @@ -0,0 +1,44 @@ +const kieActJs = require('@kie/act-js'); +const path = require('path'); +const _ = require('underscore'); +const JobMocker = require('./JobMocker'); + +class ExtendedAct extends kieActJs.Act { + async parseRunOpts(opts) { + const {cwd, actArguments, proxy} = await super.parseRunOpts(opts); + if (opts && opts.actor) { + actArguments.push('--actor', opts.actor); + } + return {cwd, actArguments, proxy}; + } + + async runEvent(event, opts) { + const {mockJobs, ...vanillaOpts} = opts; + if (mockJobs) { + await this.handleJobMocking((workflow) => workflow.events.includes(event), {mockJobs, workflowFile: opts.workflowFile, cwd: opts.cwd}); + } + return super.runEvent(event, vanillaOpts); + } + + async handleJobMocking(filter, opts) { + let workflowFiles; + if (opts.workflowFile) { + workflowFiles = [path.basename(opts.workflowFile)]; + } else if (this.workflowFile !== this.cwd) { + workflowFiles = [path.basename(this.workflowFile)]; + } else { + workflowFiles = _(_(await this.list(undefined, opts.cwd, opts.workflowFile)).filter(filter)).map((l) => l.workflowFile); + } + return Promise.all( + _(workflowFiles).map((workflowFile) => { + // eslint-disable-next-line es/no-nullish-coalescing-operators + const jobMocker = new JobMocker.JobMocker(workflowFile, opts.cwd ?? this.cwd); + return jobMocker.mock(opts.mockJobs); + }), + ); + } +} + +module.exports = { + ExtendedAct, +}; diff --git a/workflow_tests/utils/JobMocker.js b/workflow_tests/utils/JobMocker.js new file mode 100644 index 000000000000..a2682a657380 --- /dev/null +++ b/workflow_tests/utils/JobMocker.js @@ -0,0 +1,83 @@ +const fs = require('fs'); +const path = require('path'); +const yaml = require('yaml'); +const _ = require('underscore'); + +class JobMocker { + constructor(workflowFile, cwd) { + this.workflowFile = workflowFile; + this.cwd = cwd; + } + + async mock(mockJobs) { + const filePath = this.getWorkflowPath(); + const workflow = await this.readWorkflowFile(filePath); + Object.entries(mockJobs).forEach(([jobId, mockJob]) => { + const job = this.locateJob(workflow, jobId); + if (job) { + if (job.uses) { + delete job.uses; + } + if (job.secrets) { + delete job.secrets; + } + let jobWith; + if (job.with) { + jobWith = job.with; + delete job.with; + } + job.steps = _(mockJob.steps).map((step) => { + const mockStep = { + name: step.name, + run: step.mockWith, + }; + if (step.id) { + mockStep.id = step.id; + } + if (jobWith) { + mockStep.with = jobWith; + } + return mockStep; + }); + if (mockJob.outputs) { + job.outputs = mockJob.outputs; + } + if (mockJob.runsOn) { + job['runs-on'] = mockJob.runsOn; + } + } else { + throw new Error('Could not find job'); + } + }); + return this.writeWorkflowFile(filePath, workflow); + } + + locateJob(workflow, jobId) { + return workflow.jobs[jobId]; + } + + getWorkflowPath() { + if (fs.existsSync(path.join(this.cwd, this.workflowFile))) { + return path.join(this.cwd, this.workflowFile); + } + if (this.cwd.endsWith('.github')) { + return path.join(this.cwd, 'workflows', this.workflowFile); + } + if (fs.existsSync(path.join(this.cwd, '.github', 'workflows', this.workflowFile))) { + return path.join(this.cwd, '.github', 'workflows', this.workflowFile); + } + throw new Error(`Could not locate ${this.workflowFile}`); + } + + async readWorkflowFile(location) { + return yaml.parse(fs.readFileSync(location, 'utf8')); + } + + async writeWorkflowFile(location, data) { + return fs.writeFileSync(location, yaml.stringify(data), 'utf8'); + } +} + +module.exports = { + JobMocker, +}; diff --git a/workflow_tests/utils/preGenerateTest.js b/workflow_tests/utils/preGenerateTest.js new file mode 100644 index 000000000000..4ed485abec40 --- /dev/null +++ b/workflow_tests/utils/preGenerateTest.js @@ -0,0 +1,281 @@ +/* eslint no-console: ["error", { allow: ["warn", "log"] }] */ +const path = require('path'); +const {exit} = require('process'); +const fs = require('fs'); +const yaml = require('yaml'); +const _ = require('underscore'); + +const workflowsDirectory = path.resolve(__dirname, '..', '..', '.github', 'workflows'); +const workflowTestsDirectory = path.resolve(__dirname, '..'); +const workflowTestMocksDirectory = path.join(workflowTestsDirectory, 'mocks'); +const workflowTestAssertionsDirectory = path.join(workflowTestsDirectory, 'assertions'); +const workflowFilePattern = '\\w+\\.yml'; +const workflowFileRegex = new RegExp(workflowFilePattern, 'g'); + +const capitalize = (s) => (s && s.charAt(0).toUpperCase() + s.slice(1)) || ''; +const mockFileTemplate = (mockSteps, exports) => `const utils = require('../utils/utils'); +${mockSteps} +${exports} +`; +const assertionFileTemplate = (jobAssertions, exports) => `const utils = require('../utils/utils'); +${jobAssertions} +${exports} +`; +const testFileTemplate = (workflowName) => `const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/${workflowName}Assertions'); +const mocks = require('./mocks/${workflowName}Mocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + { + src: path.resolve(__dirname, '..', '.github', 'actions'), + dest: '.github/actions', + }, + { + src: path.resolve(__dirname, '..', '.github', 'libs'), + dest: '.github/libs', + }, + { + src: path.resolve(__dirname, '..', '.github', 'scripts'), + dest: '.github/scripts', + }, + { + src: path.resolve(__dirname, '..', '.github', 'workflows', '${workflowName}.yml'), + dest: '.github/workflows/${workflowName}.yml', + }, +]; + +describe('test workflow ${workflowName}', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + test${capitalize(workflowName)}WorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + + // if any branches besides main are need add: pushedBranches: ['staging', 'production'], + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + it('test stub', async () => { + const repoPath = mockGithub.repo.getPath('test${capitalize(workflowName)}WorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', '${workflowName}.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams( + act, + '[EVENT]', + {}, + {}, + githubToken, + ); + const testMockSteps = { + // mock steps with imported mocks + }; + const result = await act + .runEvent('[EVENT]', { + workflowFile: path.join(repoPath, '.github', 'workflows'), + mockSteps: testMockSteps, + actor, + }); + + // assert execution with imported assertions + }); +}); +`; +const mockStepTemplate = (stepMockName, step, jobId) => ` +const ${stepMockName} = utils.getMockStep( + '${step.name || ''}', + '${step.name || ''}', + ${jobId ? `'${jobId.toUpperCase()}'` : 'null'}, + ${step.inputs ? JSON.stringify(step.inputs).replaceAll('"', "'") : 'null'}, + ${step.envs ? JSON.stringify(step.envs).replaceAll('"', "'") : 'null'}, + // add outputs if needed +);`; +const stepAssertionTemplate = (step_name, job_id, step_message, inputs, envs) => ` + utils.getStepAssertion( + '${step_name}', + true, + null, + '${job_id}', + '${step_message}', + [${_(inputs).map((input) => `{key: '${input}', value: '[FILL_IN]'}`)}], + [${_(envs).map((env) => `{key: '${env}', value: '[FILL_IN]'}`)}], + ),`; +const jobMocksTemplate = (jobMocksName, stepMocks) => ` +const ${jobMocksName} = [${_(stepMocks).map( + (stepMock) => ` + ${stepMock}`, +)} +];`; +const jobAssertionTemplate = (jobAssertionName, stepAssertions) => ` +const ${jobAssertionName} = (workflowResult, didExecute = true) => { + const steps = [${stepAssertions} + ]; + + for (const expectedStep of steps) { + if (didExecute) { + expect(workflowResult).toEqual(expect.arrayContaining([expectedStep])); + } else { + expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep])); + } + } +};`; +const mocksExportsTemplate = (jobMocks) => ` +module.exports = { + ${_(jobMocks).map((jobMock) => `${jobMock}`)} +};`; +const assertionsExportsTemplate = (jobAssertions) => ` +module.exports = { + ${_(jobAssertions).map((jobAssertion) => `${jobAssertion}`)} +};`; + +const checkArguments = (args) => { + if (args.length > 0 && args[0]) { + return; + } + console.warn('Please provide workflow file name'); + exit(1); +}; +const checkWorkflowFileName = (fileName) => { + if (workflowFileRegex.test(fileName)) { + return; + } + console.warn(`Please provide a valid workflow file name ([workflow].yml) instead of ${fileName}`); + exit(1); +}; +const checkWorkflowFilePath = (filePath) => { + if (fs.existsSync(filePath)) { + return; + } + console.warn(`Provided workflow file does not exist: ${filePath}`); + exit(1); +}; +const checkIfTestFileExists = (testsDirectory, testFileName) => { + if (!fs.existsSync(path.join(testsDirectory, testFileName))) { + return; + } + console.warn(`The test file ${testFileName} already exists, exiting`); + exit(1); +}; +const checkIfMocksFileExists = (mocksDirectory, mocksFileName) => { + if (!fs.existsSync(path.join(mocksDirectory, mocksFileName))) { + return; + } + console.warn(`The mocks file ${mocksFileName} already exists, exiting`); + exit(1); +}; +const checkIfAssertionsFileExists = (assertionsDirectory, assertionsFileName) => { + if (!fs.existsSync(path.join(assertionsDirectory, assertionsFileName))) { + return; + } + console.warn(`The assertions file ${assertionsFileName} already exists, exiting`); + exit(1); +}; +const parseWorkflowFile = (workflow) => { + const workflowJobs = {}; + Object.entries(workflow.jobs).forEach(([jobId, job]) => { + workflowJobs[jobId] = { + steps: [], + }; + job.steps.forEach((step) => { + const workflowStep = { + name: step.name || '', + inputs: _.keys(step.with || {}) || [], + envs: _.keys(step.env || {}) || [], + }; + workflowJobs[jobId].steps.push(workflowStep); + }); + }); + return workflowJobs; +}; +const getMockFileContent = (workflowName, jobs) => { + let content = ''; + const jobMocks = []; + Object.entries(jobs).forEach(([jobId, job]) => { + let mockStepsContent = `\n// ${jobId.toLowerCase()}`; + const stepMocks = []; + job.steps.forEach((step) => { + const stepMockName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__${step.name + .replaceAll(' ', '_') + .replaceAll('-', '_') + .replaceAll(',', '') + .replaceAll('#', '') + .toUpperCase()}__STEP_MOCK`; + stepMocks.push(stepMockName); + mockStepsContent += mockStepTemplate(stepMockName, step, jobId); + }); + const jobMocksName = `${workflowName.toUpperCase()}__${jobId.toUpperCase()}__STEP_MOCKS`; + jobMocks.push(jobMocksName); + mockStepsContent += jobMocksTemplate(jobMocksName, stepMocks); + content += mockStepsContent; + }); + return mockFileTemplate(content, mocksExportsTemplate(jobMocks)); +}; +const getAssertionsFileContent = (workflowName, jobs) => { + let content = ''; + const jobAssertions = []; + Object.entries(jobs).forEach(([jobId, job]) => { + let stepAssertionsContent = ''; + job.steps.forEach((step) => { + stepAssertionsContent += stepAssertionTemplate(step.name, jobId.toUpperCase(), step.name, step.inputs, step.envs); + }); + const jobAssertionName = `assert${jobId.charAt(0).toUpperCase() + jobId.slice(1)}JobExecuted`; + jobAssertions.push(jobAssertionName); + content += jobAssertionTemplate(jobAssertionName, stepAssertionsContent); + }); + return assertionFileTemplate(content, assertionsExportsTemplate(jobAssertions)); +}; +const getTestFileContent = (workflowName) => testFileTemplate(workflowName); + +const call_args = process.argv.slice(2); +checkArguments(call_args); + +const workflowFileName = call_args[0]; +checkWorkflowFileName(workflowFileName); + +const workflowName = workflowFileName.slice(0, -4); +const workflowFilePath = path.join(workflowsDirectory, workflowFileName); +checkWorkflowFilePath(workflowFilePath); + +const workflowTestFileName = `${workflowName}.test.js`; +checkIfTestFileExists(workflowTestsDirectory, workflowTestFileName); + +const workflowTestMocksFileName = `${workflowName}Mocks.js`; +checkIfMocksFileExists(workflowTestMocksDirectory, workflowTestMocksFileName); + +const workflowTestAssertionsFileName = `${workflowName}Assertions.js`; +checkIfAssertionsFileExists(workflowTestAssertionsDirectory, workflowTestAssertionsFileName); + +const workflow = yaml.parse(fs.readFileSync(workflowFilePath, 'utf8')); +const workflowJobs = parseWorkflowFile(workflow); + +const mockFileContent = getMockFileContent(workflowName, workflowJobs); +const mockFilePath = path.join(workflowTestMocksDirectory, workflowTestMocksFileName); +console.log(`Creating mock file ${mockFilePath}`); +fs.writeFileSync(mockFilePath, mockFileContent); +console.log(`Mock file ${mockFilePath} created`); + +const assertionsFileContent = getAssertionsFileContent(workflowName, workflowJobs); +const assertionsFilePath = path.join(workflowTestAssertionsDirectory, workflowTestAssertionsFileName); +console.log(`Creating assertions file ${assertionsFilePath}`); +fs.writeFileSync(assertionsFilePath, assertionsFileContent); +console.log(`Assertions file ${assertionsFilePath} created`); + +const testFileContent = getTestFileContent(workflowName, workflowJobs); +const testFilePath = path.join(workflowTestsDirectory, workflowTestFileName); +console.log(`Creating test file ${testFilePath}`); +fs.writeFileSync(testFilePath, testFileContent); +console.log(`Test file ${testFilePath} created`); diff --git a/workflow_tests/utils/utils.js b/workflow_tests/utils/utils.js new file mode 100644 index 000000000000..32e106cfb1de --- /dev/null +++ b/workflow_tests/utils/utils.js @@ -0,0 +1,178 @@ +const yaml = require('yaml'); +const fs = require('fs'); +const path = require('path'); + +function setUpActParams(act, event = null, eventOptions = null, secrets = null, githubToken = null, envVars = null, inputs = null) { + let updated_act = act; + + if (event && eventOptions) { + // according to `Act` docs event data should be under the key with the event name (`[event]: eventOptions`), but + // for some event types this does not work (like `issues`), but providing the data on the JSON top level does, + // hence `...eventOptions` - this seems to cover all options + const eventData = { + [event]: eventOptions, + ...eventOptions, + }; + updated_act = updated_act.setEvent(eventData); + } + + if (secrets) { + Object.entries(secrets).forEach(([key, value]) => { + updated_act = updated_act.setSecret(key, value); + }); + } + + if (githubToken) { + updated_act = updated_act.setGithubToken(githubToken); + } + + if (envVars) { + Object.entries(envVars).forEach(([key, value]) => { + updated_act = updated_act.setEnv(key, value); + }); + } + + if (inputs) { + Object.entries(inputs).forEach(([key, value]) => { + updated_act = updated_act.setInput(key, value); + }); + } + + return updated_act; +} + +function createMockStep(name, message, job_id = null, inputs = null, in_envs = null, outputs = null, out_envs = null, isSuccessful = true, id = null) { + const mockStepName = name; + let mockWithCommand = 'echo [MOCK]'; + if (job_id) { + mockWithCommand += ` [${job_id}]`; + } + mockWithCommand += ` ${message}`; + if (inputs) { + inputs.forEach((input) => { + mockWithCommand += `, ${input}="\${{ inputs.${input} && inputs.${input} || github.event.inputs.${input} }}"`; + }); + } + if (in_envs) { + in_envs.forEach((env) => { + mockWithCommand += `, ${env}="\${{ env.${env} }}"`; + }); + } + if (outputs) { + Object.entries(outputs).forEach(([key, value]) => { + mockWithCommand += `\necho "${key}=${value}" >> "$GITHUB_OUTPUT"`; + }); + } + if (out_envs) { + Object.entries(out_envs).forEach(([key, value]) => { + mockWithCommand += `\necho "${key}=${value}" >> "$GITHUB_ENV"`; + }); + } + if (!isSuccessful) { + mockWithCommand += '\nexit 1'; + } + const mockStep = { + name: mockStepName, + mockWith: mockWithCommand, + }; + if (id) { + mockStep.id = id; + } + return mockStep; +} + +function createStepAssertion(name, isSuccessful = true, expectedOutput = null, jobId = null, message = null, inputs = null, envs = null) { + const stepName = `Main ${name}`; + const stepStatus = isSuccessful ? 0 : 1; + let stepOutput; + if (expectedOutput !== undefined && expectedOutput !== null) { + stepOutput = expectedOutput; + } else { + stepOutput = '[MOCK]'; + if (jobId) { + stepOutput += ` [${jobId}]`; + } + if (message) { + stepOutput += ` ${message}`; + } + if (inputs) { + inputs.forEach((input) => { + stepOutput += `, ${input.key}=${input.value}`; + }); + } + if (envs) { + envs.forEach((env) => { + stepOutput += `, ${env.key}=${env.value}`; + }); + } + } + return { + name: stepName, + status: stepStatus, + output: stepOutput, + }; +} + +function setJobRunners(act, jobs, workflowPath) { + if (!act || !jobs || !workflowPath) { + return act; + } + + const workflow = yaml.parse(fs.readFileSync(workflowPath, 'utf8')); + Object.entries(jobs).forEach(([jobId, runner]) => { + const job = workflow.jobs[jobId]; + job['runs-on'] = runner; + }); + fs.writeFileSync(workflowPath, yaml.stringify(workflow), 'utf8'); + return act; +} + +function deepCopy(originalObject) { + return JSON.parse(JSON.stringify(originalObject)); +} + +function getLogFilePath(workflowName, testName) { + const logsDir = path.resolve(__dirname, '..', 'logs'); + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir); + } + const workflowTestsLogDir = path.resolve(logsDir, workflowName); + if (!fs.existsSync(workflowTestsLogDir)) { + fs.mkdirSync(workflowTestsLogDir); + } + const cleanTestName = testName.replace(' ', '_').replace('-', '_').substr(0, 240); + return path.resolve(workflowTestsLogDir, `${cleanTestName}.log`); +} + +function removeMockRepoDir() { + const mockDirRepo = path.resolve(__dirname, '..', '..', 'repo'); + if (fs.existsSync(mockDirRepo)) { + fs.rmSync(mockDirRepo, {recursive: true, force: true}); + } +} + +const FILES_TO_COPY_INTO_TEST_REPO = [ + { + src: path.resolve(__dirname, '..', '..', '.github', 'actions'), + dest: '.github/actions', + }, + { + src: path.resolve(__dirname, '..', '..', '.github', 'libs'), + dest: '.github/libs', + }, + { + src: path.resolve(__dirname, '..', '..', '.github', 'scripts'), + dest: '.github/scripts', + }, +]; + +module.exports = { + setUpActParams, + createMockStep, + createStepAssertion, + setJobRunners, + deepCopy, + getLogFilePath, + FILES_TO_COPY_INTO_TEST_REPO, + removeMockRepoDir, +}; diff --git a/workflow_tests/validateGithubActions.test.js b/workflow_tests/validateGithubActions.test.js new file mode 100644 index 000000000000..dfa5e9362ce7 --- /dev/null +++ b/workflow_tests/validateGithubActions.test.js @@ -0,0 +1,90 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/validateGithubActionsAssertions'); +const mocks = require('./mocks/validateGithubActionsMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'validateGithubActions.yml'), + dest: '.github/workflows/validateGithubActions.yml', + }, +]; + +describe('test workflow validateGithubActions', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testValidateGithubActionsWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('pull request opened', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'opened', + }; + it('executes verification', async () => { + const repoPath = mockGithub.repo.getPath('testValidateGithubActionsWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'validateGithubActions.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + verify: mocks.VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'validateGithubActions.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('validateGithubActions', expect.getState().currentTestName), + }); + + assertions.assertVerifyJobExecuted(result); + }); + }); + describe('pull request synchronized', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'synchronize', + }; + it('executes verification', async () => { + const repoPath = mockGithub.repo.getPath('testValidateGithubActionsWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'validateGithubActions.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + verify: mocks.VALIDATEGITHUBACTIONS__VERIFY__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'validateGithubActions.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('validateGithubActions', expect.getState().currentTestName), + }); + + assertions.assertVerifyJobExecuted(result); + }); + }); +}); diff --git a/workflow_tests/verifyPodfile.test.js b/workflow_tests/verifyPodfile.test.js new file mode 100644 index 000000000000..de062af2a2c2 --- /dev/null +++ b/workflow_tests/verifyPodfile.test.js @@ -0,0 +1,133 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/verifyPodfileAssertions'); +const mocks = require('./mocks/verifyPodfileMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'verifyPodfile.yml'), + dest: '.github/workflows/verifyPodfile.yml', + }, +]; + +describe('test workflow verifyPodfile', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + const osbotifyActor = 'OSBotify'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testVerifyPodfileWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('pull request opened', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'opened', + }; + it('executes workflow', async () => { + const repoPath = mockGithub.repo.getPath('testVerifyPodfileWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + act = utils.setJobRunners(act, {verify: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + verify: mocks.VERIFYPODFILE__VERIFY__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('verifyPodfile', expect.getState().currentTestName), + }); + + assertions.assertVerifyJobExecuted(result); + }); + describe('actor is OSBotify', () => { + it('does not execute workflow', async () => { + const repoPath = mockGithub.repo.getPath('testVerifyPodfileWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + act = utils.setJobRunners(act, {verify: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + verify: mocks.VERIFYPODFILE__VERIFY__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'), + mockSteps: testMockSteps, + actor: osbotifyActor, + logFile: utils.getLogFilePath('verifyPodfile', expect.getState().currentTestName), + }); + + assertions.assertVerifyJobExecuted(result, false); + }); + }); + }); + describe('pull request synchronized', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'synchronize', + }; + it('executes workflow', async () => { + const repoPath = mockGithub.repo.getPath('testVerifyPodfileWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + act = utils.setJobRunners(act, {verify: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + verify: mocks.VERIFYPODFILE__VERIFY__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('verifyPodfile', expect.getState().currentTestName), + }); + + assertions.assertVerifyJobExecuted(result); + }); + describe('actor is OSBotify', () => { + it('does not execute workflow', async () => { + const repoPath = mockGithub.repo.getPath('testVerifyPodfileWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + act = utils.setJobRunners(act, {verify: 'ubuntu-latest'}, workflowPath); + const testMockSteps = { + verify: mocks.VERIFYPODFILE__VERIFY__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'verifyPodfile.yml'), + mockSteps: testMockSteps, + actor: osbotifyActor, + logFile: utils.getLogFilePath('verifyPodfile', expect.getState().currentTestName), + }); + + assertions.assertVerifyJobExecuted(result, false); + }); + }); + }); +}); diff --git a/workflow_tests/verifySignedCommits.test.js b/workflow_tests/verifySignedCommits.test.js new file mode 100644 index 000000000000..911208e91f4a --- /dev/null +++ b/workflow_tests/verifySignedCommits.test.js @@ -0,0 +1,90 @@ +const path = require('path'); +const kieMockGithub = require('@kie/mock-github'); +const utils = require('./utils/utils'); +const assertions = require('./assertions/verifySignedCommitsAssertions'); +const mocks = require('./mocks/verifySignedCommitsMocks'); +const eAct = require('./utils/ExtendedAct'); + +jest.setTimeout(90 * 1000); +let mockGithub; +const FILES_TO_COPY_INTO_TEST_REPO = [ + ...utils.deepCopy(utils.FILES_TO_COPY_INTO_TEST_REPO), + { + src: path.resolve(__dirname, '..', '.github', 'workflows', 'verifySignedCommits.yml'), + dest: '.github/workflows/verifySignedCommits.yml', + }, +]; + +describe('test workflow verifySignedCommits', () => { + const githubToken = 'dummy_github_token'; + const actor = 'Dummy Actor'; + + beforeAll(async () => { + // in case of the tests being interrupted without cleanup the mock repo directory may be left behind + // which breaks the next test run, this removes any possible leftovers + utils.removeMockRepoDir(); + }); + + beforeEach(async () => { + // create a local repository and copy required files + mockGithub = new kieMockGithub.MockGithub({ + repo: { + testVerifySignedCommitsWorkflowRepo: { + files: FILES_TO_COPY_INTO_TEST_REPO, + }, + }, + }); + + await mockGithub.setup(); + }); + + afterEach(async () => { + await mockGithub.teardown(); + }); + describe('pull request opened', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'opened', + }; + it('test stub', async () => { + const repoPath = mockGithub.repo.getPath('testVerifySignedCommitsWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'verifySignedCommits.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + verifySignedCommits: mocks.VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'verifySignedCommits.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('verifySignedCommits', expect.getState().currentTestName), + }); + + assertions.assertVerifySignedCommitsJobExecuted(result); + }); + }); + describe('pull request synchronized', () => { + const event = 'pull_request'; + const eventOptions = { + action: 'synchronize', + }; + it('test stub', async () => { + const repoPath = mockGithub.repo.getPath('testVerifySignedCommitsWorkflowRepo') || ''; + const workflowPath = path.join(repoPath, '.github', 'workflows', 'verifySignedCommits.yml'); + let act = new eAct.ExtendedAct(repoPath, workflowPath); + act = utils.setUpActParams(act, event, eventOptions, {}, githubToken); + const testMockSteps = { + verifySignedCommits: mocks.VERIFYSIGNEDCOMMITS__VERIFYSIGNEDCOMMITS__STEP_MOCKS, + }; + const result = await act.runEvent(event, { + workflowFile: path.join(repoPath, '.github', 'workflows', 'verifySignedCommits.yml'), + mockSteps: testMockSteps, + actor, + logFile: utils.getLogFilePath('verifySignedCommits', expect.getState().currentTestName), + }); + + assertions.assertVerifySignedCommitsJobExecuted(result); + }); + }); +});