diff --git a/.github/actions/build-auth/action.yml b/.github/actions/build-auth/action.yml new file mode 100644 index 00000000000..b91daf07995 --- /dev/null +++ b/.github/actions/build-auth/action.yml @@ -0,0 +1,70 @@ +# action.yml +name: "Build Auth" +description: "Build Auth microservice" +inputs: + version: + description: "Version tag" + required: true + upload-build: + default: true + run-integration-tests: + default: false + run-qc: + default: false + github-token: + default: false + sp-creds: + description: "Azure Service Principal creds" + +runs: + using: "composite" + steps: + # These are for CI and not credentials of any system + - name: Set Environment Variables + working-directory: prime-router + shell: bash + run: | + echo >> $GITHUB_ENV DB_USER='prime' + echo >> $GITHUB_ENV DB_PASSWORD='changeIT!' + + - name: Remove unnecessary software + shell: bash + run: | + sudo rm -rf /usr/local/lib/android + + - name: Set up JDK 17 + uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 + with: + java-version: "17" + distribution: "temurin" + cache: "gradle" + + - uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 + + - name: Lint + if: inputs.run-integration-tests == 'true' + run: ./gradlew :auth:ktlintCheck + shell: bash + + - name: Spin up build containers + working-directory: prime-router + shell: bash + run: docker compose -f docker-compose.postgres.yml up -d + + - name: Build auth Package + uses: ./.github/actions/retry + with: + timeout_minutes: 10 + max_attempts: 2 + retry_wait_seconds: 30 + command: | + ./gradlew :auth:build -x test + shell: bash + + - name: Cleanup Gradle Cache + if: inputs.run-integration-tests == 'true' + working-directory: prime-router + run: | + rm -f .gradle/caches/modules-2/modules-2.lock + rm -f .gradle/caches/modules-2/gc.properties + shell: bash diff --git a/.github/actions/build-backend/action.yml b/.github/actions/build-backend/action.yml index ebf83067faa..4d9950d8c16 100644 --- a/.github/actions/build-backend/action.yml +++ b/.github/actions/build-backend/action.yml @@ -33,7 +33,7 @@ runs: sudo rm -rf /usr/local/lib/android - name: Set up JDK 17 - uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 with: java-version: "17" distribution: "temurin" diff --git a/.github/actions/build-submissions/action.yml b/.github/actions/build-submissions/action.yml index e683a5ad28d..4531ecb67ac 100644 --- a/.github/actions/build-submissions/action.yml +++ b/.github/actions/build-submissions/action.yml @@ -33,7 +33,7 @@ runs: sudo rm -rf /usr/local/lib/android - name: Set up JDK 17 - uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 with: java-version: "17" distribution: "temurin" diff --git a/.github/actions/sonarcloud/action.yml b/.github/actions/sonarcloud/action.yml index 8f1791a3c4c..3365018c6c4 100644 --- a/.github/actions/sonarcloud/action.yml +++ b/.github/actions/sonarcloud/action.yml @@ -22,11 +22,11 @@ runs: args: > -Dsonar.coverage.exclusions=prime-router/src/test/**,prime-router/src/testIntegration/**,prime-router/src/main/kotlin/cli/tests/**,frontend-react/**/__mocks__/**,frontend-react/**/mocks/**,frontend-react/**/*.test.*,submissions/src/test/** -Dsonar.cpd.exclusions=frontend-react/**/*.test.*,prime-router/src/test/**,prime-router/src/testIntegration/**,prime-router/src/main/kotlin/cli/tests/**,submissions/src/test/** - -Dsonar.sources=frontend-react/src,prime-router/src,submissions/src,shared/src + -Dsonar.sources=frontend-react/src,prime-router/src,submissions/src,auth/src,shared/src -Dsonar.projectKey=CDCgov_prime-data-hub -Dsonar.organization=cdcgov - -Dsonar.java.binaries=prime-router/build/classes/java/main,prime-router/build/classes/kotlin/main,submissions/build/classes/kotlin/main,shared/build/classes/kotlin/main - -Dsonar.java.libraries=prime-router/build/libs/*.jar,prime-router/build/**/*.jar,submissions/build/**/*.jar,shared/build/**/*.jar + -Dsonar.java.binaries=prime-router/build/classes/java/main,prime-router/build/classes/kotlin/main,submissions/build/classes/kotlin/main,auth/build/classes/kotlin/main,shared/build/classes/kotlin/main + -Dsonar.java.libraries=prime-router/build/libs/*.jar,prime-router/build/**/*.jar,submissions/build/**/*.jar,auth/build/**/*.jar,shared/build/**/*.jar -Dsonar.coverage.jacoco.xmlReportPaths=prime-router/build/reports/jacoco/test/jacocoTestReport.xml -Dsonar.javascript.lcov.reportPaths=frontend-react/coverage/lcov.info diff --git a/.github/workflows/StaleItemsReport.yml b/.github/workflows/StaleItemsReport.yml index dfe57121b8d..f3a11ee97b9 100644 --- a/.github/workflows/StaleItemsReport.yml +++ b/.github/workflows/StaleItemsReport.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 diff --git a/.github/workflows/alert_MBUsers_Inactive.yml b/.github/workflows/alert_MBUsers_Inactive.yml index 8e29bab1724..72396770da0 100644 --- a/.github/workflows/alert_MBUsers_Inactive.yml +++ b/.github/workflows/alert_MBUsers_Inactive.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 diff --git a/.github/workflows/alert_PD_schedule_Slack.yml b/.github/workflows/alert_PD_schedule_Slack.yml index 288e6d5acb2..e9593ec4d9c 100644 --- a/.github/workflows/alert_PD_schedule_Slack.yml +++ b/.github/workflows/alert_PD_schedule_Slack.yml @@ -17,7 +17,7 @@ jobs: steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: IsMonday id: IsMonday if: github.event_name == 'schedule' && github.event.schedule == '7 13 * * Mon' @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 diff --git a/.github/workflows/alert_cert_expire.yml b/.github/workflows/alert_cert_expire.yml index d5f3c088662..430ee3353d4 100644 --- a/.github/workflows/alert_cert_expire.yml +++ b/.github/workflows/alert_cert_expire.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Get runner ip id: runner_ip diff --git a/.github/workflows/alert_resource_costs.yml b/.github/workflows/alert_resource_costs.yml index 92db32281f5..0b16f6491b6 100644 --- a/.github/workflows/alert_resource_costs.yml +++ b/.github/workflows/alert_resource_costs.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Login into Azure uses: ./.github/actions/vpn-azure diff --git a/.github/workflows/alert_stale_branches.yaml b/.github/workflows/alert_stale_branches.yaml index 924f781450a..2849296912d 100644 --- a/.github/workflows/alert_stale_branches.yaml +++ b/.github/workflows/alert_stale_branches.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 - name: Get our Counts diff --git a/.github/workflows/alert_terraform_changes.yml b/.github/workflows/alert_terraform_changes.yml index 304479f0888..a9033ec99ba 100644 --- a/.github/workflows/alert_terraform_changes.yml +++ b/.github/workflows/alert_terraform_changes.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure with: diff --git a/.github/workflows/alert_version_upgrade.yml b/.github/workflows/alert_version_upgrade.yml index 6d66c50dcbb..f8f1255eab8 100644 --- a/.github/workflows/alert_version_upgrade.yml +++ b/.github/workflows/alert_version_upgrade.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 diff --git a/.github/workflows/build_frontend.yaml b/.github/workflows/build_frontend.yaml index 1769065b789..49be6ce187a 100644 --- a/.github/workflows/build_frontend.yaml +++ b/.github/workflows/build_frontend.yaml @@ -29,7 +29,7 @@ jobs: version: ${{ steps.build_vars.outputs.version }} steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars uses: ./.github/actions/build-vars @@ -46,7 +46,7 @@ jobs: steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build frontend uses: ./.github/actions/build-frontend diff --git a/.github/workflows/build_hub.yml b/.github/workflows/build_hub.yml index 3774600f35e..33f4587a15f 100644 --- a/.github/workflows/build_hub.yml +++ b/.github/workflows/build_hub.yml @@ -30,7 +30,7 @@ jobs: has_router_change: ${{ steps.build_vars.outputs.has_router_change }} steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars uses: ./.github/actions/build-vars @@ -56,7 +56,7 @@ jobs: sudo swapon --show - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build backend uses: ./.github/actions/build-backend diff --git a/.github/workflows/cleanup_acr_images.yml b/.github/workflows/cleanup_acr_images.yml index 02e42a25bc6..abf1185097a 100644 --- a/.github/workflows/cleanup_acr_images.yml +++ b/.github/workflows/cleanup_acr_images.yml @@ -13,7 +13,7 @@ jobs: env: [staging, prod] steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN & Login into Azure uses: ./.github/actions/vpn-azure diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dc1210ddb87..cda3706bbdd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/dependency_review.yml b/.github/workflows/dependency_review.yml index dc956eee512..0bbf231149f 100644 --- a/.github/workflows/dependency_review.yml +++ b/.github/workflows/dependency_review.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: 'Dependency Review' uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c with: diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 22300f7d233..3112e70fc10 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -20,7 +20,7 @@ jobs: tf_change: ${{ steps.build_vars.outputs.has_terraform_change }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars uses: ./.github/actions/build-vars @@ -39,7 +39,7 @@ jobs: change_count: ${{ steps.stats1.outputs.change-count }} steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure with: @@ -89,7 +89,7 @@ jobs: working-directory: operations/app/terraform/vars/${{ needs.pre_job.outputs.env_name }} steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure with: diff --git a/.github/workflows/deployment_rollback.yml b/.github/workflows/deployment_rollback.yml index 3c83a690e21..35342708a52 100644 --- a/.github/workflows/deployment_rollback.yml +++ b/.github/workflows/deployment_rollback.yml @@ -29,7 +29,7 @@ jobs: exit 1 - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Set deployment environment run: echo "ENV=${{ github.event.inputs.environment }}" >> $GITHUB_ENV diff --git a/.github/workflows/destroy_demo_environment.yml b/.github/workflows/destroy_demo_environment.yml index e15620c53e0..a64a2e21f81 100644 --- a/.github/workflows/destroy_demo_environment.yml +++ b/.github/workflows/destroy_demo_environment.yml @@ -24,7 +24,7 @@ jobs: working-directory: operations/app/terraform/vars/demo steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use specific version of Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd diff --git a/.github/workflows/export_cost_data.yml b/.github/workflows/export_cost_data.yml index b7f451d3b25..05969238c83 100644 --- a/.github/workflows/export_cost_data.yml +++ b/.github/workflows/export_cost_data.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure with: diff --git a/.github/workflows/frontend_chromatic_main.yml b/.github/workflows/frontend_chromatic_main.yml index 6c56b2bffa9..7ca6151a5df 100644 --- a/.github/workflows/frontend_chromatic_main.yml +++ b/.github/workflows/frontend_chromatic_main.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - On Push Event - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 - name: Use Node.js with yarn @@ -32,7 +32,7 @@ jobs: run: yarn install --immutable - name: Run Chromatic - uses: chromaui/action@6eca23b4399151ac2cfc17fa95190d807c7e9519 + uses: chromaui/action@f4e60a7072abcac4203f4ca50613f28e199a52ba with: workingDir: frontend-react token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/frontend_ci.yml b/.github/workflows/frontend_ci.yml index 2f586b5632a..55aacb3d022 100644 --- a/.github/workflows/frontend_ci.yml +++ b/.github/workflows/frontend_ci.yml @@ -28,7 +28,7 @@ jobs: is_permitted: ${{ github.event_name == 'pull_request' && steps.build_vars.outputs.has_frontend_change == 'true' && steps.build_vars.outputs.is_deployment_pr == 'false' }} steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars uses: ./.github/actions/build-vars @@ -41,7 +41,7 @@ jobs: steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use Node.js with yarn uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: @@ -61,7 +61,7 @@ jobs: steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use Node.js with yarn uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: @@ -98,7 +98,7 @@ jobs: sudo swapon --show - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use Node.js with yarn uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: @@ -149,7 +149,7 @@ jobs: steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use Node.js with yarn uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: @@ -229,7 +229,7 @@ jobs: steps: - name: Checkout - On Pull Request - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 # By default the pull_request event will run on a ephermeral merge commit which simulates a merge between the pull request @@ -247,7 +247,7 @@ jobs: - name: Run Chromatic id: chromatic - uses: chromaui/action@6eca23b4399151ac2cfc17fa95190d807c7e9519 + uses: chromaui/action@f4e60a7072abcac4203f4ca50613f28e199a52ba with: workingDir: frontend-react token: ${{ secrets.GITHUB_TOKEN }} @@ -300,7 +300,7 @@ jobs: # steps: # - name: Check out changes - # uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + # uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # - name: Initialize CodeQL # uses: github/codeql-action/init@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # with: @@ -320,7 +320,7 @@ jobs: # steps: # - name: Check out changes - # uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + # uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # with: # fetch-depth: 0 # - name: Use Node.js with yarn @@ -354,7 +354,7 @@ jobs: # runs-on: ubuntu-latest # steps: # - name: Check out changes - # uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + # uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # - name: Use Node.js with yarn # uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # with: diff --git a/.github/workflows/log_management.yml b/.github/workflows/log_management.yml index 2b6810adb05..4da1340e62c 100644 --- a/.github/workflows/log_management.yml +++ b/.github/workflows/log_management.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest name: Run log management steps steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Workflow Housekeeper - workflows NOT in default branch uses: JosiahSiegel/workflow-housekeeper@731cc20bb613208b34efb6ac74aab4ba147abb50 env: diff --git a/.github/workflows/prepare_deployment_branch.yaml b/.github/workflows/prepare_deployment_branch.yaml index 275d823032a..25cf8aff75e 100644 --- a/.github/workflows/prepare_deployment_branch.yaml +++ b/.github/workflows/prepare_deployment_branch.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Set Environment Variables run: | diff --git a/.github/workflows/publish_docker.yaml b/.github/workflows/publish_docker.yaml index 34f7a2d2011..2c58a5c8b0c 100644 --- a/.github/workflows/publish_docker.yaml +++ b/.github/workflows/publish_docker.yaml @@ -23,7 +23,7 @@ jobs: run_publish_sftp_alpine: ${{ steps.check_sftp_alpine_image.outputs.run_job }} steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 id: skip_check with: @@ -89,7 +89,7 @@ jobs: AZ_ENV: [ demo1, demo2, demo3, test, staging, prod ] steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Log In to the Container Registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 @@ -116,7 +116,7 @@ jobs: packages: write steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Log In to the Container Registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 @@ -144,7 +144,7 @@ jobs: packages: write steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Log In to the Container Registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 diff --git a/.github/workflows/release_chatops_app.yml b/.github/workflows/release_chatops_app.yml index 3b590e47ecc..2cf114cb640 100644 --- a/.github/workflows/release_chatops_app.yml +++ b/.github/workflows/release_chatops_app.yml @@ -20,7 +20,7 @@ jobs: env_name: ${{ steps.build_vars.outputs.env_name }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars @@ -36,7 +36,7 @@ jobs: working-directory: operations/slack-boltjs-app steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: submodules: true diff --git a/.github/workflows/release_to_azure.yml b/.github/workflows/release_to_azure.yml index 00e50056549..619bc91645a 100644 --- a/.github/workflows/release_to_azure.yml +++ b/.github/workflows/release_to_azure.yml @@ -27,7 +27,7 @@ jobs: dns_ip: ${{ steps.build_vars.outputs.dns_ip }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build backend uses: ./.github/actions/build-backend @@ -61,7 +61,7 @@ jobs: working-directory: frontend-react steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build frontend uses: ./.github/actions/build-frontend @@ -90,7 +90,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use specific version of Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Login to Azure if: | @@ -164,7 +164,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 deploy_router_release: name: "Deploy Router Release: ${{ needs.pre_job.outputs.env_name }}" @@ -181,7 +181,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure @@ -220,7 +220,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure diff --git a/.github/workflows/release_to_github.yml b/.github/workflows/release_to_github.yml index 373e48d4ccc..a1b804a342c 100644 --- a/.github/workflows/release_to_github.yml +++ b/.github/workflows/release_to_github.yml @@ -19,7 +19,7 @@ jobs: enable_builds: 'true' steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 - name: Build vars @@ -36,7 +36,7 @@ jobs: working-directory: prime-router steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build backend if: ${{ needs.wf_vars.outputs.enable_builds == 'true' }} uses: ./.github/actions/build-backend @@ -53,7 +53,7 @@ jobs: working-directory: frontend-react steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build frontend if: ${{ needs.wf_vars.outputs.enable_builds == 'true' }} uses: ./.github/actions/build-frontend @@ -78,7 +78,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 - name: Download router artifact diff --git a/.github/workflows/release_trial_frontend.yml b/.github/workflows/release_trial_frontend.yml index baad32b1250..9c8df9192c7 100644 --- a/.github/workflows/release_trial_frontend.yml +++ b/.github/workflows/release_trial_frontend.yml @@ -21,7 +21,7 @@ jobs: version: ${{ github.ref_name }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars @@ -37,7 +37,7 @@ jobs: working-directory: frontend-react steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build frontend uses: ./.github/actions/build-frontend @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure diff --git a/.github/workflows/restore_databases.yml b/.github/workflows/restore_databases.yml index f0051fbeb7c..628f82c377e 100644 --- a/.github/workflows/restore_databases.yml +++ b/.github/workflows/restore_databases.yml @@ -60,7 +60,7 @@ jobs: sink_backup_storage: ${{ env.SINK_BACKUP_STORAGE }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Set env variables shell: bash @@ -137,7 +137,7 @@ jobs: backup_age_valid: ${{ env.BACKUP_AGE_VALID }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: azure/login@a65d910e8af852a8061c627c456678983e180302 with: @@ -228,7 +228,7 @@ jobs: sink_db_server: ${{ needs.pre_job.outputs.sink_db_server }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: azure/login@a65d910e8af852a8061c627c456678983e180302 with: diff --git a/.github/workflows/scan_action_logs.yml b/.github/workflows/scan_action_logs.yml index 8e5389d7aff..2c209176fc4 100644 --- a/.github/workflows/scan_action_logs.yml +++ b/.github/workflows/scan_action_logs.yml @@ -10,15 +10,15 @@ jobs: runs-on: ubuntu-latest name: Scan repo run logs steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Scan run logs uses: josiahsiegel/runleaks@4dd30d107c03b6ade87978e10c94a77015e488f9 id: scan with: github-token: ${{ secrets.RUNLEAKS_TOKEN }} - run-limit: 500 + run-limit: 300 min-days-old: 0 - max-days-old: 2 + max-days-old: 1 patterns-path: ".github/runleaks/patterns.txt" exclusions-path: ".github/runleaks/exclusions.txt" fail-on-leak: false diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index e9e9e58c9c5..2704fa9ba6f 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -9,6 +9,7 @@ on: - "prime-router/**" - "submissions/**" - "shared/**" + - "auth/**" jobs: pre_job: @@ -18,7 +19,7 @@ jobs: has_router_change: ${{ steps.build_vars.outputs.has_router_change }} steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars uses: ./.github/actions/build-vars @@ -30,12 +31,12 @@ jobs: strategy: fail-fast: false matrix: - folder: [prime-router, submissions, shared] + folder: [prime-router, submissions, auth, shared] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: snyk/actions/setup@b98d498629f1c368650224d6d212bf7dfa89e4bf - name: Set up JDK 17 to generate backend coverage stats - uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 with: java-version: "17" distribution: "temurin" diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 0bd98dd2c01..6f996603649 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -42,7 +42,7 @@ jobs: sudo swapon --show - name: "Check out everything" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 with: fetch-depth: 0 @@ -62,7 +62,7 @@ jobs: - name: Set up JDK 17 if: steps.changed-files-yaml.outputs.backend_any_changed == 'true' || steps.branch-name.outputs.is_default == 'true' - uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 with: java-version: "17" distribution: "temurin" @@ -97,6 +97,10 @@ jobs: if: steps.changed-files-yaml.outputs.backend_any_changed == 'true' || steps.branch-name.outputs.is_default == 'true' uses: ./.github/actions/build-submissions + - name: Build Auth Package + if: steps.changed-files-yaml.outputs.backend_any_changed == 'true' || steps.branch-name.outputs.is_default == 'true' + uses: ./.github/actions/build-auth + - name: Perform Java CodeQL Analysis if: steps.changed-files-yaml.outputs.backend_any_changed == 'true' || steps.branch-name.outputs.is_default == 'true' uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/start_frontend_smoke.yml b/.github/workflows/start_frontend_smoke.yml index 673b2ee5197..6e000e723e0 100644 --- a/.github/workflows/start_frontend_smoke.yml +++ b/.github/workflows/start_frontend_smoke.yml @@ -23,7 +23,7 @@ jobs: steps: - name: "Check out changes" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use Node.js with yarn uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 with: diff --git a/.github/workflows/start_test_servers.yml b/.github/workflows/start_test_servers.yml index b1b52c9d2bb..292ef517956 100644 --- a/.github/workflows/start_test_servers.yml +++ b/.github/workflows/start_test_servers.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # Login to Azure - uses: azure/login@a65d910e8af852a8061c627c456678983e180302 diff --git a/.github/workflows/stop_test_servers.yml b/.github/workflows/stop_test_servers.yml index 9fd0ebd0506..5f16822a7f9 100644 --- a/.github/workflows/stop_test_servers.yml +++ b/.github/workflows/stop_test_servers.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure diff --git a/.github/workflows/sync-translation-schemas.yml b/.github/workflows/sync-translation-schemas.yml index c6103d8deba..45ea3cd7f0c 100644 --- a/.github/workflows/sync-translation-schemas.yml +++ b/.github/workflows/sync-translation-schemas.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Login into Azure uses: ./.github/actions/vpn-azure diff --git a/.github/workflows/validate_resources.yml b/.github/workflows/validate_resources.yml index 101557421ec..02af76df69d 100644 --- a/.github/workflows/validate_resources.yml +++ b/.github/workflows/validate_resources.yml @@ -22,7 +22,7 @@ jobs: env_name: ${{ env.VALIDATE_ENV }} steps: - name: Check out changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Connect to VPN and login to Azure uses: ./.github/actions/vpn-azure @@ -101,7 +101,7 @@ jobs: env_name: [ demo1, demo2, demo3 ] steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: azure/login@a65d910e8af852a8061c627c456678983e180302 with: @@ -134,7 +134,7 @@ jobs: concurrency: ${{ matrix.env_name }} steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: azure/login@a65d910e8af852a8061c627c456678983e180302 with: diff --git a/.github/workflows/validate_terraform.yml b/.github/workflows/validate_terraform.yml index 6fa0c54c997..bd3cc505cca 100644 --- a/.github/workflows/validate_terraform.yml +++ b/.github/workflows/validate_terraform.yml @@ -18,7 +18,7 @@ jobs: has_operations_change: ${{ steps.build_vars.outputs.has_terraform_change }} steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Build vars id: build_vars uses: ./.github/actions/build-vars @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check Out Changes - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Use specific version of Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd with: @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Run Checkov action uses: bridgecrewio/checkov-action@d0e41abbcc8c1103c6ae7e451681d071f05e1c20 diff --git a/.gitmodules b/.gitmodules index 2dabe2b7af9..e9bf75b86d0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "operations/slack-boltjs-app"] path = operations/slack-boltjs-app - url = https://github.com/JosiahSiegel/slack-boltjs-app + url = https://github.com/focusconsulting/slack-boltjs-app diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 2360922c337..6eca1793cc0 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -17,7 +17,7 @@ kotlin { } dependencies { - val kotlinVersion = KotlinVersion.CURRENT.toString() + val kotlinVersion by System.getProperties() implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") implementation("org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin:12.1.1") -} \ No newline at end of file +} diff --git a/frontend-react/404.html b/frontend-react/404.html index 6e352fe8836..3250d693a92 100644 --- a/frontend-react/404.html +++ b/frontend-react/404.html @@ -5,10 +5,14 @@ - %VITE_TITLE% - - - + Page not found + + + + diff --git a/frontend-react/e2e/helpers/utils.ts b/frontend-react/e2e/helpers/utils.ts index eaa20ef8e30..75570e0cb17 100644 --- a/frontend-react/e2e/helpers/utils.ts +++ b/frontend-react/e2e/helpers/utils.ts @@ -154,3 +154,14 @@ export function removeDateTime(filename: string) { return filename; } + +export function isAbsoluteURL(url: string): boolean { + return /^(https?|ftp|file|mailto):/.test(url); +} + +export function isAssetURL(url: string): boolean { + // Regular expression to match common asset file extensions at the end of a URL + const assetExtensions = /\.(pdf|png|jpg|jpeg|gif|bmp|svg|webp|mp4|mp3|wav|ogg|avi|mov|mkv|zip|rar|tar|gz|iso)$/i; + + return assetExtensions.test(url); +} diff --git a/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts index 60bc2032f68..c6b94cbb9cd 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/last-mile-failures-page.spec.ts @@ -1,3 +1,4 @@ +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { tableRows } from "../../../../helpers/utils"; import { LastMileFailuresPage } from "../../../../pages/authenticated/admin/last-mile-failures"; import { test as baseTest, expect } from "../../../../test"; @@ -95,7 +96,7 @@ test.describe("Last Mile Failure page", () => { test.use({ storageState: "e2e/.auth/receiver.json" }); test("returns Page Not Found", async ({ lastMileFailuresPage }) => { - await expect(lastMileFailuresPage.page).toHaveTitle(/Page Not Found/); + await expect(lastMileFailuresPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -103,7 +104,7 @@ test.describe("Last Mile Failure page", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test("returns Page Not Found", async ({ lastMileFailuresPage }) => { - await expect(lastMileFailuresPage.page).toHaveTitle(/Page Not Found/); + await expect(lastMileFailuresPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); diff --git a/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts index 6af81d230eb..f5f19cc8620 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/message-id-search-page.spec.ts @@ -1,3 +1,4 @@ +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { noData, tableRows } from "../../../../helpers/utils"; import { MOCK_GET_MESSAGES } from "../../../../mocks/messages"; import { MessageIDSearchPage } from "../../../../pages/authenticated/admin/message-id-search"; @@ -160,7 +161,7 @@ test.describe("Message ID Search Page", () => { messageIDSearchPage.mockError = true; await messageIDSearchPage.reload(); - await expect(messageIDSearchPage.page).toHaveTitle(/Page Not Found/); + await expect(messageIDSearchPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -171,7 +172,7 @@ test.describe("Message ID Search Page", () => { messageIDSearchPage.mockError = true; await messageIDSearchPage.reload(); - await expect(messageIDSearchPage.page).toHaveTitle(/Page Not Found/); + await expect(messageIDSearchPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); }); diff --git a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts index 4068bc091dd..ab33d105683 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-edit-page.spec.ts @@ -1,4 +1,5 @@ import { expect } from "@playwright/test"; +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { tableDataCellValue } from "../../../../helpers/utils"; import { MOCK_GET_ORGANIZATION_IGNORE } from "../../../../mocks/organizations"; import { OrganizationEditPage } from "../../../../pages/authenticated/admin/organization-edit"; @@ -47,14 +48,14 @@ test.describe("Organization Edit Page", () => { test.describe("receiver user", () => { test.use({ storageState: "e2e/.auth/receiver.json" }); test("returns Page Not Found", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationEditPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("sender user", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test("returns Page Not Found", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationEditPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -84,18 +85,24 @@ test.describe("Organization Edit Page", () => { test("has expected 'Meta'", async ({ organizationEditPage }) => { const meta = organizationEditPage.page.getByTestId("gridContainer").getByTestId("grid").nth(2); await expect(meta).toHaveText(organizationEditPage.getOrgMeta(MOCK_GET_ORGANIZATION_IGNORE)); - }); + }); test("has expected 'Description'", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page.getByTestId("description")).toHaveValue(MOCK_GET_ORGANIZATION_IGNORE.description); + await expect(organizationEditPage.page.getByTestId("description")).toHaveValue( + MOCK_GET_ORGANIZATION_IGNORE.description, + ); }); test("has expected 'Jurisdiction'", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page.getByTestId("jurisdiction")).toHaveValue(MOCK_GET_ORGANIZATION_IGNORE.jurisdiction); + await expect(organizationEditPage.page.getByTestId("jurisdiction")).toHaveValue( + MOCK_GET_ORGANIZATION_IGNORE.jurisdiction, + ); }); test("has expected 'Filters'", async ({ organizationEditPage }) => { - await expect(organizationEditPage.page.getByTestId("filters")).toHaveValue(JSON.stringify(MOCK_GET_ORGANIZATION_IGNORE.filters, null, 2)); + await expect(organizationEditPage.page.getByTestId("filters")).toHaveValue( + JSON.stringify(MOCK_GET_ORGANIZATION_IGNORE.filters, null, 2), + ); }); }); @@ -129,9 +136,12 @@ test.describe("Organization Edit Page", () => { test.describe("'Organization Sender Settings' section", () => { test("can create a new organization sender", async ({ organizationEditPage }) => { await organizationEditPage.page - .locator('#orgsendersettings').getByRole('link', { name: 'New' }) + .locator("#orgsendersettings") + .getByRole("link", { name: "New" }) .click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgnewsetting/org/ignore/settingtype/sender`); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgnewsetting/org/ignore/settingtype/sender`, + ); await expect(organizationEditPage.page.getByText(/Org name: ignore/)).toBeVisible(); await expect(organizationEditPage.page.getByText(/Setting Type: sender/)).toBeVisible(); @@ -141,9 +151,20 @@ test.describe("Organization Edit Page", () => { }); test("can edit an organization sender", async ({ organizationEditPage }) => { - const firstOrgSender = await organizationEditPage.page.locator("#orgsendersettings").nth(0).locator("td").nth(0).innerText(); - await organizationEditPage.page.locator('#orgsendersettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`); + const firstOrgSender = await organizationEditPage.page + .locator("#orgsendersettings") + .nth(0) + .locator("td") + .nth(0) + .innerText(); + await organizationEditPage.page + .locator("#orgsendersettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); await expect(organizationEditPage.page.getByText(`Sender name: ${firstOrgSender}`)).toBeVisible(); @@ -162,15 +183,24 @@ test.describe("Organization Edit Page", () => { const orgSenderLocator = firstOrgSender.replace("-", "_"); - await expect(organizationEditPage.page.locator(`#id_Item__${orgSenderLocator}__has_been_saved`).getByTestId("alerttoast")).toHaveText(`Item '${firstOrgSender}' has been saved`); + await expect( + organizationEditPage.page + .locator(`#id_Item__${orgSenderLocator}__has_been_saved`) + .getByTestId("alerttoast"), + ).toHaveText(`Item '${firstOrgSender}' has been saved`); await expect(organizationEditPage.page).toHaveURL(organizationEditPage.url); }); test("can cancel when editing an organization sender", async ({ organizationEditPage }) => { const firstOrgSender = await tableDataCellValue(organizationEditPage.page, 0, 0); - await organizationEditPage.page. - locator('#orgsendersettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`); + await organizationEditPage.page + .locator("#orgsendersettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); await expect(organizationEditPage.page.getByText(`Sender name: ${firstOrgSender}`)).toBeVisible(); @@ -182,9 +212,12 @@ test.describe("Organization Edit Page", () => { test.describe("'Organization Receiver Settings' section", () => { test("can create a new organization receiver", async ({ organizationEditPage }) => { await organizationEditPage.page - .locator('#orgreceiversettings').getByRole('link', { name: 'New' }) + .locator("#orgreceiversettings") + .getByRole("link", { name: "New" }) .click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgnewsetting/org/ignore/settingtype/receiver`); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgnewsetting/org/ignore/settingtype/receiver`, + ); await expect(organizationEditPage.page.getByText(/Org name: ignore/)).toBeVisible(); await expect(organizationEditPage.page.getByText(/Setting Type: receiver/)).toBeVisible(); @@ -194,11 +227,24 @@ test.describe("Organization Edit Page", () => { }); test("can edit an organization receiver", async ({ organizationEditPage }) => { - const firstOrgReceiver = await organizationEditPage.page.locator("#orgreceiversettings").nth(0).locator("td").nth(0).innerText(); - await organizationEditPage.page.locator('#orgreceiversettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`); + const firstOrgReceiver = await organizationEditPage.page + .locator("#orgreceiversettings") + .nth(0) + .locator("td") + .nth(0) + .innerText(); + await organizationEditPage.page + .locator("#orgreceiversettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); - await expect(organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`)).toBeVisible(); + await expect( + organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`), + ).toBeVisible(); await organizationEditPage.orgReceiverEdit.editJsonButton.click(); const modal = organizationEditPage.page.getByTestId("modalWindow").nth(0); @@ -208,23 +254,42 @@ test.describe("Organization Edit Page", () => { await expect(organizationEditPage.orgReceiverEdit.editJsonModal.save).toHaveAttribute("disabled"); await organizationEditPage.orgReceiverEdit.editJsonModal.checkSyntax.click(); - await expect(organizationEditPage.orgReceiverEdit.editJsonModal.save).not.toHaveAttribute("disabled"); + await expect(organizationEditPage.orgReceiverEdit.editJsonModal.save).not.toHaveAttribute( + "disabled", + ); await organizationEditPage.orgReceiverEdit.editJsonModal.save.click(); await expect(modal).toBeHidden(); const orgReceiverLocator = firstOrgReceiver.replace("-", "_"); - await expect(organizationEditPage.page.locator(`#id_Item__${orgReceiverLocator}__has_been_updated`).getByTestId("alerttoast")).toHaveText(`Item '${firstOrgReceiver}' has been updated`); + await expect( + organizationEditPage.page + .locator(`#id_Item__${orgReceiverLocator}__has_been_updated`) + .getByTestId("alerttoast"), + ).toHaveText(`Item '${firstOrgReceiver}' has been updated`); await expect(organizationEditPage.page).toHaveURL(organizationEditPage.url); }); test("can cancel when editing an organization receiver", async ({ organizationEditPage }) => { - const firstOrgReceiver = await organizationEditPage.page.locator("#orgreceiversettings").nth(0).locator("td").nth(0).innerText(); - await organizationEditPage.page.locator('#orgreceiversettings').getByRole('link', { name: 'Edit' }).nth(0).click(); - await expect(organizationEditPage.page).toHaveURL(`/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`); + const firstOrgReceiver = await organizationEditPage.page + .locator("#orgreceiversettings") + .nth(0) + .locator("td") + .nth(0) + .innerText(); + await organizationEditPage.page + .locator("#orgreceiversettings") + .getByRole("link", { name: "Edit" }) + .nth(0) + .click(); + await expect(organizationEditPage.page).toHaveURL( + `/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`, + ); await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); - await expect(organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`)).toBeVisible(); + await expect( + organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`), + ).toBeVisible(); await organizationEditPage.orgReceiverEdit.cancelButton.click(); await expect(organizationEditPage.page).toHaveURL(organizationEditPage.url); diff --git a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts index 70c8cf8079f..cb9ee546ffe 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/organization-settings-page.spec.ts @@ -2,6 +2,7 @@ import { expect } from "@playwright/test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../../../../mocks/organizations"; import { OrganizationPage } from "../../../../pages/authenticated/admin/organization"; import { test as baseTest } from "../../../../test"; @@ -51,14 +52,14 @@ test.describe("Admin Organization Settings Page", () => { test.describe("receiver user", () => { test.use({ storageState: "e2e/.auth/receiver.json" }); test("returns Page Not Found", async ({ organizationPage }) => { - await expect(organizationPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("sender user", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test("returns Page Not Found", async ({ organizationPage }) => { - await expect(organizationPage.page).toHaveTitle(/Page Not Found/); + await expect(organizationPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); @@ -79,8 +80,8 @@ test.describe("Admin Organization Settings Page", () => { test.describe("when there is no error", () => { test("nav contains the 'Admin tools' dropdown with 'Organization Settings' option", async ({ - organizationPage, - }) => { + organizationPage, + }) => { const navItems = organizationPage.page.locator(".usa-nav li"); await expect(navItems).toContainText(["Admin tools"]); @@ -126,8 +127,8 @@ test.describe("Admin Organization Settings Page", () => { i === 0 ? MOCK_GET_ORGANIZATION_SETTINGS_LIST[0] : (MOCK_GET_ORGANIZATION_SETTINGS_LIST.find((i) => i.name === cols[0]) ?? { - name: "INVALID", - }); + name: "INVALID", + }); // if first row, we expect column headers. else, the data row matching id (name) // SetEdit is text of buttons in button column const expectedColContents = diff --git a/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts index edba29a56c0..5f9049f8a09 100644 --- a/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts @@ -1,6 +1,7 @@ -import {addDays, endOfDay, startOfDay, subDays} from "date-fns"; -import {AdminReceiverStatusPage} from "../../../../pages/authenticated/admin/receiver-status"; -import {test as baseTest, expect, logins} from "../../../../test"; +import { addDays, endOfDay, startOfDay, subDays } from "date-fns"; +import { pageNotFound } from "../../../../../src/content/error/ErrorMessages"; +import { AdminReceiverStatusPage } from "../../../../pages/authenticated/admin/receiver-status"; +import { test as baseTest, expect, logins } from "../../../../test"; export interface AdminReceiverStatusPageFixtures { adminReceiverStatusPage: AdminReceiverStatusPage; @@ -38,29 +39,29 @@ const test = baseTest.extend({ test.describe("Admin Receiver Status Page", () => { test.describe("not authenticated", () => { - test("redirects to login", async ({adminReceiverStatusPage}) => { + test("redirects to login", async ({ adminReceiverStatusPage }) => { await expect(adminReceiverStatusPage.page).toHaveURL("/login"); }); }); test.describe("authenticated receiver", () => { - test.use({storageState: logins.receiver.path}); - test("returns Page Not Found", async ({adminReceiverStatusPage}) => { - await expect(adminReceiverStatusPage.page).toHaveTitle(/Page Not Found/); + test.use({ storageState: logins.receiver.path }); + test("returns Page Not Found", async ({ adminReceiverStatusPage }) => { + await expect(adminReceiverStatusPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("authenticated sender", () => { - test.use({storageState: logins.sender.path}); - test("returns Page Not Found", async ({adminReceiverStatusPage}) => { - await expect(adminReceiverStatusPage.page).toHaveTitle(/Page Not Found/); + test.use({ storageState: logins.sender.path }); + test("returns Page Not Found", async ({ adminReceiverStatusPage }) => { + await expect(adminReceiverStatusPage.page).toHaveTitle(new RegExp(pageNotFound)); }); }); test.describe("authenticated admin", () => { - test.use({storageState: logins.admin.path}); + test.use({ storageState: logins.admin.path }); - test("If there is an error, the error is shown on the page", async ({adminReceiverStatusPage}) => { + test("If there is an error, the error is shown on the page", async ({ adminReceiverStatusPage }) => { adminReceiverStatusPage.mockError = true; await adminReceiverStatusPage.reload(); @@ -68,72 +69,66 @@ test.describe("Admin Receiver Status Page", () => { }); test.describe("Header", () => { - test( - "has correct title + heading", - async ({adminReceiverStatusPage}) => { - await adminReceiverStatusPage.testHeader(); - }, - ); + test("has correct title + heading", async ({ adminReceiverStatusPage }) => { + await adminReceiverStatusPage.testHeader(); + }); }); test.describe("When there is no error", () => { test.describe("Displays correctly", () => { - test.describe( - "filters", - () => { - test("date range", async ({adminReceiverStatusPage}) => { - const {button, label, modalOverlay, valueDisplay} = - adminReceiverStatusPage.filterFormInputs.dateRange; - await expect(label).toBeVisible(); - await expect(button).toBeVisible(); - await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); - await expect(modalOverlay).toBeHidden(); - }); + test.describe("filters", () => { + test("date range", async ({ adminReceiverStatusPage }) => { + const { button, label, modalOverlay, valueDisplay } = + adminReceiverStatusPage.filterFormInputs.dateRange; + await expect(label).toBeVisible(); + await expect(button).toBeVisible(); + await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); + await expect(modalOverlay).toBeHidden(); + }); - test("receiver name", async ({adminReceiverStatusPage}) => { - const {input, expectedTooltipText, label, tooltip, expectedDefaultValue} = - adminReceiverStatusPage.filterFormInputs.receiverName; - await expect(label).toBeVisible(); - await expect(input).toBeVisible(); - await expect(input).toHaveValue(expectedDefaultValue); - - await expect(tooltip).toBeHidden(); - await input.hover(); - await expect(tooltip).toBeVisible(); - await expect(tooltip).toHaveText(expectedTooltipText); - }); + test("receiver name", async ({ adminReceiverStatusPage }) => { + const { input, expectedTooltipText, label, tooltip, expectedDefaultValue } = + adminReceiverStatusPage.filterFormInputs.receiverName; + await expect(label).toBeVisible(); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(expectedDefaultValue); + + await expect(tooltip).toBeHidden(); + await input.hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText(expectedTooltipText); + }); - test("results message", async ({adminReceiverStatusPage}) => { - const {input, expectedTooltipText, label, tooltip, expectedDefaultValue} = - adminReceiverStatusPage.filterFormInputs.resultMessage; - await expect(label).toBeVisible(); - await expect(input).toBeVisible(); - await expect(input).toHaveValue(expectedDefaultValue); - - await expect(tooltip).toBeHidden(); - await input.hover(); - await expect(tooltip).toBeVisible(); - await expect(tooltip).toHaveText(expectedTooltipText); - }); + test("results message", async ({ adminReceiverStatusPage }) => { + const { input, expectedTooltipText, label, tooltip, expectedDefaultValue } = + adminReceiverStatusPage.filterFormInputs.resultMessage; + await expect(label).toBeVisible(); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(expectedDefaultValue); + + await expect(tooltip).toBeHidden(); + await input.hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText(expectedTooltipText); + }); - test("success type", async ({adminReceiverStatusPage}) => { - const {input, expectedTooltipText, label, tooltip, expectedDefaultValue} = - adminReceiverStatusPage.filterFormInputs.successType; - await expect(label).toBeVisible(); - await expect(input).toBeVisible(); - await expect(input).toHaveValue(expectedDefaultValue); - - await expect(tooltip).toBeHidden(); - await input.hover(); - await expect(tooltip).toBeVisible(); - await expect(tooltip).toHaveText(expectedTooltipText); - }); - }, - ); + test("success type", async ({ adminReceiverStatusPage }) => { + const { input, expectedTooltipText, label, tooltip, expectedDefaultValue } = + adminReceiverStatusPage.filterFormInputs.successType; + await expect(label).toBeVisible(); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(expectedDefaultValue); + + await expect(tooltip).toBeHidden(); + await input.hover(); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText(expectedTooltipText); + }); + }); // Failures here indicate potential misalignment of playwright/browser timezone test.describe("receiver statuses", () => { - test("time periods", async ({adminReceiverStatusPage}) => { + test("time periods", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverStatusDisplay(); expect(result).toBe(true); }); @@ -142,64 +137,57 @@ test.describe("Admin Receiver Status Page", () => { test.describe("Footer", () => { test("has footer and explicit scroll to footer and scroll to top", async ({ - adminReceiverStatusPage, - }) => { + adminReceiverStatusPage, + }) => { await adminReceiverStatusPage.testFooter(); }); }); test.describe("Functions correctly", () => { test.describe("filters", () => { - test.describe( - "date range", - () => { - test("works through calendar", async ({adminReceiverStatusPage}) => { - const {valueDisplay} = adminReceiverStatusPage.filterFormInputs.dateRange; - const now = new Date(); - const targetFrom = startOfDay(subDays(now, 3)); - const targetTo = addDays(endOfDay(now), 1); - - const reqUrl = await adminReceiverStatusPage.updateFilters({ - dateRange: { - value: [targetFrom, targetTo], - }, - }); - expect(reqUrl).toBeDefined(); - - await expect(valueDisplay).toHaveText( - adminReceiverStatusPage.expectedDateRangeLabelText, - ); - expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ - start_date: targetFrom.toISOString(), - end_date: targetTo.toISOString(), - }); + test.describe("date range", () => { + test("works through calendar", async ({ adminReceiverStatusPage }) => { + const { valueDisplay } = adminReceiverStatusPage.filterFormInputs.dateRange; + const now = new Date(); + const targetFrom = startOfDay(subDays(now, 3)); + const targetTo = addDays(endOfDay(now), 1); + + const reqUrl = await adminReceiverStatusPage.updateFilters({ + dateRange: { + value: [targetFrom, targetTo], + }, }); + expect(reqUrl).toBeDefined(); - test("works through textboxes", async ({adminReceiverStatusPage}) => { - const {valueDisplay} = adminReceiverStatusPage.filterFormInputs.dateRange; - await expect(adminReceiverStatusPage.receiverStatusRowsLocator).not.toHaveCount(0); - const now = new Date(); - const targetFrom = startOfDay(subDays(now, 3)); - const targetTo = addDays(endOfDay(now), 1); - - const reqUrl = await adminReceiverStatusPage.updateFilters({ - dateRange: { - value: [targetFrom, targetTo], - }, - }); - - expect(reqUrl).toBeDefined(); - - await expect(valueDisplay).toHaveText( - adminReceiverStatusPage.expectedDateRangeLabelText, - ); - expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ - start_date: targetFrom.toISOString(), - end_date: targetTo.toISOString(), - }); + await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); + expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ + start_date: targetFrom.toISOString(), + end_date: targetTo.toISOString(), }); - }, - ); + }); + + test("works through textboxes", async ({ adminReceiverStatusPage }) => { + const { valueDisplay } = adminReceiverStatusPage.filterFormInputs.dateRange; + await expect(adminReceiverStatusPage.receiverStatusRowsLocator).not.toHaveCount(0); + const now = new Date(); + const targetFrom = startOfDay(subDays(now, 3)); + const targetTo = addDays(endOfDay(now), 1); + + const reqUrl = await adminReceiverStatusPage.updateFilters({ + dateRange: { + value: [targetFrom, targetTo], + }, + }); + + expect(reqUrl).toBeDefined(); + + await expect(valueDisplay).toHaveText(adminReceiverStatusPage.expectedDateRangeLabelText); + expect(Object.fromEntries(reqUrl!.searchParams.entries())).toMatchObject({ + start_date: targetFrom.toISOString(), + end_date: targetTo.toISOString(), + }); + }); + }); test("receiver name", async ({ adminReceiverStatusPage }) => { const { organizationName, receiverName, successRate } = @@ -229,12 +217,12 @@ test.describe("Admin Receiver Status Page", () => { await expect(receiversStatusRows).toHaveCount(adminReceiverStatusPage.timePeriodData.length); }); - test("result message", async ({adminReceiverStatusPage}) => { + test("result message", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverMessage(); expect(result).toBe(true); }); - test("success type", async ({adminReceiverStatusPage}) => { + test("success type", async ({ adminReceiverStatusPage }) => { const [failRow, , mixedRow] = adminReceiverStatusPage.timePeriodData; const failRowTitle = adminReceiverStatusPage.getExpectedReceiverStatusRowTitle( failRow.organizationName, @@ -272,7 +260,7 @@ test.describe("Admin Receiver Status Page", () => { test.describe("receiver statuses", () => { test.describe("date range length changes", () => { - test("increases", async ({adminReceiverStatusPage}) => { + test("increases", async ({ adminReceiverStatusPage }) => { const rows = adminReceiverStatusPage.receiverStatusRowsLocator; const days = rows.nthCustom(0).days; await expect(rows).not.toHaveCount(0); @@ -287,7 +275,7 @@ test.describe("Admin Receiver Status Page", () => { await expect(days).toHaveCount(4); }); - test("decreases", async ({adminReceiverStatusPage}) => { + test("decreases", async ({ adminReceiverStatusPage }) => { const rows = adminReceiverStatusPage.receiverStatusRowsLocator; const days = rows.nthCustom(0).days; await expect(rows).not.toHaveCount(0); @@ -303,17 +291,17 @@ test.describe("Admin Receiver Status Page", () => { }); }); - test("time period modals", async ({adminReceiverStatusPage}) => { + test("time period modals", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverTimePeriodModals(); expect(result).toBe(true); }); - test("receiver org links", async ({adminReceiverStatusPage}) => { + test("receiver org links", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverOrgLinks(); expect(result).toBe(true); }); - test("receiver links", async ({adminReceiverStatusPage}) => { + test("receiver links", async ({ adminReceiverStatusPage }) => { const result = await adminReceiverStatusPage.testReceiverLinks(); expect(result).toBe(true); }); diff --git a/frontend-react/e2e/spec/chromium-only/authenticated/last-mile-failures-page-user-flow.spec.ts b/frontend-react/e2e/spec/chromium-only/authenticated/last-mile-failures-page-user-flow.spec.ts index 6e4baa74876..c10e94b3f5d 100644 --- a/frontend-react/e2e/spec/chromium-only/authenticated/last-mile-failures-page-user-flow.spec.ts +++ b/frontend-react/e2e/spec/chromium-only/authenticated/last-mile-failures-page-user-flow.spec.ts @@ -83,7 +83,7 @@ test.describe("Last Mile Failure page", await expect(modal).toContainText(`Report ID:${reportIdCell}`); }); - test("table column 'Receiver' will open receiver edit page", async ({ lastMileFailuresPage, isMockDisabled }) => { + test.skip("table column 'Receiver' will open receiver edit page", async ({ lastMileFailuresPage, isMockDisabled }) => { test.skip(!isMockDisabled, "Mocks are ENABLED, skipping test"); const receiver = tableRows(lastMileFailuresPage.page).nth(0).locator("td").nth(2); const receiverCell = await receiver.getByRole("link").innerText(); diff --git a/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-edit-page-user-flow.spec.ts b/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-edit-page-user-flow.spec.ts new file mode 100644 index 00000000000..2762d708025 --- /dev/null +++ b/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-edit-page-user-flow.spec.ts @@ -0,0 +1,113 @@ +import { expect } from "@playwright/test"; +import { OrganizationEditPage } from "../../../pages/authenticated/admin/organization-edit"; +import { test as baseTest } from "../../../test"; + +export interface OrganizationEditPageFixtures { + organizationEditPage: OrganizationEditPage; +} + +const test = baseTest.extend({ + organizationEditPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }, + use, + ) => { + const page = new OrganizationEditPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe("Organization Edit Page", { + tag: "@smoke", +}, () => { + test.describe("admin user", () => { + test.use({storageState: "e2e/.auth/admin.json"}); + + test("has correct title", async ({organizationEditPage}) => { + await organizationEditPage.testHeader(false); + await expect(organizationEditPage.page.getByText(/Org name: ignore/)).toBeVisible(); + }); + + test.describe("edit section", () => { + test("has expected 'Meta'", async ({organizationEditPage}) => { + const meta = organizationEditPage.page.getByTestId("gridContainer").getByTestId("grid").nth(2); + await expect(meta).not.toBeEmpty(); + }); + + test("has expected 'Description'", async ({organizationEditPage}) => { + await expect(organizationEditPage.page.getByTestId("description")).not.toBeEmpty(); + }); + + test("has expected 'Jurisdiction'", async ({organizationEditPage}) => { + await expect(organizationEditPage.page.getByTestId("jurisdiction")).not.toBeEmpty(); + }); + }); + + test.describe("'Organization Sender Settings' section", () => { + test.beforeEach(async ({ organizationEditPage }) => { + await organizationEditPage.page.locator("#orgsendersettings .usa-table tbody").waitFor({ state: "visible" }); + }); + + test("has at least one sender listed in the table", async ({organizationEditPage}) => { + const rowCount = await organizationEditPage.page.locator("#orgsendersettings .usa-table tbody tr").count(); + expect(rowCount).toBeGreaterThanOrEqual(1); + }); + + test("can edit an organization sender", async ({ organizationEditPage }) => { + const firstOrgSender = await organizationEditPage.page.locator("#orgsendersettings").nth(0).locator("td").nth(0).innerText(); + await organizationEditPage.page.locator('#orgsendersettings').getByRole('link', { name: 'Edit' }).nth(0).click(); + await expect(organizationEditPage.page).toHaveURL(`/admin/orgsendersettings/org/ignore/sender/${firstOrgSender}/action/edit`); + await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); + await expect(organizationEditPage.page.getByText(`Sender name: ${firstOrgSender}`)).toBeVisible(); + + await expect(organizationEditPage.page.getByTestId("name")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("format")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("topic")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("customerStatus")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("processingType")).not.toBeEmpty(); + }); + }); + + test.describe("'Organization Receiver Settings' section", () => { + test.beforeEach(async ({ organizationEditPage }) => { + await organizationEditPage.page.locator("#orgreceiversettings .usa-table tbody").waitFor({ state: "visible" }); + }); + + test("has at least one sender listed in the table", async ({organizationEditPage}) => { + const rowCount = await organizationEditPage.page.locator("#orgreceiversettings .usa-table tbody tr").count(); + expect(rowCount).toBeGreaterThanOrEqual(1); + }); + + test("can edit an organization receiver", async ({ organizationEditPage }) => { + const firstOrgReceiver = await organizationEditPage.page.locator("#orgreceiversettings").nth(0).locator("td").nth(0).innerText(); + await organizationEditPage.page.locator('#orgreceiversettings').getByRole('link', { name: 'Edit' }).nth(0).click(); + await expect(organizationEditPage.page).toHaveURL(`/admin/orgreceiversettings/org/ignore/receiver/${firstOrgReceiver}/action/edit`); + await expect(organizationEditPage.page.getByText(`Org name: ignore`)).toBeVisible(); + await expect(organizationEditPage.page.getByText(`Receiver name: ${firstOrgReceiver}`)).toBeVisible(); + + await expect(organizationEditPage.page.getByTestId("name")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("topic")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("customerStatus")).not.toBeEmpty(); + await expect(organizationEditPage.page.getByTestId("translation")).not.toBeEmpty(); + }); + }); + }); +}); diff --git a/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-page-user-flow.spec.ts b/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-page-user-flow.spec.ts index 0cd6327d074..c1decd33a17 100644 --- a/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-page-user-flow.spec.ts +++ b/frontend-react/e2e/spec/chromium-only/authenticated/organization-settings-page-user-flow.spec.ts @@ -1,6 +1,5 @@ import { expect } from "@playwright/test"; import { tableRows } from "../../../helpers/utils"; -import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../../../mocks/organizations"; import { OrganizationPage } from "../../../pages/authenticated/admin/organization"; import { test as baseTest } from "../../../test"; @@ -72,23 +71,24 @@ test.describe("Admin Organization Settings Page - user flow smoke tests", { test("filtering works as expected", async ({organizationPage}) => { const table = organizationPage.page.getByRole("table"); - const {description, name, jurisdiction, stateCode} = MOCK_GET_ORGANIZATION_SETTINGS_LIST[2]; + const firstDataRow = organizationPage.page.getByRole("table").getByRole("row").nth(1); + const firstDataRowName = (await firstDataRow.getByRole("cell").nth(0).textContent()) ?? "INVALID"; const filterBox = organizationPage.page.getByRole("textbox", { name: "Filter:", }); await expect(filterBox).toBeVisible(); - await filterBox.fill(name); + await filterBox.fill(firstDataRowName); const rows = await table.getByRole("row").all(); expect(rows).toHaveLength(2); const cols = rows[1].getByRole("cell").allTextContents(); const expectedColContents = [ - name, - description ?? "", - jurisdiction ?? "", - stateCode ?? "", - "", + await firstDataRow.getByRole("cell").nth(0).textContent(), + await firstDataRow.getByRole("cell").nth(1).textContent() ?? "", + await firstDataRow.getByRole("cell").nth(2).textContent() ?? "", + await firstDataRow.getByRole("cell").nth(3).textContent() ?? "", + await firstDataRow.getByRole("cell").nth(4).textContent() ?? "", "SetEdit", ]; diff --git a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts index a53fdbc0b7a..34486c7505d 100644 --- a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts +++ b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts @@ -1,7 +1,9 @@ /* eslint-disable playwright/no-networkidle */ import axios, { AxiosError } from "axios"; import * as fs from "fs"; -import { test as baseTest, expect } from "../../test"; +import { pageNotFound } from "../../../src/content/error/ErrorMessages"; +import { isAbsoluteURL, isAssetURL } from "../../helpers/utils"; +import { test as baseTest, Browser, chromium, expect } from "../../test"; const test = baseTest.extend({}); @@ -16,7 +18,6 @@ const test = baseTest.extend({}); test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () => { let urlPaths: string[] = []; - const normalizeUrl = (href: string, baseUrl: string) => new URL(href, baseUrl).toString(); // Using our sitemap.xml, we'll create a pathnames array // We cannot use our POM, we must @@ -26,7 +27,7 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const response = await page.goto("/sitemap.xml"); const sitemapXml = await response!.text(); // Since we don't want to use any external XML parsing libraries, - // we can use page.evaluate, but that creates it's own execution context + // we can use page.evaluate, but that creates its own execution context // wherein we need to explicitly return something, which is why // we have the convoluted // elem.textContent ? new URL(elem.textContent).pathname : null, @@ -43,22 +44,22 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = }); test("Check if paths were fetched", () => { - expect(urlPaths.length).toBeGreaterThan(0); + expect(urlPaths.length).toBeGreaterThan(0); // Ensure that paths were fetched correctly }); test("Check all public-facing URLs and their links for a valid 200 response", async ({ page, frontendWarningsLogPath, isFrontendWarningsLog, + baseURL, }) => { let aggregateHref = []; // Set test timeout to be 1 minute instead of 30 seconds - test.setTimeout(60000); + test.setTimeout(120000); for (const path of urlPaths) { await page.goto(path, { waitUntil: "networkidle", }); - const baseUrl = new URL(page.url()).origin; const allATags = await page.getByRole("link", { includeHidden: true }).elementHandles(); @@ -66,11 +67,12 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const href = await aTag.getAttribute("href"); // ONLY include http, https and relative path names if (href && /^(https?:|\/)/.test(href)) { - aggregateHref.push(normalizeUrl(href, baseUrl)); + aggregateHref.push(href); } } } - // Remove any link duplicates to save resources + + // Remove duplicate links aggregateHref = [...new Set(aggregateHref)]; const axiosInstance = axios.create({ @@ -79,36 +81,81 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const warnings: { url: string; message: string }[] = []; - const validateLink = async (url: string) => { + const validateLink = async (browser: Browser, url: string) => { + // Our app does not properly handle 200 vs 400 HTTP codes for our pages + // so we cannot simply use Axios since it's an HTTP client only. + // This means we must actually navigate to the page(s) with Playwright + // to then decipher the rendered HTML DOM content to then determine + // if the page is valid or not. isAbsoluteURL determines if the page + // is an internal link or external one by determining if it's an + // absolute URL or a relative URL. + + if (isAbsoluteURL(url) || isAssetURL(url)) { + try { + const normalizedURL = new URL(url, baseURL).toString(); + const response = await axiosInstance.get(normalizedURL); + return { url, status: response.status }; + } catch (error) { + const e = error as AxiosError; + warnings.push({ url, message: e.message }); + return { url, status: e.response ? e.response.status : 400 }; + } + } else { + // For internal relative URLs, use Playwright to navigate and check the page content + const context = await browser.newContext(); + const page = await context.newPage(); + + try { + const absoluteUrl = new URL(url, baseURL).toString(); + await page.goto(absoluteUrl, { waitUntil: "load" }); + + const pageContent = await page.content(); + const hasPageNotFoundText = pageContent.includes(pageNotFound); + const isErrorWrapperVisible = await page.locator('[data-testid="error-page-wrapper"]').isVisible(); + + if (hasPageNotFoundText && isErrorWrapperVisible) { + warnings.push({ url, message: "Internal link: Page not found" }); + return { url, status: 404 }; + } + + return { url, status: 200 }; + } catch (error) { + warnings.push({ url, message: "Internal link: Page error" }); + return { url, status: 400 }; + } finally { + await page.close(); + await context.close(); + } + } + }; + + const browser = await chromium.launch(); + + const results = []; + for (const href of aggregateHref) { try { - const response = await axiosInstance.get(url); - return { url, status: response.status }; + const result = await validateLink(browser, href); + results.push(result); } catch (error) { - const e = error as AxiosError; - console.error(`Error accessing ${url}:`, e.message); - const warning = { url: url, message: e.message }; - warnings.push(warning); - - return { - url, - status: e.response ? e.response.status : "Request failed", - }; + console.error(`Issue validating link: ${href}`, error); + results.push({ url: href, status: 500 }); } - }; + } - const results = await Promise.all(aggregateHref.map((href) => validateLink(href))); + await browser.close(); if (isFrontendWarningsLog && warnings.length > 0) { fs.writeFileSync(frontendWarningsLogPath, `${JSON.stringify(warnings)}\n`); } results.forEach((result) => { - try { - expect(result.status).toBe(200); - } catch (error) { - const e = error as AxiosError; - console.warn(`Non-fatal: ${e.message}`); + if (result.status !== 200) { + console.warn(`Warning: ${result.url} returned status ${result.status}`); } }); + + // Required expect statement + if somehow the warnings and number of links + // are the same, that's a huge problem. + expect(warnings.length).toBeLessThan(aggregateHref.length); }); }); diff --git a/frontend-react/package.json b/frontend-react/package.json index 74e016d89d4..47d201b9a26 100644 --- a/frontend-react/package.json +++ b/frontend-react/package.json @@ -25,6 +25,7 @@ "history": "^5.3.0", "html-to-text": "^9.0.5", "lodash": "^4.17.21", + "p-limit": "^6.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-helmet-async": "^2.0.5", @@ -72,7 +73,7 @@ "test:debug": "cross-env DEBUG_PRINT_LIMIT=100000 vitest --run --no-file-parallelism", "test:ci": "cross-env VITE_BACKEND_URL=http://localhost vitest --coverage", "test:ui": "cross-env vitest --ui", - "test:e2e": "playwright test", + "test:e2e": "playwright test --trace on", "test:e2e-smoke": "MOCK_DISABLED=true playwright test --project chromium --grep @smoke", "test:e2e-ui": "playwright test --ui", "test:e2e-ui:smoke": "MOCK_DISABLED=true playwright test --project chromium --grep @smoke --ui", diff --git a/frontend-react/playwright.config.ts b/frontend-react/playwright.config.ts index 428b8dbb7ef..2a57ce85c36 100644 --- a/frontend-react/playwright.config.ts +++ b/frontend-react/playwright.config.ts @@ -25,6 +25,7 @@ export default defineConfig({ // Tests sharded in CI runner and reported as blobs that are later turned into html report reporter: isCi ? [["blob", { outputDir: "e2e-data/report" }]] : [["html", { outputFolder: "e2e-data/report" }]], outputDir: "e2e-data/results", + timeout: 1000 * 180, use: { // keep playwright and browser timezones aligned. set preferably UTC by env var timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, diff --git a/frontend-react/src/AppRouter.tsx b/frontend-react/src/AppRouter.tsx index 2a65608fb01..b6d41cd6487 100644 --- a/frontend-react/src/AppRouter.tsx +++ b/frontend-react/src/AppRouter.tsx @@ -9,172 +9,85 @@ import { PERMISSIONS } from "./utils/UsefulTypes"; /* Content Pages */ const Home = lazy(lazyRouteMarkdown(() => import("./content/home/index.mdx"))); -const About = lazy( - lazyRouteMarkdown(() => import("./content/about/index.mdx")), -); -const OurNetwork = lazy( - lazyRouteMarkdown(() => import("./content/about/our-network.mdx")), -); -const Roadmap = lazy( - lazyRouteMarkdown(() => import("./content/about/roadmap.mdx")), -); +const About = lazy(lazyRouteMarkdown(() => import("./content/about/index.mdx"))); +const OurNetwork = lazy(lazyRouteMarkdown(() => import("./content/about/our-network.mdx"))); +const Roadmap = lazy(lazyRouteMarkdown(() => import("./content/about/roadmap.mdx"))); const News = lazy(lazyRouteMarkdown(() => import("./content/about/news.mdx"))); -const Security = lazy( - lazyRouteMarkdown(() => import("./content/about/security.mdx")), -); -const ReleaseNotes = lazy( - lazyRouteMarkdown(() => import("./content/about/release-notes.mdx")), -); -const CaseStudies = lazy( - lazyRouteMarkdown(() => import("./content/about/case-studies.mdx")), -); +const Security = lazy(lazyRouteMarkdown(() => import("./content/about/security.mdx"))); +const ReleaseNotes = lazy(lazyRouteMarkdown(() => import("./content/about/release-notes.mdx"))); +const CaseStudies = lazy(lazyRouteMarkdown(() => import("./content/about/case-studies.mdx"))); const ReferHealthcareOrganizations = lazy( - lazyRouteMarkdown( - () => - import( - "./content/managing-your-connection/refer-healthcare-organizations.mdx" - ), - ), + lazyRouteMarkdown(() => import("./content/managing-your-connection/refer-healthcare-organizations.mdx")), ); -const GettingStartedSendingData = lazy( - lazyRouteMarkdown( - () => import("./content/getting-started/sending-data.mdx"), - ), -); +const GettingStartedSendingData = lazy(lazyRouteMarkdown(() => import("./content/getting-started/sending-data.mdx"))); const GettingStartedReceivingData = lazy( - lazyRouteMarkdown( - () => import("./content/getting-started/receiving-data.mdx"), - ), + lazyRouteMarkdown(() => import("./content/getting-started/receiving-data.mdx")), ); const ReportStreamApiIndex = lazy( - lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/ReportStreamApi.mdx" - ), - ), -); -const DeveloperResourcesIndex = lazy( - lazyRouteMarkdown( - () => import("./content/developer-resources/index-page.mdx"), - ), + lazyRouteMarkdown(() => import("./content/developer-resources/reportstream-api/ReportStreamApi.mdx")), ); +const DeveloperResourcesIndex = lazy(lazyRouteMarkdown(() => import("./content/developer-resources/index-page.mdx"))); const ReportStreamApiGettingStarted = lazy( lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/getting-started/GettingStarted.mdx" - ), + () => import("./content/developer-resources/reportstream-api/getting-started/GettingStarted.mdx"), ), ); const ReportStreamApiDocumentation = lazy( - lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/documentation/Documentation.mdx" - ), - ), + lazyRouteMarkdown(() => import("./content/developer-resources/reportstream-api/documentation/Documentation.mdx")), ); const ReportStreamApiDocumentationResponses = lazy( lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/documentation/ResponsesFromReportStream.mdx" - ), + () => import("./content/developer-resources/reportstream-api/documentation/ResponsesFromReportStream.mdx"), ), ); const ManagingYourConnectionIndex = lazy( - lazyRouteMarkdown( - () => import("./content/managing-your-connection/index.mdx"), - ), -); -const SupportIndex = lazy( - lazyRouteMarkdown(() => import("./content/support/index.mdx")), + lazyRouteMarkdown(() => import("./content/managing-your-connection/index.mdx")), ); +const SupportIndex = lazy(lazyRouteMarkdown(() => import("./content/support/index.mdx"))); const ReportStreamApiDocumentationPayloads = lazy( lazyRouteMarkdown( - () => - import( - "./content/developer-resources/reportstream-api/documentation/SamplePayloadsAndOutput.mdx" - ), + () => import("./content/developer-resources/reportstream-api/documentation/SamplePayloadsAndOutput.mdx"), ), ); /* Public Pages */ const TermsOfService = lazy(() => import("./pages/TermsOfService")); -const LoginCallback = lazy( - () => import("./shared/LoginCallback/LoginCallback"), -); -const LogoutCallback = lazy( - () => import("./shared/LogoutCallback/LogoutCallback"), -); +const LoginCallback = lazy(() => import("./shared/LoginCallback/LoginCallback")); +const LogoutCallback = lazy(() => import("./shared/LogoutCallback/LogoutCallback")); const Login = lazy(() => import("./pages/Login")); -const ErrorNoPage = lazy( - () => import("./pages/error/legacy-content/ErrorNoPage"), -); +const ErrorNoPage = lazy(() => import("./pages/error/legacy-content/ErrorNoPage")); /* Auth Pages */ const FeatureFlagsPage = lazy(() => import("./pages/misc/FeatureFlags")); -const SubmissionDetailsPage = lazy( - () => import("./pages/submissions/SubmissionDetails"), -); +const SubmissionDetailsPage = lazy(() => import("./pages/submissions/SubmissionDetails")); const SubmissionsPage = lazy(() => import("./pages/submissions/Submissions")); const AdminMainPage = lazy(() => import("./pages/admin/AdminMain")); const AdminOrgNewPage = lazy(() => import("./pages/admin/AdminOrgNew")); const AdminOrgEditPage = lazy(() => import("./pages/admin/AdminOrgEdit")); -const EditSenderSettingsPage = lazy( - () => import("./components/Admin/EditSenderSettings"), -); +const EditSenderSettingsPage = lazy(() => import("./components/Admin/EditSenderSettings")); const AdminLMFPage = lazy(() => import("./pages/admin/AdminLastMileFailures")); -const AdminMessageTrackerPage = lazy( - () => import("./pages/admin/AdminMessageTracker"), -); +const AdminMessageTrackerPage = lazy(() => import("./pages/admin/AdminMessageTracker")); const AdminReceiverDashPage = lazy( - () => - import( - "./pages/admin/receiver-dashboard/AdminReceiverDashboardPage/AdminReceiverDashboardPage" - ), -); -const DeliveryDetailPage = lazy( - () => import("./pages/deliveries/details/DeliveryDetail"), -); -const ValueSetsDetailPage = lazy( - () => import("./pages/admin/value-set-editor/ValueSetsDetail"), -); -const ValueSetsIndexPage = lazy( - () => import("./pages/admin/value-set-editor/ValueSetsIndex"), + () => import("./pages/admin/receiver-dashboard/AdminReceiverDashboardPage/AdminReceiverDashboardPage"), ); +const DeliveryDetailPage = lazy(() => import("./pages/deliveries/details/DeliveryDetail")); +const ValueSetsDetailPage = lazy(() => import("./pages/admin/value-set-editor/ValueSetsDetail")); +const ValueSetsIndexPage = lazy(() => import("./pages/admin/value-set-editor/ValueSetsIndex")); const DeliveriesPage = lazy(() => import("./pages/deliveries/Deliveries")); -const EditReceiverSettingsPage = lazy( - () => import("./components/Admin/EditReceiverSettings"), -); +const EditReceiverSettingsPage = lazy(() => import("./components/Admin/EditReceiverSettings")); const AdminRevHistoryPage = lazy(() => import("./pages/admin/AdminRevHistory")); -const MessageDetailsPage = lazy( - () => import("./components/MessageTracker/MessageDetails"), -); -const ManagePublicKeyPage = lazy( - () => import("./components/ManagePublicKey/ManagePublicKey"), -); -const DataDashboardPage = lazy( - () => import("./pages/data-dashboard/DataDashboard"), -); -const ReportDetailsPage = lazy( - () => import("./components/DataDashboard/ReportDetails/ReportDetails"), -); +const MessageDetailsPage = lazy(() => import("./components/MessageTracker/MessageDetails")); +const ManagePublicKeyPage = lazy(() => import("./components/ManagePublicKey/ManagePublicKey")); +const DataDashboardPage = lazy(() => import("./pages/data-dashboard/DataDashboard")); +const ReportDetailsPage = lazy(() => import("./components/DataDashboard/ReportDetails/ReportDetails")); const FacilitiesProvidersPage = lazy( - () => - import( - "./components/DataDashboard/FacilitiesProviders/FacilitiesProviders" - ), + () => import("./components/DataDashboard/FacilitiesProviders/FacilitiesProviders"), ); const FacilityProviderSubmitterDetailsPage = lazy( - () => - import( - "./components/DataDashboard/FacilityProviderSubmitterDetails/FacilityProviderSubmitterDetails" - ), + () => import("./components/DataDashboard/FacilityProviderSubmitterDetails/FacilityProviderSubmitterDetails"), ); const NewSettingPage = lazy(() => import("./components/Admin/NewSetting")); @@ -343,9 +256,7 @@ export const appRoutes: RouteObject[] = [ children: [ { path: "", - element: ( - - ), + element: , index: true, handle: { isContentPage: true, @@ -353,18 +264,14 @@ export const appRoutes: RouteObject[] = [ }, { path: "responses-from-reportstream", - element: ( - - ), + element: , handle: { isContentPage: true, }, }, { path: "sample-payloads-and-output", - element: ( - - ), + element: , handle: { isContentPage: true, }, @@ -491,27 +398,15 @@ export const appRoutes: RouteObject[] = [ }, { path: "facility/:senderId", - element: ( - - ), + element: , }, { path: "provider/:senderId", - element: ( - - ), + element: , }, { path: "submitter/:senderId", - element: ( - - ), + element: , }, ], }, diff --git a/frontend-react/src/content/error/ErrorMessages.ts b/frontend-react/src/content/error/ErrorMessages.ts index b6ebe5ea076..4e0e5586e05 100644 --- a/frontend-react/src/content/error/ErrorMessages.ts +++ b/frontend-react/src/content/error/ErrorMessages.ts @@ -11,8 +11,7 @@ export interface ParagraphWithTitle { export type ErrorDisplayMessage = ParagraphWithTitle | string; /** Default message for an error */ -export const GENERIC_ERROR_STRING = - "Our apologies, there was an error loading this content."; +export const GENERIC_ERROR_STRING = "Our apologies, there was an error loading this content."; /** Default content for an error page */ export const GENERIC_ERROR_PAGE_CONFIG: ErrorDisplayMessage = { header: "An error has occurred", @@ -21,3 +20,5 @@ export const GENERIC_ERROR_PAGE_CONFIG: ErrorDisplayMessage = { have been automatically notified and will be looking into this with the utmost urgency.`, }; + +export const pageNotFound = "Page not found"; diff --git a/frontend-react/src/content/home/index.mdx b/frontend-react/src/content/home/index.mdx index 5d196b9e14c..c17cecde963 100644 --- a/frontend-react/src/content/home/index.mdx +++ b/frontend-react/src/content/home/index.mdx @@ -25,7 +25,7 @@ import site from "../site.json";
-
+

How we help you

diff --git a/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx b/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx index a076f75d3f4..aceb33d7c51 100644 --- a/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx +++ b/frontend-react/src/pages/error/legacy-content/ErrorNoPage.tsx @@ -2,6 +2,7 @@ import { Button } from "@trussworks/react-uswds"; import { Helmet } from "react-helmet-async"; import { useNavigate } from "react-router-dom"; +import { pageNotFound } from "../../../content/error/ErrorMessages"; import site from "../../../content/site.json"; export const ErrorNoPage = () => { @@ -9,42 +10,31 @@ export const ErrorNoPage = () => { return ( <> - Page Not Found | {import.meta.env.VITE_TITLE} + + {pageNotFound} | {import.meta.env.VITE_TITLE} + -
+
-

Page not found

+

{pageNotFound}

- We’re sorry, we can’t find the page you're - looking for. It might have been removed, changed - names, or is otherwise unavailable. + We’re sorry, we can’t find the page you're looking for. It might have been removed, + changed names, or is otherwise unavailable.

- If you typed the URL directly, check your - spelling and capitalization. Our URLs look like - this:{" "} - - reportstream.cdc.gov/example-one - - . + If you typed the URL directly, check your spelling and capitalization. Our URLs look + like this: reportstream.cdc.gov/example-one.

- Visit our homepage or contact us at{" "} - {site.orgs.RS.email} and we’ll point you in the + Visit our homepage or contact us at {site.orgs.RS.email} and we’ll point you in the right direction.{" "}

  • -
  • @@ -52,11 +42,7 @@ export const ErrorNoPage = () => { diff --git a/frontend-react/yarn.lock b/frontend-react/yarn.lock index 262a13d07be..c22b4708f09 100644 --- a/frontend-react/yarn.lock +++ b/frontend-react/yarn.lock @@ -11429,6 +11429,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^6.1.0": + version: 6.1.0 + resolution: "p-limit@npm:6.1.0" + dependencies: + yocto-queue: ^1.1.1 + checksum: 0c98d8fc1006b70fc7423232a47e8d026dc69279b06fe7ff8b4c0cc8023de2b6bb8991b609d93c3dec691a7a362ab0f0157df521d931a01fec192a5e404b9ee5 + languageName: node + linkType: hard + "p-locate@npm:^3.0.0": version: 3.0.0 resolution: "p-locate@npm:3.0.0" @@ -12268,6 +12277,7 @@ __metadata: msw-storybook-addon: beta npm-run-all: ^4.1.5 otpauth: ^9.3.2 + p-limit: ^6.1.0 patch-package: ^8.0.0 postcss: ^8.4.45 prettier: ^3.3.3 @@ -15612,6 +15622,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.1.1": + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: f2e05b767ed3141e6372a80af9caa4715d60969227f38b1a4370d60bffe153c9c5b33a862905609afc9b375ec57cd40999810d20e5e10229a204e8bde7ef255c + languageName: node + linkType: hard + "zwitch@npm:^2.0.0": version: 2.0.2 resolution: "zwitch@npm:2.0.2" diff --git a/gradle.properties b/gradle.properties index a0d1f026d36..aeb4a2f63d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs=-Dfile.encoding=UTF8 -Xmx2048M # Set Kotlin version for all peices of the build environment -systemProp.kotlinVersion=1.9.23 +systemProp.kotlinVersion=1.9.25 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d..a4b76b9530d 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c96..df97d72b8b9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13397..f5feea6d6b1 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4676..9b42019c791 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/prime-router/Dockerfile.build b/prime-router/Dockerfile.build index 923fc6572ef..07610edd1d9 100644 --- a/prime-router/Dockerfile.build +++ b/prime-router/Dockerfile.build @@ -3,7 +3,7 @@ # To build it, you must specify at least the following --build-arg values # with some suggested default shown here # -# --build-arg GRADLE_VERSION=8.8 - The version of Gradle you want to build with +# --build-arg GRADLE_VERSION=8.10.2 - The version of Gradle you want to build with # --build-arg AFCT_VERSION=4.0.5198 - The version of the Azure Functions Core Tools # --build-arg JAVA_VERSION=17 - The version of the JDK (and thus JRE) you want to build against/with # @@ -14,7 +14,7 @@ # a tad bit easier when you are rebuilding the builder image # FROM alpine:3.20 AS downloader -ARG GRADLE_VERSION=8.8 +ARG GRADLE_VERSION=8.10.2 ARG AFCT_VERSION=4.0.5198 RUN apk update && apk add wget --no-cache diff --git a/prime-router/build.gradle.kts b/prime-router/build.gradle.kts index 16afe126e90..65ca5665f3f 100644 --- a/prime-router/build.gradle.kts +++ b/prime-router/build.gradle.kts @@ -35,17 +35,27 @@ apply(from = rootProject.file("buildSrc/shared.gradle.kts")) plugins { val kotlinVersion by System.getProperties() id("reportstream.project-conventions") - id("org.flywaydb.flyway") version "10.17.0" + id("org.flywaydb.flyway") version "10.18.0" id("nu.studer.jooq") version "9.0" id("com.github.johnrengelman.shadow") version "8.1.1" id("com.microsoft.azure.azurefunctions") version "1.16.1" id("com.adarshr.test-logger") version "4.0.0" id("jacoco") id("org.jetbrains.dokka") version "1.8.20" - id("com.avast.gradle.docker-compose") version "0.17.7" + id("com.avast.gradle.docker-compose") version "0.17.8" id("org.jetbrains.kotlin.plugin.serialization") version "$kotlinVersion" id("com.nocwriter.runsql") version ("1.0.3") - id("io.swagger.core.v3.swagger-gradle-plugin") version "2.2.22" + id("io.swagger.core.v3.swagger-gradle-plugin") version "2.2.23" +} + +// retrieve the current commit hash +val commitId by lazy { + val stdout = ByteArrayOutputStream() + exec { + commandLine("git", "rev-parse", "--short", "HEAD") + standardOutput = stdout + } + stdout.toString(StandardCharsets.UTF_8).trim() } group = "gov.cdc.prime.reportstream" @@ -65,7 +75,7 @@ val javaVersion = when (appJvmTarget) { } val ktorVersion = "2.3.12" val kotlinVersion by System.getProperties() -val jacksonVersion = "2.17.1" +val jacksonVersion = "2.17.2" jacoco.toolVersion = "0.8.12" // Local database information, first one wins: @@ -261,6 +271,9 @@ sourceSets.create("testIntegration") { runtimeClasspath += sourceSets["main"].output } +// Add generated version object +sourceSets["main"].java.srcDir("$buildDir/generated-src/version") + val compileTestIntegrationKotlin: KotlinCompile by tasks compileTestIntegrationKotlin.kotlinOptions.jvmTarget = appJvmTarget @@ -340,6 +353,7 @@ tasks.withType().configureEach { } tasks.processResources { + dependsOn("generateVersionObject") // Set the proper build values in the build.properties file filesMatching("build.properties") { val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd") @@ -500,18 +514,32 @@ tasks.azureFunctionsPackage { finalizedBy("copyAzureScripts") } +// TODO: remove after implementation of health check endpoint tasks.register("generateVersionFile") { doLast { - val stdout = ByteArrayOutputStream() - exec { - commandLine("git", "rev-parse", "--short", "HEAD") - standardOutput = stdout - } - val currentCommit = stdout.toString(StandardCharsets.UTF_8).trim() - File(buildDir, "$azureFunctionsDir/$azureAppName/version.json").writeText("{\"commitId\": \"$currentCommit\"}") + file("$buildDir/$azureFunctionsDir/$azureAppName/version.json").writeText("{\"commitId\": \"$commitId\"}") } } +tasks.register("generateVersionObject") { + val sourceDir = file("$buildDir/generated-src/version") + val sourceFile = file("$sourceDir/Version.kt") + sourceDir.mkdirs() + sourceFile.writeText( + """ + package gov.cdc.prime.router.version + + /** + * Supplies information for the current build. + * This file is generated via Gradle task prior to compile time and should always contain the current commit hash. + */ + object Version { + const val commitId = "$commitId" + } + """.trimIndent() + ) +} + val azureResourcesTmpDir = File(buildDir, "$azureFunctionsDir-resources/$azureAppName") val azureResourcesFinalDir = File(buildDir, "$azureFunctionsDir/$azureAppName") tasks.register("gatherAzureResources") { @@ -805,7 +833,7 @@ buildscript { // will need to be removed once this issue is resolved in Maven. classpath("net.minidev:json-smart:2.5.1") // as per flyway v10 docs the postgres flyway module must be on the project buildpath - classpath("org.flywaydb:flyway-database-postgresql:10.17.0") + classpath("org.flywaydb:flyway-database-postgresql:10.18.0") } } @@ -818,37 +846,37 @@ configurations { } dependencies { - jooqGenerator("org.postgresql:postgresql:42.7.3") + jooqGenerator("org.postgresql:postgresql:42.7.4") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") implementation("com.microsoft.azure.functions:azure-functions-java-library:3.1.0") - implementation("com.azure:azure-core:1.51.0") - implementation("com.azure:azure-core-http-netty:1.15.3") + implementation("com.azure:azure-core:1.52.0") + implementation("com.azure:azure-core-http-netty:1.15.4") implementation("com.azure:azure-storage-blob:12.27.0") { exclude(group = "com.azure", module = "azure-core") } implementation("com.azure:azure-storage-queue:12.22.0") { exclude(group = "com.azure", module = "azure-core") } - implementation("com.azure:azure-security-keyvault-secrets:4.8.5") { + implementation("com.azure:azure-security-keyvault-secrets:4.8.6") { exclude(group = "com.azure", module = "azure-core") exclude(group = "com.azure", module = "azure-core-http-netty") } - implementation("com.azure:azure-identity:1.13.2") { + implementation("com.azure:azure-identity:1.13.3") { exclude(group = "com.azure", module = "azure-core") exclude(group = "com.azure", module = "azure-core-http-netty") } - implementation("com.nimbusds:nimbus-jose-jwt:9.40") - implementation("org.apache.logging.log4j:log4j-api:2.23.1") - implementation("org.apache.logging.log4j:log4j-core:2.23.1") - implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1") - implementation("org.apache.logging.log4j:log4j-layout-template-json:2.23.1") - implementation("org.apache.logging.log4j:log4j-api-kotlin:1.4.0") + implementation("com.nimbusds:nimbus-jose-jwt:9.41.1") + implementation("org.apache.logging.log4j:log4j-api:2.24.0") + implementation("org.apache.logging.log4j:log4j-core:2.24.0") + implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.0") + implementation("org.apache.logging.log4j:log4j-layout-template-json:2.24.0") + implementation("org.apache.logging.log4j:log4j-api-kotlin:1.5.0") implementation("io.github.oshai:kotlin-logging-jvm:7.0.0") - implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.9.3") + implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.10.0") implementation("tech.tablesaw:tablesaw-core:0.43.1") implementation("com.github.ajalt.clikt:clikt-jvm:4.4.0") @@ -857,7 +885,7 @@ dependencies { implementation("com.github.javafaker:javafaker:1.0.2") { exclude(group = "org.yaml", module = "snakeyaml") } - implementation("org.yaml:snakeyaml:2.2") + implementation("org.yaml:snakeyaml:2.3") implementation("io.github.linuxforhealth:hl7v2-fhir-converter") { version { branch = "master" @@ -873,7 +901,7 @@ dependencies { implementation("ca.uhn.hapi:hapi-base:2.5.1") implementation("ca.uhn.hapi:hapi-structures-v251:2.5.1") implementation("ca.uhn.hapi:hapi-structures-v27:2.5.1") - implementation("com.googlecode.libphonenumber:libphonenumber:8.13.42") + implementation("com.googlecode.libphonenumber:libphonenumber:8.13.46") implementation("org.thymeleaf:thymeleaf:3.1.2.RELEASE") implementation("com.sendgrid:sendgrid-java:4.10.2") implementation("com.okta.jwt:okta-jwt-verifier:0.5.7") @@ -887,12 +915,12 @@ dependencies { implementation("org.apache.commons:commons-text:1.12.0") implementation("commons-codec:commons-codec:1.17.1") implementation("commons-io:commons-io:2.16.1") - implementation("org.postgresql:postgresql:42.7.3") + implementation("org.postgresql:postgresql:42.7.4") implementation("com.zaxxer:HikariCP:5.1.0") - implementation("org.flywaydb:flyway-core:10.17.0") - implementation("org.flywaydb:flyway-database-postgresql:10.17.0") + implementation("org.flywaydb:flyway-core:10.18.0") + implementation("org.flywaydb:flyway-database-postgresql:10.18.0") implementation("org.commonmark:commonmark:0.22.0") - implementation("com.google.guava:guava:33.2.1-jre") + implementation("com.google.guava:guava:33.3.0-jre") implementation("com.helger.as2:as2-lib:5.1.2") implementation("org.bouncycastle:bcprov-jdk15to18:1.78.1") implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") @@ -915,14 +943,14 @@ dependencies { implementation("it.skrape:skrapeit-http-fetcher:1.3.0-alpha.2") implementation("org.apache.poi:poi:5.3.0") implementation("org.apache.poi:poi-ooxml:5.3.0") - implementation("org.apache.commons:commons-compress:1.26.2") + implementation("org.apache.commons:commons-compress:1.27.1") implementation("commons-io:commons-io:2.16.1") implementation("com.anyascii:anyascii:0.3.2") // force jsoup since skrapeit-html-parser@1.2.1+ has not updated implementation("org.jsoup:jsoup:1.18.1") // https://mvnrepository.com/artifact/io.swagger/swagger-annotations implementation("io.swagger:swagger-annotations:1.6.14") - implementation("io.swagger.core.v3:swagger-jaxrs2:2.2.22") + implementation("io.swagger.core.v3:swagger-jaxrs2:2.2.23") // https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api implementation("javax.ws.rs:javax.ws.rs-api:2.1.1") // https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api @@ -948,7 +976,7 @@ dependencies { implementation("io.konform:konform-jvm:0.4.0") runtimeOnly("com.okta.jwt:okta-jwt-verifier-impl:0.5.7") - runtimeOnly("com.squareup.okio:okio:3.9.0") + runtimeOnly("com.squareup.okio:okio:3.9.1") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5") diff --git a/prime-router/docs/docs-deprecated/chatops-provisioning.md b/prime-router/docs/docs-deprecated/chatops-provisioning.md index be53b2b7e44..2d43ba96f49 100644 --- a/prime-router/docs/docs-deprecated/chatops-provisioning.md +++ b/prime-router/docs/docs-deprecated/chatops-provisioning.md @@ -2,7 +2,7 @@ ## Application -[slack-boltjs-app](https://github.com/JosiahSiegel/slack-boltjs-app) is installed as a submodule under [operations/slack-boltjs-app](../../operations/slack-boltjs-app/). +[slack-boltjs-app](https://github.com/focusconsulting/slack-boltjs-app) is installed as a submodule under [operations/slack-boltjs-app](../../operations/slack-boltjs-app/). To update the submodule, navigate to the subdirectory and `git pull`. diff --git a/prime-router/docs/docs-deprecated/getting-started/install-gradle.md b/prime-router/docs/docs-deprecated/getting-started/install-gradle.md index 955f9e71ced..90fc7b3cf64 100644 --- a/prime-router/docs/docs-deprecated/getting-started/install-gradle.md +++ b/prime-router/docs/docs-deprecated/getting-started/install-gradle.md @@ -20,7 +20,7 @@ If your package manager contains a gradle version >=8.0.0, feel free to install ```bash mkdir -p ${HOME?}/bin/gradle-bins/ cd ${HOME?}/bin/gradle-bins/ -VERSION=8.8 +VERSION=8.10.2 wget https://services.gradle.org/distributions/gradle-${VERSION?}-bin.zip unzip "gradle-${VERSION?}-bin.zip" rm "gradle-${VERSION?}-bin.zip" diff --git a/prime-router/docs/docs-deprecated/playbooks/dependabot-updates.md b/prime-router/docs/docs-deprecated/playbooks/dependabot-updates.md index d470d812472..1c84a92132e 100644 --- a/prime-router/docs/docs-deprecated/playbooks/dependabot-updates.md +++ b/prime-router/docs/docs-deprecated/playbooks/dependabot-updates.md @@ -18,7 +18,7 @@ Please pay attention to [security related dependency PRs](https://github.com/CDC ## Steps 1. Open the dependabot's pull request to be merged and identify if the PR is out of date from master. If so, create a new comment in the PR with the text -`@dependabot rebase` to let dependabot rebase the branch for you. If you use any other method, dependabot will not be able to keep track of the PR. If the PR has conflicts (and no manual commits were added), use `@dpendabot recreate` to recreate the PR from scratch. +`@dependabot rebase` to let dependabot rebase the branch for you. If you use any other method, dependabot will not be able to keep track of the PR. If the PR has conflicts (and no manual commits were added), use `@dependabot recreate` to recreate the PR from scratch. 1. Verify that the build for the PR is successful. Note that the unit, integration and smoke tests are run as part of the build. 1. Read the updated library's changelog and identify and communicate any risks you find. When in doubt ask! Library changes can affect many parts of the system. 1. Identify any library version conflicts for the updated library. This may happen when other libraries are dependent on a different version of the same library. See diff --git a/prime-router/docs/observability/azure-events.md b/prime-router/docs/observability/azure-events.md index 07fcbaaeb6a..d7b6b6a9bbb 100644 --- a/prime-router/docs/observability/azure-events.md +++ b/prime-router/docs/observability/azure-events.md @@ -45,115 +45,6 @@ class MyService( } ``` -Under the hood, it will serialize your event class and push the event -to the configured Microsoft AppInsights instance. - -## Event Glossery - -### ReportCreatedEvent -This event is emitted by the convert step when a report is successfully translated into a FHIR bundle. -- reportId - - The ID assigned to the created report -- topic - - The topic of the created report - - -### ReportAcceptedEvent -This event is emitted by the destination filter step, _before_ any filters are evaluated -- reportId - - The report ID from the preceding function (convert step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- observations - - A list of observations each containing a list of its mapped conditions -- bundleSize - - Length of the bundle JSON string -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - -### ReportNotRoutedEvent -This is event is emitted by the destination filter step if a bundle not routed to any receivers. - -- reportId - - The ID of the empty report that terminated this lineage -- parentReportId - - The report ID from the preceding function (convert step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- bundleSize - - Length of the bundle JSON string -- failingFilters - - A list of all the filters that failed causing this report not the be routed -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - -### ReportRouteEvent -This event is emitted by the receiver filter step, _after_ all filters have passed and a report has been -routed to a receiver. Many `ReportRouteEvent` can correspond to a `ReportAcceptedEvent` and can be "joined" on: - -`ReportAcceptedEvent.reportId == ReportRouteEvent.parentReportId` - -- reportId - - The ID of the report routed to the receiver -- parentReportId - - The report ID from the preceding function (destination filter step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- receiver - - The full receiver name. (deprecated: When a report does not get routed to a receiver this value will be `"null"`) -- observations - - A list of observations each containing a list of its mapped conditions -- (deprecated) originalObservations - - (deprecated) A list of observations in the originally submitted report, before any filters were run -- filteredObservations - - A list of observations that were filtered from the bundle during filtering -- bundleSize - - Length of the bundle JSON string -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - -### ReceiverFilterFailedEvent -This event is emitted by the receiver filter step if a bundle fails a receiver filter. - -- reportId - - The ID of the empty report that terminated this lineage -- parentReportId - - The report ID from the preceding function (destination filter step) -- submittedReportId - - The report ID submitted by the sender -- topic - - The topic of the report -- sender - - The full sender name -- receiver - - The full receiver name. -- observations - - A list of observations each containing a list of its mapped conditions -- failingFilters - - A list of all the filters that failed for this report -- failingFilterType - - The type of filter that failed this report -- bundleSize - - Length of the bundle JSON string -- messageId - - From the bundle.identifier value and system. If ingested as HL7 this comes from MSH-10 - - ## How to query for events Events that are pushed to Azure can be found in the `customEvents` table in the log explorer. The properties defined in @@ -176,24 +67,24 @@ customEvents ### Distinct senders ``` customEvents -| where name == "ReportAcceptedEvent" -| extend sender = tostring(customDimensions.sender) -| distinct sender +| where name == "REPORT_RECEIVED" +| extend senderName = tostring(parse_json(tostring(customDimensions.params)).senderName) +| distinct senderName ``` ### Get report count sent by sender ``` customEvents -| where name == "ReportAcceptedEvent" -| extend sender = tostring(customDimensions.sender) -| summarize count() by sender +| where name == "REPORT_RECEIVED" +| extend senderName = tostring(parse_json(tostring(customDimensions.params)).senderName) +| summarize count() by senderName | order by count_ ``` ### Get report count sent by topic ``` customEvents -| where name == "ReportAcceptedEvent" +| where name == "REPORT_RECEIVED" | extend topic = tostring(customDimensions.topic) | summarize count() by topic | order by count_ @@ -202,12 +93,8 @@ customEvents ### Get reportable conditions count for all reports sent to Report Stream ``` customEvents -| where name == "ReportAcceptedEvent" -| extend observations = parse_json(tostring(customDimensions.observations)) -| mv-expand observations -| extend conditions = parse_json(tostring(observations.conditions)) -| mv-expand conditions -| extend conditionDisplay = tostring(conditions.display) +| where name == "ITEM_ACCEPTED" +| extend conditionDisplay = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(customDimensions.params)).bundleDigest)).observationSummaries))[0].testSummary))[0].conditions))[0].display) | summarize count() by conditionDisplay | order by count_ ``` @@ -215,28 +102,25 @@ customEvents ### Distinct receivers ``` customEvents -| where name == "ReportRouteEvent" -| extend receiver = tostring(customDimensions.receiver) -| where receiver != "null" -| distinct receiver +| where name == "ITEM_ROUTED" +| extend receiverName = tostring(parse_json(tostring(customDimensions.params)).receiverName) +| distinct receiverName ``` ### Get report count routed to a receiver ``` customEvents -| where name == "ReportRouteEvent" -| extend receiver = tostring(customDimensions.receiver) -| where receiver != "null" -| summarize count() by receiver +| where name == "ITEM_ROUTED" +| extend receiverName = tostring(parse_json(tostring(customDimensions.params)).receiverName) +| summarize count() by receiverName | order by count_ ``` ### Get report count routed by topic ``` customEvents -| where name == "ReportRouteEvent" -| extend topic = tostring(customDimensions.topic), receiver = tostring(customDimensions.receiver) -| where receiver != "null" +| where name == "ITEM_ROUTED" +| extend topic = tostring(customDimensions.topic) | summarize count() by topic | order by count_ ``` @@ -244,13 +128,8 @@ customEvents ### Get reportable conditions count for all reports routed to receivers ``` customEvents -| where name == "ReportRouteEvent" -| extend observations = parse_json(tostring(customDimensions.observations)), receiver = tostring(customDimensions.receiver) -| where receiver != "null" -| mv-expand observations -| extend conditions = parse_json(tostring(observations.conditions)) -| mv-expand conditions -| extend conditionDisplay = tostring(conditions.display) +| where name == "ITEM_ROUTED" +| extend conditionDisplay = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(customDimensions.params)).bundleDigest)).observationSummaries))[0].testSummary))[0].conditions))[0].display) | summarize count() by conditionDisplay | order by count_ ``` diff --git a/prime-router/docs/universal-pipeline/destination-filter.md b/prime-router/docs/universal-pipeline/destination-filter.md index fd9162c4d38..21fbe27e878 100644 --- a/prime-router/docs/universal-pipeline/destination-filter.md +++ b/prime-router/docs/universal-pipeline/destination-filter.md @@ -186,10 +186,10 @@ This filter will log messages to the console when: This step emits one of two events below _once_ each time it runs. -| Event | Trigger | -|-------------------------------------------------------------------------------|------------------------------------------------------------------| -| [ReportAcceptedEvent](../observability/azure-events.md#reportacceptedevent) | when a report is received by this step (precludes all filtering) | -| [ReportNotRoutedEvent](../observability/azure-events.md#reportnotroutedevent) | when a report is not valid for any receivers | +| Event | Trigger | +|-----------------|------------------------------------------------------------------| +| ITEM_ROUTED | when a report is received by this step (precludes all filtering) | +| ITEM_NOT_ROUTED | when a report is not valid for any receivers | ## Retries diff --git a/prime-router/docs/universal-pipeline/receiver-filter.md b/prime-router/docs/universal-pipeline/receiver-filter.md index 3c0de1e403d..e98957961f0 100644 --- a/prime-router/docs/universal-pipeline/receiver-filter.md +++ b/prime-router/docs/universal-pipeline/receiver-filter.md @@ -294,8 +294,7 @@ This step emits one of the below events _per receiver_ each time it runs. | Event | Trigger | |-----------------------------------------------------------------------------------------|------------------------------------------------------------| -| [ReportRouteEvent](../observability/azure-events.md#reportrouteevent) | When a report is routed to a receiver (all filters passed) | -| [ReceiverFilterFailedEvent](../observability/azure-events.md#receiverfilterfailedevent) | When a report fails receiver filters | +| ITEM_FILTER_FAILED | When a report fails receiver filters | ## Retries diff --git a/prime-router/gradle.properties b/prime-router/gradle.properties index 99176a42c51..2e777795522 100644 --- a/prime-router/gradle.properties +++ b/prime-router/gradle.properties @@ -1,3 +1,3 @@ # This setting makes flyway fall back to session locks as concurrent index creation cannot be done # within a transaction. This setting is needed as of flyway 9.19.4. -flyway.postgresql.transactional.lock=false +flyway.postgresql.transactional.lock=false \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/ActionHistory.kt b/prime-router/src/main/kotlin/azure/ActionHistory.kt index de7a0f0a053..53c139c788b 100644 --- a/prime-router/src/main/kotlin/azure/ActionHistory.kt +++ b/prime-router/src/main/kotlin/azure/ActionHistory.kt @@ -6,6 +6,8 @@ import com.microsoft.azure.functions.HttpRequestMessage import com.microsoft.azure.functions.HttpResponseMessage import com.microsoft.azure.functions.HttpStatusType import com.networknt.org.apache.commons.validator.routines.InetAddressValidator +import fhirengine.engine.CustomFhirPathFunctions +import gov.cdc.prime.reportstream.shared.BlobUtils import gov.cdc.prime.router.ActionLog import gov.cdc.prime.router.ActionLogLevel import gov.cdc.prime.router.ClientSource @@ -25,11 +27,16 @@ import gov.cdc.prime.router.azure.db.tables.pojos.ItemLineage import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile import gov.cdc.prime.router.azure.db.tables.pojos.ReportLineage import gov.cdc.prime.router.azure.db.tables.pojos.Task +import gov.cdc.prime.router.azure.observability.bundleDigest.BundleDigestExtractor +import gov.cdc.prime.router.azure.observability.bundleDigest.FhirPathBundleDigestLabResultExtractorStrategy import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.azure.observability.event.ReportStreamEventName import gov.cdc.prime.router.azure.observability.event.ReportStreamEventProperties import gov.cdc.prime.router.common.AzureHttpUtils.getSenderIP import gov.cdc.prime.router.common.JacksonMapperUtilities +import gov.cdc.prime.router.fhirengine.translation.hl7.utils.CustomContext +import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder +import gov.cdc.prime.router.report.ReportService import io.ktor.http.HttpStatusCode import org.apache.logging.log4j.kotlin.Logging import org.jooq.impl.SQLDataType @@ -565,6 +572,7 @@ class ActionHistory( result: String, header: WorkflowEngine.Header, reportEventService: IReportStreamEventService, + reportService: ReportService, transportType: String, ) { if (isReportAlreadyTracked(sentReportId)) { @@ -616,6 +624,45 @@ class ActionHistory( ) } + val lineages = Report.createItemLineagesFromDb(header, sentReportId) + lineages?.forEach { itemLineage -> + val receiverFilterReportFile = reportService.getReportForItemAtTask( + itemLineage.parentReportId, + itemLineage.parentIndex, + TaskAction.receiver_filter + ) + if (receiverFilterReportFile != null) { + val blob = BlobAccess.downloadBlob( + receiverFilterReportFile.bodyUrl, + BlobUtils.digestToString(receiverFilterReportFile.blobDigest) + ) + val bundle = FhirTranscoder.decode(blob) + val bundleDigestExtractor = BundleDigestExtractor( + FhirPathBundleDigestLabResultExtractorStrategy( + CustomContext( + bundle, + bundle, + mutableMapOf(), + CustomFhirPathFunctions() + ) + ) + ) + reportEventService.sendItemEvent(ReportStreamEventName.ITEM_SENT, reportFile, TaskAction.send) { + trackingId(bundle) + parentReportId(header.reportFile.reportId) + childItemIndex(itemLineage.childIndex) + params( + mapOf( + ReportStreamEventProperties.BUNDLE_DIGEST + to bundleDigestExtractor.generateDigest(bundle), + ReportStreamEventProperties.RECEIVER_NAME to receiver.fullName, + ) + ) + } + } else { + logger.error("No translate report found for sent item.") + } + } reportsOut[reportFile.reportId] = reportFile } diff --git a/prime-router/src/main/kotlin/azure/SendFunction.kt b/prime-router/src/main/kotlin/azure/SendFunction.kt index dc77ddbe883..e258e641c8e 100644 --- a/prime-router/src/main/kotlin/azure/SendFunction.kt +++ b/prime-router/src/main/kotlin/azure/SendFunction.kt @@ -117,7 +117,8 @@ class SendFunction( retryItems, context, actionHistory, - reportEventService + reportEventService, + workflowEngine.reportService ) if (nextRetry != null) { nextRetryItems += nextRetry diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt deleted file mode 100644 index 6f915d64bdf..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReceiverFilterFailedEvent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.ReportStreamFilterType -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report fails a receiver's filters - */ - -data class ReceiverFilterFailedEvent( - val reportId: ReportId, - val parentReportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val receiver: String, - val observations: List, - val failingFilters: List, - val failingFilterType: ReportStreamFilterType, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt deleted file mode 100644 index 0eed89d94aa..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportAcceptedEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report is ready to be processed per receiver - * - * This event should contain all observations sent by the sender since no - * receiver specific filters have been run - */ -data class ReportAcceptedEvent( - val reportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val observations: List, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt deleted file mode 100644 index f6453dad064..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportCreatedEvent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * An event emitted during every report created - */ -data class ReportCreatedEvent( - val reportId: ReportId, - val topic: Topic, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt deleted file mode 100644 index 0280e12d912..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportNotRoutedEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report does not get routed to any receivers - */ - -data class ReportNotRoutedEvent( - val reportId: ReportId, - val parentReportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt deleted file mode 100644 index 4479c753fa7..00000000000 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportRouteEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package gov.cdc.prime.router.azure.observability.event - -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.Topic - -/** - * Event definition for when a report gets routed to a receiver - */ -data class ReportRouteEvent( - val reportId: ReportId, - val parentReportId: ReportId, - val submittedReportId: ReportId, - val topic: Topic, - val sender: String, - val receiver: String?, // TODO: this should not be nullable anymore after #14450 - val observations: List, - val filteredObservations: List, - val bundleSize: Int, - val messageId: AzureEventUtils.MessageID, -) : AzureCustomEvent \ No newline at end of file diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventData.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventData.kt index b527e8a1466..99556776b23 100644 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventData.kt +++ b/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventData.kt @@ -27,6 +27,7 @@ data class ReportEventData( val blobUrl: String, val pipelineStepName: TaskAction, val timestamp: OffsetDateTime, + val commitId: String, ) /** @@ -87,6 +88,7 @@ enum class ReportStreamEventName { ITEM_ROUTED, REPORT_LAST_MILE_FAILURE, REPORT_NOT_PROCESSABLE, + ITEM_SENT, } /** diff --git a/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventService.kt b/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventService.kt index d8c37ac8ddb..42509ce0748 100644 --- a/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventService.kt +++ b/prime-router/src/main/kotlin/azure/observability/event/ReportStreamEventService.kt @@ -7,6 +7,7 @@ import gov.cdc.prime.router.azure.DatabaseAccess import gov.cdc.prime.router.azure.db.enums.TaskAction import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile import gov.cdc.prime.router.report.ReportService +import gov.cdc.prime.router.version.Version import java.time.OffsetDateTime import java.util.UUID @@ -412,7 +413,8 @@ class ReportStreamEventService( topic, childBodyUrl, pipelineStepName, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ) } diff --git a/prime-router/src/main/kotlin/history/db/ReportGraph.kt b/prime-router/src/main/kotlin/history/db/ReportGraph.kt index b9c425a07f9..103b9831eb3 100644 --- a/prime-router/src/main/kotlin/history/db/ReportGraph.kt +++ b/prime-router/src/main/kotlin/history/db/ReportGraph.kt @@ -19,7 +19,7 @@ import org.jooq.CommonTableExpression import org.jooq.DSLContext import org.jooq.Record import org.jooq.Record1 -import org.jooq.Record2 +import org.jooq.SelectConditionStep import org.jooq.SelectOnConditionStep import org.jooq.impl.CustomRecord import org.jooq.impl.CustomTable @@ -213,6 +213,25 @@ class ReportGraph( return descendantReportRecords(txn, cte, searchedForTaskActions).fetchInto(ReportFile::class.java) } + /** + * Retrieves ancestor report from a [TaskAction] for a particular item. + * + * @param txn the transaction to run the DB access under + * @param childReportId the reportId to search for ancestors of + * @param childIndex the index of the child + * @param searchedForTaskAction the task action associated with the desired ancestor report + * @return The ancestor report for that particular action + */ + fun getAncestorReport( + txn: DataAccessTransaction, + childReportId: UUID, + childIndex: Int, + searchedForTaskAction: TaskAction, + ): ReportFile? { + val cte = itemAncestorGraphCommonTableExpression(childReportId, childIndex) + return ancestorReportRecords(txn, cte, searchedForTaskAction).fetchOneInto(ReportFile::class.java) + } + /** * Returns all the metadata rows associated with the passed in [ItemGraphRecord] * @@ -421,19 +440,15 @@ class ReportGraph( */ fun reportAncestorGraphCommonTableExpression(childReportIds: List) = DSL.name(lineageCteName).fields( - PARENT_REPORT_ID_FIELD, - PATH_FIELD + PARENT_REPORT_ID_FIELD ).`as`( DSL.select( - REPORT_LINEAGE.PARENT_REPORT_ID, - REPORT_LINEAGE.CHILD_REPORT_ID.cast(SQLDataType.VARCHAR), + REPORT_LINEAGE.PARENT_REPORT_ID ).from(REPORT_LINEAGE) .where(REPORT_LINEAGE.CHILD_REPORT_ID.`in`(childReportIds)) .unionAll( DSL.select( - REPORT_LINEAGE.PARENT_REPORT_ID, - DSL.field("$lineageCteName.$PATH_FIELD", SQLDataType.VARCHAR) - .concat(REPORT_LINEAGE.PARENT_REPORT_ID) + REPORT_LINEAGE.PARENT_REPORT_ID ) .from(REPORT_LINEAGE) .join(DSL.table(DSL.name(lineageCteName))) @@ -454,7 +469,7 @@ class ReportGraph( */ private fun rootReportRecords( txn: DataAccessTransaction, - cte: CommonTableExpression>, + cte: CommonTableExpression>, ) = DSL.using(txn) .withRecursive(cte) .select(REPORT_FILE.asterisk()) @@ -520,4 +535,30 @@ class ReportGraph( return select } + + /** + * Fetches all ancestor report records in a recursive manner. + * + * @param txn the data access transaction + * @param cte the common table expression for report lineage + * @return the descendant report records + */ + private fun ancestorReportRecords( + txn: DataAccessTransaction, + cte: CommonTableExpression, + searchedForTaskAction: TaskAction, + ): SelectConditionStep { + val select = DSL.using(txn) + .withRecursive(cte) + .select(REPORT_FILE.asterisk()) + .distinctOn(REPORT_FILE.REPORT_ID) + .from(cte) + .join(REPORT_FILE) + .on(REPORT_FILE.REPORT_ID.eq(ItemGraphTable.ITEM_GRAPH.PARENT_REPORT_ID)) + .join(ACTION) + .on(ACTION.ACTION_ID.eq(REPORT_FILE.ACTION_ID)) + .where(ACTION.ACTION_NAME.eq(searchedForTaskAction)) + + return select + } } \ No newline at end of file diff --git a/prime-router/src/main/kotlin/report/ReportService.kt b/prime-router/src/main/kotlin/report/ReportService.kt index 7fb5481b8ef..1997c5a63f7 100644 --- a/prime-router/src/main/kotlin/report/ReportService.kt +++ b/prime-router/src/main/kotlin/report/ReportService.kt @@ -2,6 +2,7 @@ package gov.cdc.prime.router.report import gov.cdc.prime.router.ReportId import gov.cdc.prime.router.azure.DatabaseAccess +import gov.cdc.prime.router.azure.db.enums.TaskAction import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile import gov.cdc.prime.router.common.BaseEngine import gov.cdc.prime.router.history.db.ReportGraph @@ -47,7 +48,23 @@ class ReportService( * @return List of ReportFile objects of the root reports */ fun getRootReports(childReportId: ReportId): List { - return reportGraph.getRootReports(childReportId) + return reportGraph.getRootReports(childReportId).distinctBy { it.reportId } + } + + /** + * Accepts a descendant item (report id and index) and finds the ancestor report associated with the + * passed [TaskAction] + * + * @param childReportId the descendant child report + * @param childIndex the index of the item + * @param task the particular task to find the ancestor report for + * + * @return the [ReportFile] ancestor at the passed [TaskAction] + */ + fun getReportForItemAtTask(childReportId: ReportId, childIndex: Int, task: TaskAction): ReportFile? { + return db.transactReturning { txn -> + reportGraph.getAncestorReport(txn, childReportId, childIndex, task) + } } /** diff --git a/prime-router/src/main/kotlin/transport/AS2Transport.kt b/prime-router/src/main/kotlin/transport/AS2Transport.kt index 6817e065307..b79e2f2d444 100644 --- a/prime-router/src/main/kotlin/transport/AS2Transport.kt +++ b/prime-router/src/main/kotlin/transport/AS2Transport.kt @@ -22,6 +22,7 @@ import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.credentials.CredentialHelper import gov.cdc.prime.router.credentials.CredentialRequestReason import gov.cdc.prime.router.credentials.UserJksCredential +import gov.cdc.prime.router.report.ReportService import org.apache.hc.core5.util.Timeout import org.apache.http.conn.ConnectTimeoutException import org.apache.logging.log4j.kotlin.Logging @@ -50,6 +51,7 @@ class AS2Transport(val metadata: Metadata? = null) : ITransport, Logging { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { // DevNote: This code is similar to the SFTP code in structure // @@ -78,6 +80,7 @@ class AS2Transport(val metadata: Metadata? = null) : ITransport, Logging { msg, header, reportEventService, + reportService, this::class.java.simpleName ) actionHistory.trackItemLineages(Report.createItemLineagesFromDb(header, sentReportId)) diff --git a/prime-router/src/main/kotlin/transport/BlobStoreTransport.kt b/prime-router/src/main/kotlin/transport/BlobStoreTransport.kt index 0c1a47ddcce..ea372653d59 100644 --- a/prime-router/src/main/kotlin/transport/BlobStoreTransport.kt +++ b/prime-router/src/main/kotlin/transport/BlobStoreTransport.kt @@ -10,6 +10,7 @@ import gov.cdc.prime.router.azure.BlobAccess import gov.cdc.prime.router.azure.WorkflowEngine import gov.cdc.prime.router.azure.db.enums.TaskAction import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService +import gov.cdc.prime.router.report.ReportService class BlobStoreTransport : ITransport { override fun send( @@ -21,6 +22,7 @@ class BlobStoreTransport : ITransport { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { val blobTransportType = transportType as BlobStoreTransportType val envVar: String = blobTransportType.containerName @@ -41,6 +43,7 @@ class BlobStoreTransport : ITransport { msg, header, reportEventService, + reportService, this::class.java.simpleName ) actionHistory.trackItemLineages(Report.createItemLineagesFromDb(header, sentReportId)) diff --git a/prime-router/src/main/kotlin/transport/EmailTransport.kt b/prime-router/src/main/kotlin/transport/EmailTransport.kt index c4e152fe49f..a7e75f090ee 100644 --- a/prime-router/src/main/kotlin/transport/EmailTransport.kt +++ b/prime-router/src/main/kotlin/transport/EmailTransport.kt @@ -14,6 +14,7 @@ import gov.cdc.prime.router.TransportType import gov.cdc.prime.router.azure.ActionHistory import gov.cdc.prime.router.azure.WorkflowEngine import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService +import gov.cdc.prime.router.report.ReportService import org.thymeleaf.TemplateEngine import org.thymeleaf.context.Context import org.thymeleaf.templateresolver.StringTemplateResolver @@ -33,6 +34,7 @@ class EmailTransport : ITransport { context: ExecutionContext, actionHistory: ActionHistory, // not used by emailer reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { val emailTransport = transportType as EmailTransportType val content = buildContent(header) diff --git a/prime-router/src/main/kotlin/transport/GAENTransport.kt b/prime-router/src/main/kotlin/transport/GAENTransport.kt index 1a81e40312e..a5abfbdd829 100644 --- a/prime-router/src/main/kotlin/transport/GAENTransport.kt +++ b/prime-router/src/main/kotlin/transport/GAENTransport.kt @@ -20,6 +20,7 @@ import gov.cdc.prime.router.common.HttpClientUtils import gov.cdc.prime.router.credentials.CredentialHelper import gov.cdc.prime.router.credentials.CredentialRequestReason import gov.cdc.prime.router.credentials.UserApiKeyCredential +import gov.cdc.prime.router.report.ReportService import io.ktor.client.HttpClient import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode @@ -78,6 +79,7 @@ class GAENTransport(val httpClient: HttpClient? = null) : ITransport, Logging { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { val gaenTransportInfo = transportType as GAENTransportType val reportId = header.reportFile.reportId @@ -106,7 +108,7 @@ class GAENTransport(val httpClient: HttpClient? = null) : ITransport, Logging { // Record the work in history and logs when (postResult) { - PostResult.SUCCESS -> recordFullSuccess(params, reportEventService) + PostResult.SUCCESS -> recordFullSuccess(params, reportEventService, reportService) PostResult.RETRY -> recordFailureWithRetry(params) PostResult.FAIL -> recordFailure(params) } @@ -123,7 +125,11 @@ class GAENTransport(val httpClient: HttpClient? = null) : ITransport, Logging { /** * Record in [ActionHistory] the full success of this notification. Log an info message as well. */ - private fun recordFullSuccess(params: SendParams, reportEventService: IReportStreamEventService) { + private fun recordFullSuccess( + params: SendParams, + reportEventService: IReportStreamEventService, + reportService: ReportService, + ) { val msg = "${params.receiver.fullName}: Successful exposure notifications of ${params.comboId}" val history = params.actionHistory params.context.logger.info(msg) @@ -137,6 +143,7 @@ class GAENTransport(val httpClient: HttpClient? = null) : ITransport, Logging { msg, params.header, reportEventService, + reportService, this::class.java.simpleName ) history.trackItemLineages(Report.createItemLineagesFromDb(params.header, params.sentReportId)) diff --git a/prime-router/src/main/kotlin/transport/ITransport.kt b/prime-router/src/main/kotlin/transport/ITransport.kt index 3f05de5fd74..169ec7191f1 100644 --- a/prime-router/src/main/kotlin/transport/ITransport.kt +++ b/prime-router/src/main/kotlin/transport/ITransport.kt @@ -6,6 +6,7 @@ import gov.cdc.prime.router.TransportType import gov.cdc.prime.router.azure.ActionHistory import gov.cdc.prime.router.azure.WorkflowEngine import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService +import gov.cdc.prime.router.report.ReportService interface ITransport { /** @@ -26,5 +27,6 @@ interface ITransport { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? } \ No newline at end of file diff --git a/prime-router/src/main/kotlin/transport/NullTransport.kt b/prime-router/src/main/kotlin/transport/NullTransport.kt index 9c3f90f867b..2bb329c9acc 100644 --- a/prime-router/src/main/kotlin/transport/NullTransport.kt +++ b/prime-router/src/main/kotlin/transport/NullTransport.kt @@ -7,6 +7,7 @@ import gov.cdc.prime.router.TransportType import gov.cdc.prime.router.azure.ActionHistory import gov.cdc.prime.router.azure.WorkflowEngine import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService +import gov.cdc.prime.router.report.ReportService /** * The Null transport is intended for testing and benchmarking purposes. @@ -21,6 +22,7 @@ class NullTransport : ITransport { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { if (header.content == null) error("No content for report ${header.reportFile.reportId}") val receiver = header.receiver ?: error("No receiver defined for report ${header.reportFile.reportId}") @@ -34,6 +36,7 @@ class NullTransport : ITransport { msg, header, reportEventService, + reportService, this::class.java.simpleName ) actionHistory.trackItemLineages(Report.createItemLineagesFromDb(header, sentReportId)) diff --git a/prime-router/src/main/kotlin/transport/RESTTransport.kt b/prime-router/src/main/kotlin/transport/RESTTransport.kt index 6b0758c9ccb..c134ad04e0b 100644 --- a/prime-router/src/main/kotlin/transport/RESTTransport.kt +++ b/prime-router/src/main/kotlin/transport/RESTTransport.kt @@ -20,6 +20,7 @@ import gov.cdc.prime.router.credentials.UserApiKeyCredential import gov.cdc.prime.router.credentials.UserAssertionCredential import gov.cdc.prime.router.credentials.UserJksCredential import gov.cdc.prime.router.credentials.UserPassCredential +import gov.cdc.prime.router.report.ReportService import gov.cdc.prime.router.tokens.AuthUtils import io.ktor.client.HttpClient import io.ktor.client.call.body @@ -93,6 +94,7 @@ class RESTTransport(private val httpClient: HttpClient? = null) : ITransport { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { val logger: Logger = context.logger @@ -157,6 +159,7 @@ class RESTTransport(private val httpClient: HttpClient? = null) : ITransport { msg, header, reportEventService, + reportService, this::class.java.simpleName ) actionHistory.trackItemLineages(Report.createItemLineagesFromDb(header, sentReportId)) diff --git a/prime-router/src/main/kotlin/transport/SftpTransport.kt b/prime-router/src/main/kotlin/transport/SftpTransport.kt index 9acfb9ea322..266960e2dd9 100644 --- a/prime-router/src/main/kotlin/transport/SftpTransport.kt +++ b/prime-router/src/main/kotlin/transport/SftpTransport.kt @@ -19,6 +19,7 @@ import gov.cdc.prime.router.credentials.SftpCredential import gov.cdc.prime.router.credentials.UserPassCredential import gov.cdc.prime.router.credentials.UserPemCredential import gov.cdc.prime.router.credentials.UserPpkCredential +import gov.cdc.prime.router.report.ReportService import net.schmizz.sshj.DefaultConfig import net.schmizz.sshj.SSHClient import net.schmizz.sshj.sftp.RemoteResourceFilter @@ -48,6 +49,7 @@ class SftpTransport : ITransport, Logging { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { val sftpTransportType = transportType as SFTPTransportType @@ -72,6 +74,7 @@ class SftpTransport : ITransport, Logging { msg, header, reportEventService, + reportService, this::class.java.simpleName ) actionHistory.trackItemLineages(Report.createItemLineagesFromDb(header, sentReportId)) diff --git a/prime-router/src/main/kotlin/transport/SoapTransport.kt b/prime-router/src/main/kotlin/transport/SoapTransport.kt index aba6e6113d3..ec771d556e5 100644 --- a/prime-router/src/main/kotlin/transport/SoapTransport.kt +++ b/prime-router/src/main/kotlin/transport/SoapTransport.kt @@ -15,6 +15,7 @@ import gov.cdc.prime.router.credentials.CredentialHelper import gov.cdc.prime.router.credentials.CredentialRequestReason import gov.cdc.prime.router.credentials.SoapCredential import gov.cdc.prime.router.credentials.UserJksCredential +import gov.cdc.prime.router.report.ReportService import gov.cdc.prime.router.serializers.SoapEnvelope import gov.cdc.prime.router.serializers.SoapObjectService import io.ktor.client.HttpClient @@ -154,6 +155,7 @@ class SoapTransport(private val httpClient: HttpClient? = null) : ITransport { context: ExecutionContext, actionHistory: ActionHistory, reportEventService: IReportStreamEventService, + reportService: ReportService, ): RetryItems? { // verify that we have a SOAP transport type for our parameters. I think if we ever fell // into this scenario with different parameters there's something seriously wrong in the system, @@ -211,6 +213,7 @@ class SoapTransport(private val httpClient: HttpClient? = null) : ITransport { msg, header, reportEventService, + reportService, this::class.java.simpleName ) actionHistory.trackItemLineages(Report.createItemLineagesFromDb(header, sentReportId)) diff --git a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MT/MT-receiver-transform.yml b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MT/MT-receiver-transform.yml index 1fb4e963ae5..dc2b9295545 100644 --- a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MT/MT-receiver-transform.yml +++ b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MT/MT-receiver-transform.yml @@ -16,3 +16,8 @@ elements: condition: '%resource.code.exists()' value: [ '%resource.system.getCodingSystemMapping()' ] hl7Spec: [ '/PATIENT_RESULT/PATIENT/PID-10-3' ] + + - name: mt-patient-county-codes + condition: 'Bundle.entry.resource.ofType(Patient).address.district.empty().not() and Bundle.entry.resource.ofType(Patient).address.state.empty().not()' + hl7Spec: [ '/PATIENT_RESULT/PATIENT/PID-11-9' ] + value: [ "FIPSCountyLookup(Bundle.entry.resource.ofType(Patient).address.district,Bundle.entry.resource.ofType(Patient).address.state)[0]" ] diff --git a/prime-router/src/test/kotlin/azure/ActionHistoryTests.kt b/prime-router/src/test/kotlin/azure/ActionHistoryTests.kt index 1d23f1c0356..c6b6c5389a6 100644 --- a/prime-router/src/test/kotlin/azure/ActionHistoryTests.kt +++ b/prime-router/src/test/kotlin/azure/ActionHistoryTests.kt @@ -23,18 +23,27 @@ import gov.cdc.prime.router.Report import gov.cdc.prime.router.Schema import gov.cdc.prime.router.Topic import gov.cdc.prime.router.azure.db.enums.TaskAction +import gov.cdc.prime.router.azure.db.tables.pojos.ItemLineage import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile +import gov.cdc.prime.router.azure.observability.bundleDigest.BundleDigest +import gov.cdc.prime.router.azure.observability.bundleDigest.BundleDigestExtractor import gov.cdc.prime.router.azure.observability.event.AzureEventService import gov.cdc.prime.router.azure.observability.event.ReportEventData import gov.cdc.prime.router.azure.observability.event.ReportStreamEventService import gov.cdc.prime.router.common.JacksonMapperUtilities +import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder +import gov.cdc.prime.router.report.ReportService import gov.cdc.prime.router.unittest.UnitTestUtils import io.mockk.every import io.mockk.mockk import io.mockk.mockkClass +import io.mockk.mockkConstructor import io.mockk.mockkObject import io.mockk.spyk +import io.mockk.unmockkAll import io.mockk.verify +import org.hl7.fhir.r4.model.Bundle +import org.junit.jupiter.api.AfterEach import java.time.OffsetDateTime import java.util.UUID import kotlin.test.Test @@ -43,6 +52,12 @@ import kotlin.test.assertNotEquals import kotlin.test.assertNotNull class ActionHistoryTests { + + @AfterEach + fun afterEach() { + unmockkAll() + } + @Test fun `test trackActionReceiverInfo`() { val actionHistory = ActionHistory(TaskAction.translate) @@ -334,9 +349,14 @@ class ActionHistoryTests { "http://blobUrl", "".toByteArray() ) + every { BlobAccess.downloadBlob(any(), any()) } returns "" val mockAzureEventService = mockk() every { mockAzureEventService.trackEvent(any()) } returns Unit val mockReportEventService = mockk() + val mockReportService = mockk() + every { + mockReportService.getReportForItemAtTask(any(), any(), any()) + } returns mockk(relaxed = true) every { mockReportEventService.getReportEventData( any(), @@ -352,17 +372,37 @@ class ActionHistoryTests { Topic.TEST, "http://blobUrl", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + "" ) every { mockReportEventService.sendReportEvent(any(), any(), any(), any()) } returns Unit + every { mockReportEventService.sendItemEvent(any(), any(), any(), any()) } returns Unit + mockkObject(Report) + mockkObject(FhirTranscoder) + every { FhirTranscoder.decode(any(), any()) } returns mockk() + mockkConstructor(BundleDigestExtractor::class) + every { anyConstructed().generateDigest(any()) } returns mockk() val header = mockk() val inReportFile = mockk() every { header.reportFile } returns inReportFile every { header.content } returns "".toByteArray() every { inReportFile.itemCount } returns 15 every { inReportFile.reportId } returns uuid + every { Report.createItemLineagesFromDb(any(), any()) } returns listOf( + ItemLineage( + 1, + header.reportFile.reportId, + 1, + uuid, + 1, + "", + "", + OffsetDateTime.now(), + "" + ) + ) val orgReceiver = org.receivers[0] val actionHistory1 = ActionHistory(TaskAction.receive) actionHistory1.action @@ -374,6 +414,7 @@ class ActionHistoryTests { "result1", header, mockReportEventService, + mockReportService, "" ) assertThat(actionHistory1.reportsOut[uuid]).isNotNull() @@ -393,6 +434,7 @@ class ActionHistoryTests { assertThat(actionHistory1.action.externalName).isEqualTo("filename1") verify(exactly = 1) { mockReportEventService.sendReportEvent(any(), any(), any(), any()) + mockReportEventService.sendItemEvent(any(), any(), any(), any()) } // not allowed to track the same report twice. assertFailure { @@ -404,6 +446,7 @@ class ActionHistoryTests { "result1", header, mockReportEventService, + mockReportService, "" ) } @@ -437,6 +480,10 @@ class ActionHistoryTests { val mockAzureEventService = mockk() every { mockAzureEventService.trackEvent(any()) } returns Unit val mockReportEventService = mockk() + val mockReportService = mockk() + every { + mockReportService.getReportForItemAtTask(any(), any(), any()) + } returns mockk(relaxed = true) every { mockReportEventService.getReportEventData( any(), @@ -452,7 +499,8 @@ class ActionHistoryTests { Topic.TEST, "http://blobUrl", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + "" ) mockkObject(BlobAccess.Companion) mockkObject(BlobUtils) @@ -460,15 +508,35 @@ class ActionHistoryTests { every { BlobAccess.uploadBlob(capture(blobUrls), any()) } returns "http://blobUrl" every { BlobUtils.sha256Digest(any()) } returns byteArrayOf() every { BlobAccess.uploadBody(any(), any(), any(), any(), Event.EventAction.NONE) } answers { callOriginal() } + every { BlobAccess.downloadBlob(any(), any()) } returns "" + mockkObject(Report) + mockkObject(FhirTranscoder) + every { FhirTranscoder.decode(any(), any()) } returns mockk() + mockkConstructor(BundleDigestExtractor::class) + every { anyConstructed().generateDigest(any()) } returns mockk() val header = mockk() every { mockReportEventService.sendReportEvent(any(), any(), any(), any()) } returns Unit + every { mockReportEventService.sendItemEvent(any(), any(), any(), any()) } returns Unit val inReportFile = mockk() every { header.reportFile } returns inReportFile every { header.content } returns "".toByteArray() every { inReportFile.itemCount } returns 15 every { inReportFile.reportId } returns uuid + every { Report.createItemLineagesFromDb(any(), any()) } returns listOf( + ItemLineage( + 1, + header.reportFile.reportId, + 1, + uuid, + 1, + "", + "", + OffsetDateTime.now(), + "" + ) + ) val actionHistory1 = ActionHistory(TaskAction.receive) actionHistory1.trackSentReport( @@ -479,6 +547,7 @@ class ActionHistoryTests { "result1", header, mockReportEventService, + mockReportService, "" ) assertThat(actionHistory1.reportsOut[uuid]).isNotNull() @@ -495,6 +564,7 @@ class ActionHistoryTests { "result1", header, mockReportEventService, + mockReportService, "" ) assertThat(actionHistory2.reportsOut[uuid]).isNotNull() @@ -502,6 +572,7 @@ class ActionHistoryTests { .isEqualTo("STED/NESTED/STLTs/REALLY_LONG_STATE_NAME/REALLY_LONG_STATE_NAME") verify(exactly = 2) { mockReportEventService.sendReportEvent(any(), any(), any(), any()) + mockReportEventService.sendItemEvent(any(), any(), any(), any()) } } @@ -677,6 +748,10 @@ class ActionHistoryTests { val mockAzureEventService = mockk() every { mockAzureEventService.trackEvent(any()) } returns Unit val mockReportEventService = mockk() + val mockReportService = mockk() + every { + mockReportService.getReportForItemAtTask(any(), any(), any()) + } returns mockk(relaxed = true) every { mockReportEventService.getReportEventData( any(), @@ -692,23 +767,44 @@ class ActionHistoryTests { Topic.TEST, "http://blobUrl", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + "" ) mockkObject(BlobAccess.Companion) mockkObject(BlobUtils) + mockkObject(Report) val blobUrls = mutableListOf() every { BlobAccess.uploadBlob(capture(blobUrls), any()) } returns "http://blobUrl" every { BlobUtils.sha256Digest(any()) } returns byteArrayOf() every { BlobAccess.uploadBody(any(), any(), any(), any(), Event.EventAction.NONE) } answers { callOriginal() } + every { BlobAccess.downloadBlob(any(), any()) } returns "" + mockkObject(FhirTranscoder) + every { FhirTranscoder.decode(any(), any()) } returns mockk() + mockkConstructor(BundleDigestExtractor::class) + every { anyConstructed().generateDigest(any()) } returns mockk() val header = mockk() every { mockReportEventService.sendReportEvent(any(), any(), any(), any()) } returns Unit + every { mockReportEventService.sendItemEvent(any(), any(), any(), any()) } returns Unit val inReportFile = mockk() every { header.reportFile } returns inReportFile every { header.content } returns "".toByteArray() every { inReportFile.itemCount } returns 15 every { inReportFile.reportId } returns uuid + every { Report.createItemLineagesFromDb(any(), any()) } returns listOf( + ItemLineage( + 1, + header.reportFile.reportId, + 1, + uuid, + 1, + "", + "", + OffsetDateTime.now(), + "" + ) + ) val actionHistory1 = ActionHistory(TaskAction.receive) actionHistory1.action actionHistory1.trackSentReport( @@ -719,6 +815,7 @@ class ActionHistoryTests { "result1", header, mockReportEventService, + mockReportService, "" ) assertThat(actionHistory1.reportsOut[uuid]).isNotNull() @@ -732,6 +829,7 @@ class ActionHistoryTests { "result1", header, mockReportEventService, + mockReportService, "" ) assertThat(actionHistory2.reportsOut[uuid2]).isNotNull() @@ -740,6 +838,7 @@ class ActionHistoryTests { assertContains(blobUrls[1], org.receivers[1].fullName) verify(exactly = 2) { mockReportEventService.sendReportEvent(any(), any(), any(), any()) + mockReportEventService.sendItemEvent(any(), any(), any(), any()) } } diff --git a/prime-router/src/test/kotlin/azure/SendFunctionTests.kt b/prime-router/src/test/kotlin/azure/SendFunctionTests.kt index e94e5d0ae87..9c6695494aa 100644 --- a/prime-router/src/test/kotlin/azure/SendFunctionTests.kt +++ b/prime-router/src/test/kotlin/azure/SendFunctionTests.kt @@ -116,7 +116,7 @@ class SendFunctionTests { val header = makeHeader() nextEvent = block(header, null, null) } - every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any()) }.returns(null) + every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any(), any(),) }.returns(null) every { workflowEngine.recordAction(any()) }.returns(Unit) every { workflowEngine.azureEventService.trackEvent(any()) }.returns(Unit) every { workflowEngine.reportService.getRootReports(any()) } returns reportList @@ -148,7 +148,7 @@ class SendFunctionTests { nextEvent = block(header, null, null) } setupWorkflow() - every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any()) } + every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any(), any(),) } .returns(RetryToken.allItems) every { workflowEngine.recordAction(any()) }.returns(Unit) every { workflowEngine.db } returns mockk() @@ -181,7 +181,7 @@ class SendFunctionTests { ) } setupWorkflow() - every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any()) } + every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any(), any(),) } .returns(RetryToken.allItems) every { workflowEngine.recordAction(any()) }.returns(Unit) every { workflowEngine.db } returns mockk() @@ -218,7 +218,7 @@ class SendFunctionTests { ) } setupWorkflow() - every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any()) } + every { sftpTransport.send(any(), any(), any(), any(), any(), any(), any(), any(), any(),) } .returns(RetryToken.allItems) every { workflowEngine.recordAction(any()) }.returns(Unit) every { workflowEngine.db } returns mockk(relaxed = true) diff --git a/prime-router/src/test/kotlin/azure/observability/context/MDCUtilsTest.kt b/prime-router/src/test/kotlin/azure/observability/context/MDCUtilsTest.kt index 5c8cc249993..3d51e2ffac2 100644 --- a/prime-router/src/test/kotlin/azure/observability/context/MDCUtilsTest.kt +++ b/prime-router/src/test/kotlin/azure/observability/context/MDCUtilsTest.kt @@ -94,7 +94,8 @@ class MDCUtilsTest { Topic.FULL_ELR, "", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + "" ), mapOf( ReportStreamEventProperties.FILENAME to "filename" @@ -121,7 +122,8 @@ class MDCUtilsTest { Topic.FULL_ELR, "", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + "" ), ItemEventData( 1, diff --git a/prime-router/src/test/kotlin/azure/observability/event/ReportEventServiceTest.kt b/prime-router/src/test/kotlin/azure/observability/event/ReportEventServiceTest.kt index a625b6c6f55..526c409cc1b 100644 --- a/prime-router/src/test/kotlin/azure/observability/event/ReportEventServiceTest.kt +++ b/prime-router/src/test/kotlin/azure/observability/event/ReportEventServiceTest.kt @@ -12,6 +12,7 @@ import gov.cdc.prime.router.db.ReportStreamTestDatabaseContainer import gov.cdc.prime.router.db.ReportStreamTestDatabaseSetupExtension import gov.cdc.prime.router.history.db.ReportGraph import gov.cdc.prime.router.report.ReportService +import gov.cdc.prime.router.version.Version import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.testcontainers.junit.jupiter.Testcontainers @@ -93,7 +94,8 @@ class ReportEventServiceTest { Topic.FULL_ELR, "", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -136,7 +138,8 @@ class ReportEventServiceTest { Topic.FULL_ELR, "", TaskAction.send, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt index a7e9795e1cf..69b6dbe7182 100644 --- a/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt +++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt @@ -69,6 +69,7 @@ import gov.cdc.prime.router.history.DetailedActionLog import gov.cdc.prime.router.metadata.LookupTable import gov.cdc.prime.router.metadata.ObservationMappingConstants import gov.cdc.prime.router.unittest.UnitTestUtils +import gov.cdc.prime.router.version.Version import io.mockk.every import io.mockk.mockkConstructor import io.mockk.mockkObject @@ -357,7 +358,8 @@ class FHIRConverterIntegrationTests { Topic.FULL_ELR, routedReports[1].bodyUrl, TaskAction.convert, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -529,7 +531,8 @@ class FHIRConverterIntegrationTests { Topic.FULL_ELR, routedReports[1].bodyUrl, TaskAction.convert, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -664,7 +667,8 @@ class FHIRConverterIntegrationTests { Topic.MARS_OTC_ELR, "", TaskAction.convert, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt index 0d7aa55ebed..76d52f0b353 100644 --- a/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt +++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt @@ -47,6 +47,7 @@ import gov.cdc.prime.router.history.db.ReportGraph import gov.cdc.prime.router.metadata.LookupTable import gov.cdc.prime.router.report.ReportService import gov.cdc.prime.router.unittest.UnitTestUtils +import gov.cdc.prime.router.version.Version import io.mockk.every import io.mockk.mockkConstructor import io.mockk.mockkObject @@ -349,7 +350,8 @@ class FHIRDestinationFilterIntegrationTests : Logging { Topic.FULL_ELR, routedReport.bodyUrl, TaskAction.destination_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -450,7 +452,8 @@ class FHIRDestinationFilterIntegrationTests : Logging { Topic.FULL_ELR, "", TaskAction.destination_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ReportEventData::childReportId diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt index 7580e6d67bb..6092e53516f 100644 --- a/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt +++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt @@ -56,6 +56,7 @@ import gov.cdc.prime.router.metadata.LookupTable import gov.cdc.prime.router.metadata.ObservationMappingConstants import gov.cdc.prime.router.report.ReportService import gov.cdc.prime.router.unittest.UnitTestUtils +import gov.cdc.prime.router.version.Version import io.mockk.every import io.mockk.mockkConstructor import io.mockk.mockkObject @@ -388,7 +389,8 @@ class FHIRReceiverFilterIntegrationTests : Logging { Topic.FULL_ELR, "", TaskAction.receiver_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ) @@ -562,7 +564,8 @@ class FHIRReceiverFilterIntegrationTests : Logging { Topic.FULL_ELR, "", TaskAction.receiver_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ) @@ -749,7 +752,8 @@ class FHIRReceiverFilterIntegrationTests : Logging { Topic.FULL_ELR, "", TaskAction.receiver_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ) @@ -880,7 +884,8 @@ class FHIRReceiverFilterIntegrationTests : Logging { Topic.FULL_ELR, "", TaskAction.receiver_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ) @@ -1163,7 +1168,8 @@ class FHIRReceiverFilterIntegrationTests : Logging { Topic.FULL_ELR, "", TaskAction.receiver_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ) diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverIntegrationTests.kt index 763a1f03a7b..df8c56b8e7b 100644 --- a/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverIntegrationTests.kt +++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverIntegrationTests.kt @@ -42,6 +42,7 @@ import gov.cdc.prime.router.fhirengine.engine.FHIRReceiver import gov.cdc.prime.router.history.DetailedActionLog import gov.cdc.prime.router.history.DetailedReport import gov.cdc.prime.router.unittest.UnitTestUtils +import gov.cdc.prime.router.version.Version import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockkObject @@ -228,7 +229,8 @@ class FHIRReceiverIntegrationTests { Topic.FULL_ELR, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -254,7 +256,8 @@ class FHIRReceiverIntegrationTests { Topic.FULL_ELR, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -339,7 +342,8 @@ class FHIRReceiverIntegrationTests { null, submissionBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -441,7 +445,8 @@ class FHIRReceiverIntegrationTests { Topic.FULL_ELR, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -544,7 +549,8 @@ class FHIRReceiverIntegrationTests { Topic.FULL_ELR, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -649,7 +655,8 @@ class FHIRReceiverIntegrationTests { Topic.FULL_ELR, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -753,7 +760,8 @@ class FHIRReceiverIntegrationTests { Topic.FULL_ELR, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) @@ -854,7 +862,8 @@ class FHIRReceiverIntegrationTests { null, receiveBlobUrl, TaskAction.receive, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) diff --git a/prime-router/src/test/kotlin/fhirengine/engine/FhirDestinationFilterTests.kt b/prime-router/src/test/kotlin/fhirengine/engine/FhirDestinationFilterTests.kt index cac3f90e718..d6546d3eb8f 100644 --- a/prime-router/src/test/kotlin/fhirengine/engine/FhirDestinationFilterTests.kt +++ b/prime-router/src/test/kotlin/fhirengine/engine/FhirDestinationFilterTests.kt @@ -39,6 +39,7 @@ import gov.cdc.prime.router.azure.observability.event.TestSummary import gov.cdc.prime.router.metadata.LookupTable import gov.cdc.prime.router.report.ReportService import gov.cdc.prime.router.unittest.UnitTestUtils +import gov.cdc.prime.router.version.Version import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk @@ -338,7 +339,8 @@ class FhirDestinationFilterTests { Topic.FULL_ELR, "test", TaskAction.destination_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp, ) @@ -502,7 +504,8 @@ class FhirDestinationFilterTests { Topic.FULL_ELR, "", TaskAction.destination_filter, - OffsetDateTime.now() + OffsetDateTime.now(), + Version.commitId ), ReportEventData::timestamp ) diff --git a/prime-router/src/testIntegration/kotlin/transport/AS2TransportIntegrationTests.kt b/prime-router/src/testIntegration/kotlin/transport/AS2TransportIntegrationTests.kt index 2587c0399da..eb8032f034b 100644 --- a/prime-router/src/testIntegration/kotlin/transport/AS2TransportIntegrationTests.kt +++ b/prime-router/src/testIntegration/kotlin/transport/AS2TransportIntegrationTests.kt @@ -15,6 +15,7 @@ import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile import gov.cdc.prime.router.azure.db.tables.pojos.Task import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.credentials.UserJksCredential +import gov.cdc.prime.router.report.ReportService import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk @@ -124,7 +125,8 @@ class AS2TransportIntegrationTests { null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() @@ -150,7 +152,8 @@ class AS2TransportIntegrationTests { null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isSameInstanceAs(RetryToken.allItems) diff --git a/prime-router/src/testIntegration/kotlin/transport/GAENTransportIntegrationTests.kt b/prime-router/src/testIntegration/kotlin/transport/GAENTransportIntegrationTests.kt index 60896850940..c7d7682a6b4 100644 --- a/prime-router/src/testIntegration/kotlin/transport/GAENTransportIntegrationTests.kt +++ b/prime-router/src/testIntegration/kotlin/transport/GAENTransportIntegrationTests.kt @@ -16,6 +16,7 @@ import gov.cdc.prime.router.azure.db.enums.TaskAction import gov.cdc.prime.router.azure.db.tables.pojos.Task import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.credentials.UserApiKeyCredential +import gov.cdc.prime.router.report.ReportService import io.ktor.http.HttpStatusCode import io.mockk.every import io.mockk.mockk @@ -143,7 +144,8 @@ class GAENTransportIntegrationTests : TransportIntegrationTests() { retryItems = null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() @@ -175,7 +177,8 @@ class GAENTransportIntegrationTests : TransportIntegrationTests() { retryItems = null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(RetryToken.isAllItems(retryItems)).isTrue() @@ -207,7 +210,8 @@ class GAENTransportIntegrationTests : TransportIntegrationTests() { retryItems = null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() @@ -240,7 +244,8 @@ class GAENTransportIntegrationTests : TransportIntegrationTests() { retryItems = null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() diff --git a/prime-router/src/testIntegration/kotlin/transport/RESTTransportIntegrationTests.kt b/prime-router/src/testIntegration/kotlin/transport/RESTTransportIntegrationTests.kt index a78b3729a53..918356e5d9d 100644 --- a/prime-router/src/testIntegration/kotlin/transport/RESTTransportIntegrationTests.kt +++ b/prime-router/src/testIntegration/kotlin/transport/RESTTransportIntegrationTests.kt @@ -18,6 +18,7 @@ import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.credentials.UserApiKeyCredential import gov.cdc.prime.router.credentials.UserAssertionCredential import gov.cdc.prime.router.credentials.UserPassCredential +import gov.cdc.prime.router.report.ReportService import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.security.Keys @@ -329,7 +330,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -355,7 +357,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -379,7 +382,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -403,7 +407,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -427,7 +432,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNotNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -451,7 +457,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -475,7 +482,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -499,7 +507,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNotNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -523,7 +532,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNotNull() assertThat(actionHistory.action.httpStatus).isNotNull() @@ -544,7 +554,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNotNull() } @@ -568,7 +579,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH transport.parameters empty val retryItems = mockRestTransport.send( transportType, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) // Then: @@ -601,7 +613,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH flexionRestTransportType which has transport.parameters val retryItems = mockRestTransport.send( flexionRestTransportType, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) // Then: @@ -628,7 +641,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== val retryItems = mockRestTransport.send( flexionRestTransportType, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() } @@ -646,7 +660,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== val retryItems = mockRestTransport.send( flexionRestTransportTypeWithJwtParams, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() } @@ -690,7 +705,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH transport.parameters empty val retryItems = mockRestTransport.send( nbsRestTransportTypeLive, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) // Then: @@ -716,7 +732,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== ) val retryItems = mockRestTransport.send( nbsRestTransportTypeLive, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() } @@ -752,7 +769,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH transport.parameters empty val retryItems = mockRestTransport.send( natusRestTransportTypeLive, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) // Then: @@ -801,7 +819,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH transport.parameters empty val retryItems = mockRestTransport.send( natusRestTransportTypeLiveEncrypt, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() @@ -836,7 +855,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // Then: @@ -869,7 +889,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== ) val retryItems = mockRestTransport.send( natusRestTransportTypeLive, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() } @@ -922,7 +943,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== val retryItems = mockRestTransport.send( okRestTransportTypeLive, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNull() } @@ -969,7 +991,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH transport.parameters empty val retryItems = mockRestTransport.send( epicRestTransportTypeLive, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) // Then: @@ -1014,7 +1037,8 @@ hnm8COa8Kr+bnTqzScpQuOfujHcFEtfcYUGfSS6HusxidwXx+lYi1A== // RESTTransport is called WITH flexionRestTransportType which has transport.parameters val retryItems = mockRestTransport.send( oracleRlNRestTransport, header, reportId, "test", null, - context, actionHistory, mockk(relaxed = true) + context, actionHistory, mockk(relaxed = true), + mockk(relaxed = true) ) // Then: diff --git a/prime-router/src/testIntegration/kotlin/transport/SftpTransportIntegrationTests.kt b/prime-router/src/testIntegration/kotlin/transport/SftpTransportIntegrationTests.kt index 542fe512e1c..92cfbb6b48d 100644 --- a/prime-router/src/testIntegration/kotlin/transport/SftpTransportIntegrationTests.kt +++ b/prime-router/src/testIntegration/kotlin/transport/SftpTransportIntegrationTests.kt @@ -175,7 +175,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // successful SFTP upload @@ -216,7 +217,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // successful SFTP upload @@ -257,7 +259,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // successful SFTP upload @@ -281,7 +284,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // asserts that the initial null check works @@ -312,7 +316,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // asserts that missing credentials will fail SFTP @@ -351,7 +356,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // asserts that authentication error will result in error @@ -383,7 +389,8 @@ class SftpTransportIntegrationTests : TransportIntegrationTests() { null, context, f.actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) // asserts that invalid credential types will result in error diff --git a/prime-router/src/testIntegration/kotlin/transport/SoapTransportIntegrationTests.kt b/prime-router/src/testIntegration/kotlin/transport/SoapTransportIntegrationTests.kt index a947cb66ae3..36bbf5221ed 100644 --- a/prime-router/src/testIntegration/kotlin/transport/SoapTransportIntegrationTests.kt +++ b/prime-router/src/testIntegration/kotlin/transport/SoapTransportIntegrationTests.kt @@ -14,6 +14,7 @@ import gov.cdc.prime.router.azure.db.enums.TaskAction import gov.cdc.prime.router.azure.db.tables.pojos.Task import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.credentials.UserPassCredential +import gov.cdc.prime.router.report.ReportService import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond @@ -121,7 +122,7 @@ class SoapTransportIntegrationTests : TransportIntegrationTests() { ) val retryItems = mockSoapTransport.send( transportType, header, reportId, "test", null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), mockk(relaxed = true) ) assertThat(retryItems).isNull() } @@ -141,7 +142,8 @@ class SoapTransportIntegrationTests : TransportIntegrationTests() { null, context, actionHistory, - mockk(relaxed = true) + mockk(relaxed = true), + mockk(relaxed = true) ) assertThat(retryItems).isNotNull() } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index c166e033acd..d372ead6770 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -12,10 +12,10 @@ repositories { } dependencies { - implementation("org.apache.commons:commons-lang3:3.15.0") + implementation("org.apache.commons:commons-lang3:3.17.0") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.28.1") - testImplementation("org.apache.commons:commons-compress:1.26.2") + testImplementation("org.apache.commons:commons-compress:1.27.1") } tasks.test { diff --git a/submissions/build.gradle.kts b/submissions/build.gradle.kts index 4f3f4c9294e..8c96e222de5 100644 --- a/submissions/build.gradle.kts +++ b/submissions/build.gradle.kts @@ -1,10 +1,10 @@ apply(from = rootProject.file("buildSrc/shared.gradle.kts")) plugins { - id("org.springframework.boot") version "3.3.2" + id("org.springframework.boot") version "3.3.4" id("io.spring.dependency-management") version "1.1.6" id("reportstream.project-conventions") - kotlin("plugin.spring") version "2.0.0" + kotlin("plugin.spring") version "2.0.20" } group = "gov.cdc.prime" @@ -29,9 +29,10 @@ dependencies { testImplementation("org.xmlunit:xmlunit-core:2.10.0") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") - testImplementation("org.apache.commons:commons-compress:1.26.2") + testImplementation("org.apache.commons:commons-compress:1.27.1") + testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.8.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.9.0") implementation(project(":shared")) } diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt index 61c348c0aa6..b82c0993a31 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionReceivedEvent.kt @@ -8,9 +8,17 @@ data class SubmissionReceivedEvent( val reportId: UUID, val parentReportId: UUID, val rootReportId: UUID, - val headers: Map, - val sender: String, - val senderIP: String, - val fileSize: String, + val requestParameters: SubmissionDetails, + val method: String, + val url: String, + val senderName: String, + val senderIp: String, + val fileLength: String, val blobUrl: String, + val pipelineStepName: String, +) + +data class SubmissionDetails( + val headers: Map, + val queryParameters: Map>, ) \ No newline at end of file diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt new file mode 100644 index 00000000000..d3dab4cdccf --- /dev/null +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AllowedParametersConfig.kt @@ -0,0 +1,41 @@ +package gov.cdc.prime.reportstream.submissions.config + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component + +/** + * Configuration class to load allowed headers and query parameters from the application properties. + * + * This class is used to read properties prefixed with "allowed" from the `application.properties` or `application.yml` + * file and bind them to the `headers` and `queryParameters` fields. + * + * Example of properties in the `application.properties` file: + * + * ``` + * allowed.headers.client_id=client_id + * allowed.headers.content_type=content-type + * allowed.queryParameters.processing=processing + * allowed.queryParameters.another_param=another + * ``` + * + * These properties will be automatically injected into the `headers` and `queryParameters` lists when the + * Spring application context is initialized. + * + * @property headers A list of allowed HTTP header names that are expected in incoming requests. + * @property queryParameters A list of allowed query parameter names that can be used in incoming requests. + */ +@Component +@ConfigurationProperties(prefix = "allowed") +class AllowedParametersConfig { + /** + * A list of allowed HTTP headers that can be accepted by the API. + * Each entry in the list represents a header name expected in the incoming request. + */ + var headers: List = emptyList() + + /** + * A list of allowed query parameters that the API can accept in incoming requests. + * Each entry in the list represents a query parameter name expected in the incoming request. + */ + var queryParameters: List = emptyList() +} \ No newline at end of file diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt index c92e98354eb..cec852bc193 100644 --- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt +++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt @@ -8,8 +8,11 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import gov.cdc.prime.reportstream.shared.BlobUtils import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.Submission +import gov.cdc.prime.reportstream.submissions.SubmissionDetails import gov.cdc.prime.reportstream.submissions.SubmissionReceivedEvent import gov.cdc.prime.reportstream.submissions.TelemetryService +import gov.cdc.prime.reportstream.submissions.config.AllowedParametersConfig +import jakarta.servlet.http.HttpServletRequest import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -42,6 +45,7 @@ class SubmissionController( private val queueClient: QueueClient, private val tableClient: TableClient, private val telemetryService: TelemetryService, + private val allowedParametersConfig: AllowedParametersConfig, ) { /** * Submits a report. @@ -54,6 +58,7 @@ class SubmissionController( * @param contentType the content type of the report (must be "application/hl7-v2" or "application/fhir+ndjson") * @param clientId the ID of the client submitting the report. Should represent org.senderName * @param data the report data + * @param request gives access to request details * @return a ResponseEntity containing the reportID, status, and timestamp */ @PostMapping("/api/v1/reports", consumes = ["application/hl7-v2", "application/fhir+ndjson"]) @@ -65,12 +70,19 @@ class SubmissionController( @RequestHeader("x-azure-clientip") senderIp: String, @RequestHeader(value = "payloadName", required = false) payloadName: String?, @RequestBody data: String, + request: HttpServletRequest, ): ResponseEntity<*> { val reportId = UUID.randomUUID() val reportReceivedTime = Instant.now() val contentTypeMime = contentType.substringBefore(';') val status = "Received" val objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule()) + // Filter request headers based on the allowed list + val filteredHeaders = filterHeaders(headers) + + // Filter query parameters based on the allowed list (only keep 'processing' or others defined in application.properties) + val filteredQueryParameters = filterQueryParameters(request) + logger.info( "Received report submission: reportId=$reportId, contentType=$contentTypeMime" + ", clientId=$clientId${payloadName?.let { ", payloadName=$it" } ?: ""}}" @@ -98,11 +110,17 @@ class SubmissionController( reportId = reportId, parentReportId = reportId, rootReportId = reportId, - headers = filterHeaders(headers), - sender = clientId, - senderIP = senderIp, - fileSize = contentLength, - blobUrl = blobClient.blobUrl + requestParameters = SubmissionDetails( + filteredHeaders, + filteredQueryParameters + ), + method = request.method, + url = request.requestURL.toString(), + senderName = clientId, + senderIp = senderIp, + fileLength = contentLength, + blobUrl = blobClient.blobUrl, + pipelineStepName = "submission" ) logger.debug("Created SUBMISSION_RECEIVED") @@ -121,7 +139,7 @@ class SubmissionController( BlobUtils.digestToString(digest), clientId.lowercase(), reportId, - filterHeaders(headers).toMap(), + filterHeaders(headers), ).serialize() logger.debug("Created message for queue") @@ -231,10 +249,38 @@ class SubmissionController( } } + /** + * Filters the request headers based on the allowed headers configured in the application.yml. + * Handles the case where allowed headers are defined as a list. + */ private fun filterHeaders(headers: Map): Map { - val headersToInclude = - listOf("client_id", "content-type", "payloadname", "x-azure-clientip", "content-length") - return headers.filter { it.key.lowercase() in headersToInclude } + val allowedHeaders = allowedParametersConfig.headers + + // Filter the request headers to only include allowed headers + return headers.filterKeys { key -> + allowedHeaders.map { it.lowercase() }.contains(key.lowercase()) + } + } + + /** + * Filters the query parameters based on the allowed query parameters configured in the application.yml. + * Handles multiple values for the same query parameter from HttpServletRequest. + */ + private fun filterQueryParameters(request: HttpServletRequest): Map> { + val allowedQueryParams = allowedParametersConfig.queryParameters + + // Create a map to hold the filtered query parameters + val filteredParams = mutableMapOf>() + + // Loop over allowed parameters and get their values from the request + allowedQueryParams.forEach { paramName -> + val values = request.getParameterValues(paramName) + if (values != null) { + filteredParams[paramName] = values.toList() // Convert array to List + } + } + + return filteredParams } private fun formBlobName( diff --git a/submissions/src/main/resources/application.properties b/submissions/src/main/resources/application.properties deleted file mode 100644 index a8750014f5d..00000000000 --- a/submissions/src/main/resources/application.properties +++ /dev/null @@ -1,7 +0,0 @@ -spring.application.name=submissions -server.port=8880 -azure.storage.connection-string=${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;} -azure.storage.container-name=${AZURE_STORAGE_CONTAINER_NAME:reports} -azure.storage.queue-name=${AZURE_STORAGE_QUEUE_NAME:elr-fhir-receive} -azure.storage.table-name=${AZURE_STORAGE_TABLE_NAME:submission} -spring.security.oauth2.resourceserver.jwt.issuer-uri=https://reportstream.oktapreview.com/oauth2/ausekaai7gUuUtHda1d7 \ No newline at end of file diff --git a/submissions/src/main/resources/application.yml b/submissions/src/main/resources/application.yml new file mode 100644 index 00000000000..c75d070b3fa --- /dev/null +++ b/submissions/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + application: + name: submissions + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://reportstream.oktapreview.com/oauth2/ausekaai7gUuUtHda1d7 + server: + port: 8880 + +azure: + storage: + connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;} + container-name: ${AZURE_STORAGE_CONTAINER_NAME:reports} + queue-name: ${AZURE_STORAGE_QUEUE_NAME:elr-fhir-receive} + table-name: ${AZURE_STORAGE_TABLE_NAME:submission} + +allowed: + headers: + - client_id + - content-type + - payloadname + - x-azure-clientip + - content-length +# - queryParameters: +# - param \ No newline at end of file diff --git a/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt b/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt index 50255bb05e6..dbfcd9934dd 100644 --- a/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt +++ b/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt @@ -10,6 +10,8 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.QueueMessage.ObjectMapperProvider +import gov.cdc.prime.reportstream.submissions.config.AzureConfig +import gov.cdc.prime.reportstream.submissions.config.SecurityConfig import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -17,7 +19,9 @@ import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.Import import org.springframework.http.MediaType +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource @@ -31,6 +35,7 @@ import java.util.Base64 @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc +@Import(AzureConfig::class, SecurityConfig::class) class SubmissionControllerIntegrationTest { @Autowired @@ -92,6 +97,7 @@ class SubmissionControllerIntegrationTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("client_id", "testClient") diff --git a/submissions/src/test/kotlin/SubmissionControllerTest.kt b/submissions/src/test/kotlin/SubmissionControllerTest.kt index ab1dfd84120..2c552872834 100644 --- a/submissions/src/test/kotlin/SubmissionControllerTest.kt +++ b/submissions/src/test/kotlin/SubmissionControllerTest.kt @@ -11,7 +11,9 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import gov.cdc.prime.reportstream.shared.QueueMessage import gov.cdc.prime.reportstream.shared.QueueMessage.ObjectMapperProvider import gov.cdc.prime.reportstream.submissions.TelemetryService +import gov.cdc.prime.reportstream.submissions.config.AllowedParametersConfig import gov.cdc.prime.reportstream.submissions.config.AzureConfig +import gov.cdc.prime.reportstream.submissions.config.SecurityConfig import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -33,6 +35,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.context.annotation.Import import org.springframework.http.MediaType +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.result.MockMvcResultMatchers @@ -42,7 +45,7 @@ import java.util.Base64 import java.util.UUID @WebMvcTest(SubmissionController::class) -@Import(AzureConfig::class) +@Import(AzureConfig::class, SecurityConfig::class, AllowedParametersConfig::class) class SubmissionControllerTest { @Autowired @@ -126,6 +129,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("client_id", "testClient") @@ -172,6 +176,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/fhir+ndjson")) .header("client_id", "testClient") @@ -203,6 +208,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .header("client_id", "testClient") @@ -219,6 +225,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("payloadname", "testPayload") @@ -241,6 +248,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.parseMediaType("application/hl7-v2")) .header("client_id", "testClient") @@ -263,6 +271,7 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.parseMediaType("application/hl7-v2")) .header("client_id", "testClient") @@ -301,11 +310,14 @@ class SubmissionControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/reports") + .with(csrf()) .content(requestBody) .contentType(MediaType.valueOf("application/hl7-v2")) .header("client_id", "testClient") .header("payloadname", "testPayload") .header("x-azure-clientip", "127.0.0.1") + .queryParam("processing", "test1", "test2") + .queryParam("test", "test2") ) .andExpect(MockMvcResultMatchers.status().isCreated) @@ -321,12 +333,19 @@ class SubmissionControllerTest { val eventDetails = objectMapper.readValue(capturedProperties["event"], Map::class.java) assert(eventDetails["reportId"] == reportId.toString()) assert(eventDetails["blobUrl"] == expectedBlobUrl) - assert(eventDetails["senderIP"] == "127.0.0.1") - val headers = eventDetails["headers"] as Map<*, *> + assert(eventDetails["senderIp"] == "127.0.0.1") + assert(eventDetails["method"] == "POST") + assert(eventDetails["senderName"] == "testClient") + assert(eventDetails["pipelineStepName"] == "submission") + assert(eventDetails["url"] == "http://localhost/api/v1/reports") + val requestParameters = eventDetails["requestParameters"] as Map<*, *> + val headers = requestParameters["headers"] as Map<*, *> assert(headers["client_id"] == "testClient") assert(headers["Content-Type"] == "application/hl7-v2;charset=UTF-8") assert(headers["payloadname"] == "testPayload") assert(headers["x-azure-clientip"] == "127.0.0.1") + val queryParameters = requestParameters["queryParameters"] as Map<*, *> + assert(queryParameters.isEmpty()) uuidMockedStatic.close() }