diff --git a/.github/workflows/cd-deploy-with-multitenancy-header.yml b/.github/workflows/cd-deploy-with-multitenancy-header.yml new file mode 100644 index 00000000..9f1e1491 --- /dev/null +++ b/.github/workflows/cd-deploy-with-multitenancy-header.yml @@ -0,0 +1,143 @@ +name: Deploy CD with Multitenancy-header + +on: + workflow_call: + secrets: + INNER_SOURCE_ACTIONS_APP_ID: + required: true + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: + required: true + GH_PAT_TOKEN: + required: true + HSDP_DOCKER_USER: + required: true + HSDP_DOCKER_PASSWORD: + required: true + CODESCENE_CI_CD_GITHUB_TOKEN: + required: true + ARM_CLIENT_ID: + required: true + ARM_CLIENT_SECRET: + required: true + +env: + TF_IN_AUTOMATION: true + ARM_SUBSCRIPTION_ID: 74d03f75-4bdb-4666-8095-500178a40764 + ARM_TENANT_ID: 1a407a2d-7675-4d17-8692-b3ac285306e4 +jobs: + terragrunt-workflow-core: + name: Terragrunt - core + runs-on: ubuntu-20.04 + container: alpine/terragrunt:1.0.1 + outputs: + core: ${{ steps.tf_output.outputs.core }} + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + defaults: + run: + working-directory: ./deploy/CD/core + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-plan + + - id: output + uses: ./.github/workflows/terragrunt-output-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-output + + terragrunt-workflow-cloudfoundry: + name: Terragrunt - cloudfoundry + runs-on: ubuntu-20.04 + needs: [terragrunt-workflow-core] + container: alpine/terragrunt:1.0.1 + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + defaults: + run: + working-directory: ./deploy/CD/cloudfoundry + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/cloudfoundry + key: client-test-cloudfoundry-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/cloudfoundry + key: client-test-cloudfoundry-plan + + terragrunt-workflow-api-gateway: + name: Terragrunt - api-gateway + runs-on: ubuntu-20.04 + needs: [terragrunt-workflow-cloudfoundry] + container: alpine/terragrunt:1.0.1 + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + CONFIG: ./envoyconfig_with_multitenancy.yml + SOURCE: header + defaults: + run: + working-directory: ./deploy/CD/api-gateway + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-plan + + - id: output + uses: ./.github/workflows/terragrunt-output-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-output \ No newline at end of file diff --git a/.github/workflows/cd-deploy-with-multitenancy-url.yml b/.github/workflows/cd-deploy-with-multitenancy-url.yml new file mode 100644 index 00000000..14351fe4 --- /dev/null +++ b/.github/workflows/cd-deploy-with-multitenancy-url.yml @@ -0,0 +1,143 @@ +name: Deploy CD with Multitenancy-Url + +on: + workflow_call: + secrets: + INNER_SOURCE_ACTIONS_APP_ID: + required: true + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: + required: true + GH_PAT_TOKEN: + required: true + HSDP_DOCKER_USER: + required: true + HSDP_DOCKER_PASSWORD: + required: true + CODESCENE_CI_CD_GITHUB_TOKEN: + required: true + ARM_CLIENT_ID: + required: true + ARM_CLIENT_SECRET: + required: true + +env: + TF_IN_AUTOMATION: true + ARM_SUBSCRIPTION_ID: 74d03f75-4bdb-4666-8095-500178a40764 + ARM_TENANT_ID: 1a407a2d-7675-4d17-8692-b3ac285306e4 +jobs: + terragrunt-workflow-core: + name: Terragrunt - core + runs-on: ubuntu-20.04 + container: alpine/terragrunt:1.0.1 + outputs: + core: ${{ steps.tf_output.outputs.core }} + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + defaults: + run: + working-directory: ./deploy/CD/core + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-plan + + - id: output + uses: ./.github/workflows/terragrunt-output-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-output + + terragrunt-workflow-cloudfoundry: + name: Terragrunt - cloudfoundry + runs-on: ubuntu-20.04 + needs: [terragrunt-workflow-core] + container: alpine/terragrunt:1.0.1 + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + defaults: + run: + working-directory: ./deploy/CD/cloudfoundry + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/cloudfoundry + key: client-test-cloudfoundry-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/cloudfoundry + key: client-test-cloudfoundry-plan + + terragrunt-workflow-api-gateway: + name: Terragrunt - api-gateway + runs-on: ubuntu-20.04 + needs: [terragrunt-workflow-cloudfoundry] + container: alpine/terragrunt:1.0.1 + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + CONFIG: ./envoyconfig_with_multitenancy.yml + SOURCE: url + defaults: + run: + working-directory: ./deploy/CD/api-gateway + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-plan + + - id: output + uses: ./.github/workflows/terragrunt-output-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-output \ No newline at end of file diff --git a/.github/workflows/cd-deploy-without-multitenancy.yml b/.github/workflows/cd-deploy-without-multitenancy.yml new file mode 100644 index 00000000..98d71d39 --- /dev/null +++ b/.github/workflows/cd-deploy-without-multitenancy.yml @@ -0,0 +1,143 @@ +name: Deploy CD without Multitenancy + +on: + workflow_call: + secrets: + INNER_SOURCE_ACTIONS_APP_ID: + required: true + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: + required: true + GH_PAT_TOKEN: + required: true + HSDP_DOCKER_USER: + required: true + HSDP_DOCKER_PASSWORD: + required: true + CODESCENE_CI_CD_GITHUB_TOKEN: + required: true + ARM_CLIENT_ID: + required: true + ARM_CLIENT_SECRET: + required: true + +env: + TF_IN_AUTOMATION: true + ARM_SUBSCRIPTION_ID: 74d03f75-4bdb-4666-8095-500178a40764 + ARM_TENANT_ID: 1a407a2d-7675-4d17-8692-b3ac285306e4 +jobs: + terragrunt-workflow-core: + name: Terragrunt - core + runs-on: ubuntu-20.04 + container: alpine/terragrunt:1.0.1 + outputs: + core: ${{ steps.tf_output.outputs.core }} + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + defaults: + run: + working-directory: ./deploy/CD/core + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-plan + + - id: output + uses: ./.github/workflows/terragrunt-output-action + with: + working-directory: ./deploy/CD/core + key: client-test-core-output + + terragrunt-workflow-cloudfoundry: + name: Terragrunt - cloudfoundry + runs-on: ubuntu-20.04 + needs: [terragrunt-workflow-core] + container: alpine/terragrunt:1.0.1 + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + defaults: + run: + working-directory: ./deploy/CD/cloudfoundry + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/cloudfoundry + key: client-test-cloudfoundry-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/cloudfoundry + key: client-test-cloudfoundry-plan + + terragrunt-workflow-api-gateway: + name: Terragrunt - api-gateway + runs-on: ubuntu-20.04 + needs: [terragrunt-workflow-cloudfoundry] + container: alpine/terragrunt:1.0.1 + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + CONFIG: ./envoyconfig_without_multitenancy.yml + SOURCE: url + defaults: + run: + working-directory: ./deploy/CD/api-gateway + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v1 + - id: inner-source-action + uses: ./.github/workflows/innersource-action + with: + GH_PAT_TOKEN: ${{ secrets.GH_PAT_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + + - id: plan + uses: ./../.actions/terragrunt-plan-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-plan + + - id: apply + uses: ./../.actions/terragrunt-apply-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-plan + + - id: output + uses: ./.github/workflows/terragrunt-output-action + with: + working-directory: ./deploy/CD/api-gateway + key: client-test-api-gateway-output \ No newline at end of file diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 00000000..f28cffd0 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,449 @@ +name: CD + +on: + push: + branches: [ master ] + workflow_dispatch: + +jobs: + Init: + name: Initialize + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Extract branch name + uses: vazco/github-actions-branch-name@v1 + id: branch + + Build-Automation-Solution: + needs: [Init] + runs-on: builder_blr + name: Build Automation Solution + steps: + - name: cleanup + run: Remove-Item ${{ github.workspace }}\* -Recurse + - uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_TOKEN_CI }} + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + with: + vs-version: '[16.4,17.0)' + - name: Add dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + - name: msbuild execute + run: msbuild ${{ github.workspace }}\Build\api_automation_build.proj /t:build /p:configuration=release,platform=anycpu /nowarn:MSB4011,MSB4210 + - name: push nuget to artifactory + run: dotnet nuget push ${{ github.workspace }}\AutomationTest\**\*.nupkg -s JFrogrepository --skip-duplicate + + DeployCDWithoutMultitenancy: + if: github.ref == 'refs/heads/terraform' + needs: [Init] + uses: ./.github/workflows/cd-deploy-without-multitenancy.yml + secrets: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + HSDP_DOCKER_USER: ${{ secrets.HSDP_DOCKER_USER }} + HSDP_DOCKER_PASSWORD: ${{ secrets.HSDP_DOCKER_PASSWORD }} + CODESCENE_CI_CD_GITHUB_TOKEN: ${{ secrets.CODESCENE_CI_CD_GITHUB_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + GH_PAT_TOKEN: ${{secrets.GH_PAT_TOKEN}} + + CI-Automation-Variables: + if: github.ref == 'refs/heads/terraform' + needs: [DeployCDWithoutMultitenancy] + runs-on: ubuntu-20.04 + outputs: + oauth_client_secret: ${{ steps.core-output.outputs.oauth_client_secret }} + ci_service_private_key: ${{ steps.core-output.outputs.ci_service_private_key }} + envoy_base_url: "https://${{ steps.gateway-output.outputs.gateway_url }}" + deploy_user: ${{ steps.core-output.outputs.deploy_user }} + deploy_password: ${{ steps.core-output.outputs.deploy_password }} + ci_service_id: ${{ steps.core-output.outputs.ci_service_id }} + oauth_client_id: ${{ steps.core-output.outputs.oauth_client_id }} + steps: + - name: Download Core output + uses: actions/download-artifact@v2 + with: + name: client-test-core-output + path: ${{ github.workspace}}/output/core + - name: Download Gateway output + uses: actions/download-artifact@v2 + with: + name: client-test-api-gateway-output + path: ${{ github.workspace}}/output/gateway + - name: Get Outputs from Core module + working-directory: ${{ github.workspace}}/output/core + id: core-output + shell: bash + run: | + apt update && apt install jq -y + core_output=$(cat output.json) + + oauth_client_secret=$(echo $core_output | jq -r '.fdn_envoy_oauth_client_password.value') + ci_service_private_key=$(echo $core_output | jq -r '.foundation_envoy_nightly_service_private_key.value') + deploy_user=$(echo $core_output | jq -r '.cf_deploy_user.value') + deploy_password=$(echo $core_output | jq -r '.cf_deploy_password.value') + ci_service_id=$(echo $core_output | jq -r '.foundation_envoy_nightly_service_id.value') + oauth_client_id=$(echo $core_output | jq -r '.fdn_envoy_oauth_client_id.value') + + echo "::set-output name=oauth_client_secret::$oauth_client_secret" + echo "::set-output name=ci_service_private_key::$ci_service_private_key" + echo "::set-output name=oauth_client_id::$oauth_client_id" + echo "::set-output name=deploy_user::$deploy_user" + echo "::set-output name=deploy_password::$deploy_password" + echo "::set-output name=ci_service_id::$ci_service_id" + + - name: Get Outputs from Api Gateway + working-directory: ${{ github.workspace}}/output/gateway + id: gateway-output + shell: bash + run: | + apt update && apt install jq -y + output=$(cat output.json) + + gateway_url=$(echo $output | jq -r '.api_gateway_url.value') + echo $gateway_url + + echo "::set-output name=gateway_url::$gateway_url" + + TestwithoutMultitenancy: + if: github.ref == 'refs/heads/terraform' + needs: [Build-Automation-Solution, CI-Automation-Variables, DeployCDWithoutMultitenancy] + runs-on: builder_blr + steps: + - uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_TOKEN_CI }} + + - name: Add dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + + - name: Restore Automation Test Project + working-directory: ${{ github.workspace }} + run: | + dotnet restore ${{ github.workspace }}\AutomationTest\automation.packages.proj --packages ./automation-dll + + - uses: microsoft/variable-substitution@v1 + name: Update Env Json + with: + files: ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/1.0.3/lib/net3.1/Env.json + env: + PipelineConfiguration.AuthUserName: "sai.chand@philips.com" + PipelineConfiguration.AuthPassword: ${{ secrets.IAM_TEST_USER_PASSWORD }} + PipelineConfiguration.APIGatewayBaseUrl: "https://envoycd-api-gateway.us-east.philips-healthsuite.com" + PipelineConfiguration.ServiceID: ${{ needs.CI-Automation-Variables.outputs.ci_service_id }} + PipelineConfiguration.ServiceIDPrivateKey: ${{ needs.CI-Automation-Variables.outputs.ci_service_private_key }} + PipelineConfiguration.OauthClientID: ${{ needs.CI-Automation-Variables.outputs.oauth_client_id }} + PipelineConfiguration.OauthClientSecret: ${{ needs.CI-Automation-Variables.outputs.oauth_client_secret }} + PipelineConfiguration.CFOrgName: "client-EDI-SolutionAccelerator" + PipelineConfiguration.CFSpaceName: "envoycd" + PipelineConfiguration.CFUserName: ${{ secrets.CF_USERNAME }} + PipelineConfiguration.CFPassword: ${{ secrets.CF_PASSWD }} + PipelineConfiguration.CookieName: "edi_session_envoycd" + PipelineConfiguration.CFOauthTokenUrl: "https://login.cloud.pcftest.com/oauth/token" + PipelineConfiguration.CFBaseUrl: "https://api.cloud.pcftest.com/v3" + PipelineConfiguration.CFAuthenticatorAppName: "authenticator_service" + PipelineConfiguration.OauthProxyCookieTimeoutInSeconds: "25" + PipelineConfiguration.AuthenticatorSessionExpireOffsetInPercent: "99" + PipelineConfiguration.AccessTokenTestRoleName: "ENVOY-NIGHTLY-SERVICE-TF" + PipelineConfiguration.OpenIdConfigurationBaseUrl: "https://foundation-client-test.us-east.philips-healthsuite.com" + PipelineConfiguration.OrgSymmetricKey: ${{ secrets.ORG_SYMMETRIC_KEY }} + PipelineConfiguration.OpenIdConfigOrganizationCertificateMapping: '[{"organizationId":"ed186a39-8b5f-4351-bebc-4e17779c293b","certificate":"-----BEGIN CERTIFICATE-----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-----END CERTIFICATE-----"},{"organizationId":"51e2503f-c1df-430d-a1ce-2524fa796cda","certificate":"-----BEGIN CERTIFICATE-----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-----END CERTIFICATE-----"}]' + PipelineConfiguration.SSOTokenUserName: "user" + PipelineConfiguration.TenantDetails: '{"ValidTenant": {"tenantName": "org1-envoycd-api-gateway", "iamOrganizationId": "51e2503f-c1df-430d-a1ce-2524fa796cda"}, "InvalidTenant":{"tenantName": "invalidorg-envoycd-api-gateway","iamOrganizationId": "1234"},"AccessTokenTenant":{"tenantName": "accesstokentestorg","iamOrganizationId": "db3ba3e1-b333-4528-80d0-41b0eeb533b0"}}' + PipelineConfiguration.IAMAuthorizationUrl: "https://iam-client-test.us-east.philips-healthsuite.com/authorize/oauth2/token" + PipelineConfiguration.IAMAccessTokenUrl: "https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token" + PipelineConfiguration.IDMClientBaseUrl: "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity" + PipelineConfiguration.IAMGetUserUrl: "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity/User?profileType=membership&userId=" + PipelineConfiguration.CDRBaseUrl: "https://cdr-edisa-test.us-east.philips-healthsuite.com" + + - name: Run Tests + continue-on-error: true + working-directory: ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest + run: | + dotnet test **/net3.1/*Test.dll --no-restore --filter TestCategory=PostDeployment --results-directory ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults --logger trx + dotnet test **/net3.1/*Test.dll --no-restore --filter TestCategory=WithoutMultitenancy --results-directory ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults --logger trx + + - name: email-notification + continue-on-error: true + working-directory: ${{ github.workspace }}/Build/PS + run: | + powershell -ExecutionPolicy RemoteSigned -NoExit -File email-notification.ps1 -BuildNumber ${{ github.run_id }} -ReportFolder ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults -Subject Api-Gateway-WithoutMultitenancy + + + DeployCDWithMultitenancyurl: + if: github.ref == 'refs/heads/terraform' + needs: [TestwithoutMultitenancy] + uses: ./.github/workflows/cd-deploy-with-multitenancy-url.yml + secrets: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + HSDP_DOCKER_USER: ${{ secrets.HSDP_DOCKER_USER }} + HSDP_DOCKER_PASSWORD: ${{ secrets.HSDP_DOCKER_PASSWORD }} + CODESCENE_CI_CD_GITHUB_TOKEN: ${{ secrets.CODESCENE_CI_CD_GITHUB_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + GH_PAT_TOKEN: ${{secrets.GH_PAT_TOKEN}} + + CI-Automation-Variablesurl: + if: github.ref == 'refs/heads/terraform' + needs: [DeployCDWithMultitenancyurl] + runs-on: ubuntu-20.04 + outputs: + oauth_client_secret: ${{ steps.core-output.outputs.oauth_client_secret }} + ci_service_private_key: ${{ steps.core-output.outputs.ci_service_private_key }} + envoy_base_url: "https://${{ steps.gateway-output.outputs.gateway_url }}" + deploy_user: ${{ steps.core-output.outputs.deploy_user }} + deploy_password: ${{ steps.core-output.outputs.deploy_password }} + ci_service_id: ${{ steps.core-output.outputs.ci_service_id }} + oauth_client_id: ${{ steps.core-output.outputs.oauth_client_id }} + steps: + - name: Download Core output + uses: actions/download-artifact@v2 + with: + name: client-test-core-output + path: ${{ github.workspace}}/output/core + - name: Download Gateway output + uses: actions/download-artifact@v2 + with: + name: client-test-api-gateway-output + path: ${{ github.workspace}}/output/gateway + - name: Get Outputs from Core module + working-directory: ${{ github.workspace}}/output/core + id: core-output + shell: bash + run: | + apt update && apt install jq -y + core_output=$(cat output.json) + + oauth_client_secret=$(echo $core_output | jq -r '.fdn_envoy_oauth_client_password.value') + ci_service_private_key=$(echo $core_output | jq -r '.foundation_envoy_nightly_service_private_key.value') + deploy_user=$(echo $core_output | jq -r '.cf_deploy_user.value') + deploy_password=$(echo $core_output | jq -r '.cf_deploy_password.value') + ci_service_id=$(echo $core_output | jq -r '.foundation_envoy_nightly_service_id.value') + oauth_client_id=$(echo $core_output | jq -r '.fdn_envoy_oauth_client_id.value') + + echo "::set-output name=oauth_client_secret::$oauth_client_secret" + echo "::set-output name=ci_service_private_key::$ci_service_private_key" + echo "::set-output name=oauth_client_id::$oauth_client_id" + echo "::set-output name=deploy_user::$deploy_user" + echo "::set-output name=deploy_password::$deploy_password" + echo "::set-output name=ci_service_id::$ci_service_id" + + - name: Get Outputs from Api Gateway + working-directory: ${{ github.workspace}}/output/gateway + id: gateway-output + shell: bash + run: | + apt update && apt install jq -y + output=$(cat output.json) + + gateway_url=$(echo $output | jq -r '.api_gateway_url.value') + echo $gateway_url + + echo "::set-output name=gateway_url::$gateway_url" + + TestwithMultitenancyurl: + if: github.ref == 'refs/heads/terraform' + needs: [Build-Automation-Solution, CI-Automation-Variablesurl, DeployCDWithMultitenancyurl] + runs-on: builder_blr + steps: + - uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_TOKEN_CI }} + + - name: Add dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + + - name: Restore Automation Test Project + working-directory: ${{ github.workspace }} + run: | + dotnet restore ${{ github.workspace }}\AutomationTest\automation.packages.proj --packages ./automation-dll + + - uses: microsoft/variable-substitution@v1 + name: Update Env Json + with: + files: ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/1.0.3/lib/net3.1/Env.json + env: + PipelineConfiguration.AuthUserName: "sai.chand@philips.com" + PipelineConfiguration.AuthPassword: ${{ secrets.IAM_TEST_USER_PASSWORD }} + PipelineConfiguration.APIGatewayBaseUrl: "https://envoycd-api-gateway.us-east.philips-healthsuite.com" + PipelineConfiguration.ServiceID: ${{ needs.CI-Automation-Variables.outputs.ci_service_id }} + PipelineConfiguration.ServiceIDPrivateKey: ${{ needs.CI-Automation-Variables.outputs.ci_service_private_key }} + PipelineConfiguration.OauthClientID: ${{ needs.CI-Automation-Variables.outputs.oauth_client_id }} + PipelineConfiguration.OauthClientSecret: ${{ needs.CI-Automation-Variables.outputs.oauth_client_secret }} + PipelineConfiguration.CFOrgName: "client-EDI-SolutionAccelerator" + PipelineConfiguration.CFSpaceName: "envoycd" + PipelineConfiguration.CFUserName: ${{ secrets.CF_USERNAME }} + PipelineConfiguration.CFPassword: ${{ secrets.CF_PASSWD }} + PipelineConfiguration.CookieName: "edi_session_envoycd" + PipelineConfiguration.CFOauthTokenUrl: "https://login.cloud.pcftest.com/oauth/token" + PipelineConfiguration.CFBaseUrl: "https://api.cloud.pcftest.com/v3" + PipelineConfiguration.CFAuthenticatorAppName: "authenticator_service" + PipelineConfiguration.OauthProxyCookieTimeoutInSeconds: "25" + PipelineConfiguration.AuthenticatorSessionExpireOffsetInPercent: "99" + PipelineConfiguration.AccessTokenTestRoleName: "ENVOY-NIGHTLY-SERVICE-TF" + PipelineConfiguration.OpenIdConfigurationBaseUrl: "https://foundation-client-test.us-east.philips-healthsuite.com" + PipelineConfiguration.OrgSymmetricKey: ${{ secrets.ORG_SYMMETRIC_KEY }} + PipelineConfiguration.OpenIdConfigOrganizationCertificateMapping: '[{"organizationId":"ed186a39-8b5f-4351-bebc-4e17779c293b","certificate":"-----BEGIN CERTIFICATE-----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-----END CERTIFICATE-----"},{"organizationId":"51e2503f-c1df-430d-a1ce-2524fa796cda","certificate":"-----BEGIN CERTIFICATE-----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-----END CERTIFICATE-----"}]' + PipelineConfiguration.SSOTokenUserName: "user" + PipelineConfiguration.TenantDetails: '{"ValidTenant": {"tenantName": "org1-envoycd-api-gateway", "iamOrganizationId": "51e2503f-c1df-430d-a1ce-2524fa796cda"}, "InvalidTenant":{"tenantName": "invalidorg-envoycd-api-gateway","iamOrganizationId": "1234"},"AccessTokenTenant":{"tenantName": "accesstokentestorg","iamOrganizationId": "db3ba3e1-b333-4528-80d0-41b0eeb533b0"}}' + PipelineConfiguration.IAMAuthorizationUrl: "https://iam-client-test.us-east.philips-healthsuite.com/authorize/oauth2/token" + PipelineConfiguration.IAMAccessTokenUrl: "https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token" + PipelineConfiguration.IDMClientBaseUrl: "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity" + PipelineConfiguration.IAMGetUserUrl: "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity/User?profileType=membership&userId=" + PipelineConfiguration.CDRBaseUrl: "https://cdr-edisa-test.us-east.philips-healthsuite.com" + + - name: Run Tests + working-directory: ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest + run: | + dotnet test **/net3.1/*Test.dll --no-restore --filter TestCategory=OrgNameInUrl --results-directory ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults --logger trx + + - name: email-notification + working-directory: ${{ github.workspace }}/Build/PS + run: | + powershell -ExecutionPolicy RemoteSigned -NoExit -File email-notification.ps1 -BuildNumber ${{ github.run_id }} -ReportFolder ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults -Subject Api-Gateway-WithMultitenancy-org-id-source-url + + DeployCDWithMultitenancyheader: + if: github.ref == 'refs/heads/terraform' + needs: [DeployCDWithMultitenancyurl, TestwithMultitenancyurl] + uses: ./.github/workflows/cd-deploy-with-multitenancy-header.yml + secrets: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + HSDP_DOCKER_USER: ${{ secrets.HSDP_DOCKER_USER }} + HSDP_DOCKER_PASSWORD: ${{ secrets.HSDP_DOCKER_PASSWORD }} + CODESCENE_CI_CD_GITHUB_TOKEN: ${{ secrets.CODESCENE_CI_CD_GITHUB_TOKEN }} + INNER_SOURCE_ACTIONS_APP_ID: ${{ secrets.INNER_SOURCE_ACTIONS_APP_ID }} + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: ${{ secrets.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + GH_PAT_TOKEN: ${{secrets.GH_PAT_TOKEN}} + + CI-Automation-Variablesheader: + if: github.ref == 'refs/heads/terraform' + needs: [DeployCDWithMultitenancyheader] + runs-on: ubuntu-20.04 + outputs: + oauth_client_secret: ${{ steps.core-output.outputs.oauth_client_secret }} + ci_service_private_key: ${{ steps.core-output.outputs.ci_service_private_key }} + envoy_base_url: "https://${{ steps.gateway-output.outputs.gateway_url }}" + deploy_user: ${{ steps.core-output.outputs.deploy_user }} + deploy_password: ${{ steps.core-output.outputs.deploy_password }} + ci_service_id: ${{ steps.core-output.outputs.ci_service_id }} + oauth_client_id: ${{ steps.core-output.outputs.oauth_client_id }} + steps: + - name: Download Core output + uses: actions/download-artifact@v2 + with: + name: client-test-core-output + path: ${{ github.workspace}}/output/core + - name: Download Gateway output + uses: actions/download-artifact@v2 + with: + name: client-test-api-gateway-output + path: ${{ github.workspace}}/output/gateway + - name: Get Outputs from Core module + working-directory: ${{ github.workspace}}/output/core + id: core-output + shell: bash + run: | + apt update && apt install jq -y + core_output=$(cat output.json) + + oauth_client_secret=$(echo $core_output | jq -r '.fdn_envoy_oauth_client_password.value') + ci_service_private_key=$(echo $core_output | jq -r '.foundation_envoy_nightly_service_private_key.value') + deploy_user=$(echo $core_output | jq -r '.cf_deploy_user.value') + deploy_password=$(echo $core_output | jq -r '.cf_deploy_password.value') + ci_service_id=$(echo $core_output | jq -r '.foundation_envoy_nightly_service_id.value') + oauth_client_id=$(echo $core_output | jq -r '.fdn_envoy_oauth_client_id.value') + + echo "::set-output name=oauth_client_secret::$oauth_client_secret" + echo "::set-output name=ci_service_private_key::$ci_service_private_key" + echo "::set-output name=oauth_client_id::$oauth_client_id" + echo "::set-output name=deploy_user::$deploy_user" + echo "::set-output name=deploy_password::$deploy_password" + echo "::set-output name=ci_service_id::$ci_service_id" + + - name: Get Outputs from Api Gateway + working-directory: ${{ github.workspace}}/output/gateway + id: gateway-output + shell: bash + run: | + apt update && apt install jq -y + output=$(cat output.json) + + gateway_url=$(echo $output | jq -r '.api_gateway_url.value') + echo $gateway_url + + echo "::set-output name=gateway_url::$gateway_url" + + TestwithMultitenancyheader: + if: github.ref == 'refs/heads/terraform' + needs: [Build-Automation-Solution, CI-Automation-Variablesheader, DeployCDWithMultitenancyheader] + runs-on: builder_blr + steps: + - uses: actions/checkout@v2 + with: + token: ${{ secrets.GH_TOKEN_CI }} + + - name: Add dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + + - name: Restore Automation Test Project + working-directory: ${{ github.workspace }} + run: | + dotnet restore ${{ github.workspace }}\AutomationTest\automation.packages.proj --packages ./automation-dll + + - uses: microsoft/variable-substitution@v1 + name: Update Env Json + with: + files: ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/1.0.3/lib/net3.1/Env.json + env: + PipelineConfiguration.AuthUserName: "sai.chand@philips.com" + PipelineConfiguration.AuthPassword: ${{ secrets.IAM_TEST_USER_PASSWORD }} + PipelineConfiguration.APIGatewayBaseUrl: "https://envoycd-api-gateway.us-east.philips-healthsuite.com" + PipelineConfiguration.ServiceID: ${{ needs.CI-Automation-Variables.outputs.ci_service_id }} + PipelineConfiguration.ServiceIDPrivateKey: ${{ needs.CI-Automation-Variables.outputs.ci_service_private_key }} + PipelineConfiguration.OauthClientID: ${{ needs.CI-Automation-Variables.outputs.oauth_client_id }} + PipelineConfiguration.OauthClientSecret: ${{ needs.CI-Automation-Variables.outputs.oauth_client_secret }} + PipelineConfiguration.CFOrgName: "client-EDI-SolutionAccelerator" + PipelineConfiguration.CFSpaceName: "envoycd" + PipelineConfiguration.CFUserName: ${{ secrets.CF_USERNAME }} + PipelineConfiguration.CFPassword: ${{ secrets.CF_PASSWD }} + PipelineConfiguration.CookieName: "edi_session_envoycd" + PipelineConfiguration.CFOauthTokenUrl: "https://login.cloud.pcftest.com/oauth/token" + PipelineConfiguration.CFBaseUrl: "https://api.cloud.pcftest.com/v3" + PipelineConfiguration.CFAuthenticatorAppName: "authenticator_service" + PipelineConfiguration.OauthProxyCookieTimeoutInSeconds: "25" + PipelineConfiguration.AuthenticatorSessionExpireOffsetInPercent: "99" + PipelineConfiguration.AccessTokenTestRoleName: "ENVOY-NIGHTLY-SERVICE-TF" + PipelineConfiguration.OpenIdConfigurationBaseUrl: "https://foundation-client-test.us-east.philips-healthsuite.com" + PipelineConfiguration.OrgSymmetricKey: ${{ secrets.ORG_SYMMETRIC_KEY }} + PipelineConfiguration.OpenIdConfigOrganizationCertificateMapping: '[{"organizationId":"ed186a39-8b5f-4351-bebc-4e17779c293b","certificate":"-----BEGIN CERTIFICATE-----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-----END CERTIFICATE-----"},{"organizationId":"51e2503f-c1df-430d-a1ce-2524fa796cda","certificate":"-----BEGIN CERTIFICATE-----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-----END CERTIFICATE-----"}]' + PipelineConfiguration.SSOTokenUserName: "user" + PipelineConfiguration.TenantDetails: '{"ValidTenant": {"tenantName": "org1-envoycd-api-gateway", "iamOrganizationId": "51e2503f-c1df-430d-a1ce-2524fa796cda"}, "InvalidTenant":{"tenantName": "invalidorg-envoycd-api-gateway","iamOrganizationId": "1234"},"AccessTokenTenant":{"tenantName": "accesstokentestorg","iamOrganizationId": "db3ba3e1-b333-4528-80d0-41b0eeb533b0"}}' + PipelineConfiguration.IAMAuthorizationUrl: "https://iam-client-test.us-east.philips-healthsuite.com/authorize/oauth2/token" + PipelineConfiguration.IAMAccessTokenUrl: "https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token" + PipelineConfiguration.IDMClientBaseUrl: "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity" + PipelineConfiguration.IAMGetUserUrl: "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity/User?profileType=membership&userId=" + PipelineConfiguration.CDRBaseUrl: "https://cdr-edisa-test.us-east.philips-healthsuite.com" + + - name: Run Tests + working-directory: ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest + run: | + dotnet test **/net3.1/*Test.dll --no-restore --filter TestCategory=OrgIdInHeader --results-directory ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults --logger trx + + - name: email-notification + working-directory: ${{ github.workspace }}/Build/PS + run: | + powershell -ExecutionPolicy RemoteSigned -NoExit -File email-notification.ps1 -BuildNumber ${{ github.run_id }} -ReportFolder ${{ github.workspace }}/automation-dll/philips.edi.foundation.apigateway.automationtest/TestResults -Subject Api-Gateway-WithMultitenancy-org-id-source-header \ No newline at end of file diff --git a/.github/workflows/innersource-action/action.yml b/.github/workflows/innersource-action/action.yml new file mode 100644 index 00000000..f999e07d --- /dev/null +++ b/.github/workflows/innersource-action/action.yml @@ -0,0 +1,33 @@ +name: 'Setup Innersource Actions' + +inputs: + GH_PAT_TOKEN: + required: true + INNER_SOURCE_ACTIONS_APP_ID: + required: true + INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64: + required: true +runs: + using: "composite" + steps: + # Setup inner source actions + - name: Get Token + id: token + uses: philips-software/app-token-action@v1.0.2 + with: + app_id: ${{ inputs.INNER_SOURCE_ACTIONS_APP_ID }} + app_base64_private_key: ${{ inputs.INNER_SOURCE_ACTIONS_APP_PRIVATE_KEY_BASE64 }} + auth_type: "installation" + + - uses: philips-software/inner-source-checkout-action@v1.1.0 + with: + token: ${{ steps.token.outputs.token }} + base_dir: ../.actions + repos: philips-internal/terragrunt-plan-action@v1, philips-internal/terragrunt-apply-action@v1 + + - uses: actions/checkout@v2 + + - uses: philips-labs/terraform-private-modules-action@v1.1 + with: + org: philips-internal + token: ${{ inputs.GH_PAT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/terragrunt-output-action/action.yml b/.github/workflows/terragrunt-output-action/action.yml new file mode 100644 index 00000000..d135f50d --- /dev/null +++ b/.github/workflows/terragrunt-output-action/action.yml @@ -0,0 +1,25 @@ +name: 'Terragrunt Output' + +inputs: + working-directory: + description: "Working directory - path to your Terragrunt module" + required: true + key: + description: "Key used to save plan file to artifacts" + required: true + +runs: + using: "composite" + steps: + - id: terragrunt_output + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + terragrunt output -json > /tmp/output.json + + - name: Upload Output + uses: actions/upload-artifact@v2 + with: + name: ${{ inputs.key }} + path: /tmp/output.json + if-no-files-found: error \ No newline at end of file diff --git a/AutomationTest/.editorconfig b/AutomationTest/.editorconfig new file mode 100644 index 00000000..0e13a93f --- /dev/null +++ b/AutomationTest/.editorconfig @@ -0,0 +1,22 @@ +[*.cs] + +# PH2028: Copyright Present +dotnet_diagnostic.PH2028.severity = none + +# PH2006: Namespace matches File Path +dotnet_diagnostic.PH2006.severity = none + +# PH 2071: change default token count for code duplication detection +dotnet_code_quality.PH2071.token_count = 100 + +# PH2079: Specify the namespace prefix in the .editorconfig file +dotnet_code_quality.PH2079.namespace_prefix = Philips.EDI.Foundation + +# PH2019: TestCleanup methods not allowed +dotnet_diagnostic.PH2019.severity = none + +# PH2016: TestInitialize methods not allowed +dotnet_diagnostic.PH2016.severity = none + +dotnet_code_quality.PH2015.allowed_test_categories = TestCategory.GatedSanity,TestCategory.Nightly,TestCategory.PostDeployment,TestCategory.WithoutMultitenancy,TestCategory.MultitenancyWithOrgNameAsSubdomain,TestCategory.MultitenancyWithOrgIdInHeader + diff --git a/AutomationTest/BusinessLayer/AuthenticationBL.cs b/AutomationTest/BusinessLayer/AuthenticationBL.cs new file mode 100644 index 00000000..9caefd13 --- /dev/null +++ b/AutomationTest/BusinessLayer/AuthenticationBL.cs @@ -0,0 +1,51 @@ +using System; +using Utilities; +using Driver.UI.Common; +using Driver.UI.Interfaces; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer +{ + public class AuthenticationBL + { + private readonly IWebDriverUi _webDriver; + + public AuthenticationBL(IWebDriverUi webDriver) + { + _webDriver = webDriver; + } + + public Cookies GetCookie(string cookieName) + { + Logger.InfoStartMethod(); + try + { + Logger.Info($"Get the cookie Named: {cookieName}"); + var cookies = _webDriver.GetAllCookies(); + return cookies.Find(x => x.CookieName.EqualsWithIgnoreCase(cookieName)); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public void SetCookie(Cookies cookie) + { + Logger.InfoStartMethod(); + try + { + Logger.Info($"Cookie Name: {cookie.CookieName}"); + Logger.Info($"Cookie Value: {cookie.CookieValue}"); + _webDriver.DeleteCookieNamed(cookie.CookieName); + _webDriver.AddNewCookie(cookie); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + } +} diff --git a/AutomationTest/BusinessLayer/CommonBL.cs b/AutomationTest/BusinessLayer/CommonBL.cs new file mode 100644 index 00000000..ecfee608 --- /dev/null +++ b/AutomationTest/BusinessLayer/CommonBL.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using Newtonsoft.Json.Linq; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer +{ + public class CommonBL + { + + public JObject GetImposterRecordedRequests(string apiGatewayBaseUrl, Dictionary headers, string mockserviceJsonConfigFile) + { + try + { + string mockserviceJsonConfigFilePath = Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Tests", "Data", "MockServiceConfigs", mockserviceJsonConfigFile); + Logger.Info($"Mockservice json config file path: {mockserviceJsonConfigFilePath}"); + string port = JObject.Parse(File.ReadAllText(mockserviceJsonConfigFilePath))["port"].ToString(); + string imposterGetUrl = $"{apiGatewayBaseUrl}/imposters/{port}"; + Logger.Info($"Imposter Get Url: { imposterGetUrl}"); + return HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, imposterGetUrl, headers, null); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public JToken GetUpstreamRecordedRequest(string apiGatewayBaseUrl, Dictionary headers, string customUniqueRequestIdHeaderName, string customUniqueRequestIdHeaderValue, string mockserviceJsonConfigFile) + { + try + { + var getResponseBody = GetImposterRecordedRequests(apiGatewayBaseUrl, headers, mockserviceJsonConfigFile); + Logger.Info($"Response body: {getResponseBody}"); + JToken testRecordedRequest = getResponseBody["requests"].Where(x => x["headers"][customUniqueRequestIdHeaderName] != null && x["headers"][customUniqueRequestIdHeaderName].ToString() == customUniqueRequestIdHeaderValue).FirstOrDefault(); + return testRecordedRequest; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + } +} diff --git a/AutomationTest/BusinessLayer/HSPIAMBusinessLayer.cs b/AutomationTest/BusinessLayer/HSPIAMBusinessLayer.cs new file mode 100644 index 00000000..1bf58b28 --- /dev/null +++ b/AutomationTest/BusinessLayer/HSPIAMBusinessLayer.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using Utilities; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Utilities.Wait; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer +{ + public class HSPIAMBusinessLayer + { + public string GetRoleId(string idmClientBaseUrl, Dictionary headers, string OrgId, string roleName) + { + try + { + string idmClientRoleUrl = $"{idmClientBaseUrl}/Role?organizationId={OrgId}&name={roleName}"; + Logger.Info($"Get Role url: {idmClientRoleUrl}"); + headers.TryAdd("api-version", "1"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, idmClientRoleUrl, headers, null); + var entry = responseBody["entry"]?.FirstOrDefault(); + var roleId = entry?["id"]?.ToString(); + Logger.Info($"Role Id: {roleId}"); + return roleId; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public List GetAllPersmissionsFromRole(string idmClientBaseUrl, Dictionary headers, string roleID) + { + try + { + string idmClientPermissionsUrl = $"{idmClientBaseUrl}/Permission?roleId={roleID}"; + Logger.Info($"Get Permission url: {idmClientPermissionsUrl}"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, idmClientPermissionsUrl, headers, null); + return responseBody["entry"]?.Select(x => x["name"].ToString()).ToList(); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public bool AssignPermissionToRole(string idmClientBaseUrl, Dictionary headers, string roleID, string permissionsJson) + { + try + { + return ManipulatePersmissionsInRole(idmClientBaseUrl, "assign-permission", headers, roleID, permissionsJson); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public bool RemovePermissionFromRole(string idmClientBaseUrl, Dictionary headers, string roleID, string permissionsJson) + { + try + { + return ManipulatePersmissionsInRole(idmClientBaseUrl, "remove-permission", headers, roleID, permissionsJson); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + private bool ManipulatePersmissionsInRole(string idmClientBaseUrl, string permissionType, Dictionary headers, string roleID, string permissionsJson) + { + string iamClientRoleUrl = $"{idmClientBaseUrl}/Role/{roleID}/${permissionType}"; + Logger.Info($"Role manipulation url: {iamClientRoleUrl}"); + var httpContent = HttpClientUtility.CreateHttpContent(permissionsJson); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Post, iamClientRoleUrl, headers, httpContent).Result; + Sleep.Seconds(5); + return response.StatusCode == HttpStatusCode.OK; + } + + /// + /// Getting the user details from IAM + /// + /// URL to get the user details form IAM + /// user mail id to get details + /// headers + /// List of usersUUID based on mail id + public List GetUserDetails(string iamGetUserUrl, string userMailId, Dictionary headers) + { + string finalUrl = $"{iamGetUserUrl}{userMailId}"; + Logger.Info($"Get Users url: {finalUrl}"); + headers.TryAdd("api-version","2"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, finalUrl, headers, null); + if (responseBody != null) + { + List userLoginId = responseBody["entry"]?.Where(x => x["id"] != null).Select(x => x["id"].ToString()).ToList(); + return userLoginId; + } + return null; + } + + /// + /// Deleting the user based on userName and respective Organization ID + /// + /// URL to get the user details form IAM + /// IDM client Base URL + /// user mail id to be deleted + /// headers + /// HttpMessage + public HttpResponseMessage DeleteUser(string iamGetUserUrl, string idmClientBaseUrl, string userMailId, Dictionary headers) + { + List userIdtoDelete = GetUserDetails(iamGetUserUrl, userMailId.ToLower(), headers); + headers.TryAdd("api-version", "2"); + headers.TryAdd("Accept", "application/json"); + if (userIdtoDelete.Count >= 1) + { + string deleteUrl = $"{idmClientBaseUrl}/User/{userIdtoDelete[0]}"; + return HttpClientUtility.ExecuteAsync(HttpMethod.Delete, deleteUrl, headers, null).Result; + } + return null; + } + } +} diff --git a/AutomationTest/BusinessLayer/ReverseProxyBL.cs b/AutomationTest/BusinessLayer/ReverseProxyBL.cs new file mode 100644 index 00000000..3e1bb5f2 --- /dev/null +++ b/AutomationTest/BusinessLayer/ReverseProxyBL.cs @@ -0,0 +1,29 @@ +using Driver.UI.Interfaces; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer +{ + public class ReverseProxyBL + { + private readonly IWebDriverUi _webDriver; + + public ReverseProxyBL(IWebDriverUi webDriver) + { + _webDriver = webDriver; + } + + public bool NavigateToImpostersPageAndGetIsMockServiceLinksDisplayed(string impostersUrl) + { + _webDriver.Goto(impostersUrl); + _webDriver.TakeScreenshot(); + return _webDriver.IsDisplayed(_webDriver.FindElementByXPath("//table[@id='imposters']")); + } + public bool ClickOnImposterAndGetMockServiceContentIsDisplayed(string mockserviceName) + { + _webDriver.Click(_webDriver.FindElementByXPath($"//a[normalize-space(.)='{mockserviceName}']")); + _webDriver.TakeScreenshot(); + return _webDriver.IsDisplayed(_webDriver.FindElementByXPath("//code[contains(.,'predicates')]")); + } + + } +} diff --git a/AutomationTest/BusinessLayer/VueSSOTokenBL.cs b/AutomationTest/BusinessLayer/VueSSOTokenBL.cs new file mode 100644 index 00000000..6a8da4cc --- /dev/null +++ b/AutomationTest/BusinessLayer/VueSSOTokenBL.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Web; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer +{ + public class VueSSOTokenBL + { + public string GenerateSsoToken(string username, string sessionTime, string symmetricKey) + { + var ssoToken = $"user_name={username}&session_time={sessionTime}"; + return EncryptSsoToken(ssoToken, symmetricKey); + } + + private static string EncryptSsoToken(string toBeEncryptedUrl, string symmetricKey) + { + byte[] encrypted; + var Key = Convert.FromBase64String(symmetricKey); + byte[] IV = new byte[16]; + using (AesManaged aes = new AesManaged()) + { + ICryptoTransform encryptor = aes.CreateEncryptor(Key, IV); + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + // Create StreamWriter and write data to a stream + using (StreamWriter sw = new StreamWriter(cs)) + sw.Write(toBeEncryptedUrl); + encrypted = ms.ToArray(); + return HttpUtility.UrlEncode(Convert.ToBase64String(encrypted)); + } + } + } + } + } +} diff --git a/AutomationTest/Env.json b/AutomationTest/Env.json new file mode 100644 index 00000000..c1c17da5 --- /dev/null +++ b/AutomationTest/Env.json @@ -0,0 +1,51 @@ +{ + "AppConfiguration": { + "RootFolder": "C:\\AutomationOutput", + "ReporterList": "html;word", + "RootEvidencePath": "C:\\AutomationOutput\\Evidences", + "DifidoFolderLocation": "difido-reports-common.jar", + "ProductName": "API Gateway", + "LessPayloadMockservice": "/mockserviceA/test", + "HeavyPayloadMockservice": "/mockserviceB/test", + "ServiceUnavailableMockservice": "/mockserviceX", + "MultitenancyMockservice": "/mockserviceA/multitenancy", + "LogoutPath": "/logout", + "CDRSubscriptionUrlPath": "/store/fhir/OrgId/Subscription", + "CDRImagingStudyUrlPath": "/store/fhir/OrgId/ImagingStudy", + "QidoStudyLevelUrlPathWithoutOrgId": "/dicom/qido/studies", + "IamBrokerConfigAPIRelativePath": "/IamTokenExchangeBroker/OrgId/config/", + "OpenIdConfigurationUrlPath": "/tokenvalidator/OrgId/openid-configuration", + "MountibankTimeoutinSeconds": "15" + }, + "PipelineConfiguration": { + "SSOTokenUserName": "", + "OrgSymmetricKey": "", + "AuthUserName": "", + "AuthPassword": "", + "ServiceID": "", + "ServiceIDPrivateKey": "", + "IAMAuthorizationUrl": "", + "IAMAccessTokenUrl": "", + "IDMClientBaseUrl": "", + "IAMGetUserUrl": "", + "CDRBaseUrl": "", + "OauthClientID": "", + "OauthClientSecret": "", + "CFOrgName": "", + "CFSpaceName": "", + "CFUserName": "", + "CFPassword": "", + "CookieName": "", + "CFOauthTokenUrl": "", + "CFBaseUrl": "", + "CFAuthenticatorAppName": "", + "APIGatewayBaseUrl": "", + "OauthProxyCookieTimeoutInSeconds": "", + "AuthenticatorSessionExpireOffsetInPercent": "", + "AccessTokenTestRoleName": "", + "OpenIdConfigurationBaseUrl": "", + "OpenIdConfigOrganizationCertificateMapping": "", + "TenantDetails": "" + }, + "ExecutionEnvironment": "Local" +} diff --git a/AutomationTest/Models/AppConfiguration.cs b/AutomationTest/Models/AppConfiguration.cs new file mode 100644 index 00000000..8b1f7c14 --- /dev/null +++ b/AutomationTest/Models/AppConfiguration.cs @@ -0,0 +1,23 @@ +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Models +{ + public class AppConfiguration + { + public string RootFolder { get; set; } + public string ReporterList { get; set; } + public string RootEvidencePath { get; set; } + public string DifidoFolderLocation { get; set; } + public string ProductName { get; set; } + public string ProductVersion { get; set; } + public string LessPayloadMockservice { get; set; } + public string HeavyPayloadMockservice { get; set; } + public string ServiceUnavailableMockservice { get; set; } + public string MultitenancyMockservice { get; set; } + public string LogoutPath { get; set; } + public string CDRSubscriptionUrlPath { get; set; } + public string QidoStudyLevelUrlPathWithoutOrgId { get; set; } + public string CDRImagingStudyUrlPath { get; set; } + public string IamBrokerConfigAPIRelativePath { get; set; } + public string OpenIdConfigurationUrlPath { get; set; } + public int MountibankTimeoutinSeconds { get; set; } + } +} diff --git a/AutomationTest/Models/Constants.cs b/AutomationTest/Models/Constants.cs new file mode 100644 index 00000000..cbd75a11 --- /dev/null +++ b/AutomationTest/Models/Constants.cs @@ -0,0 +1,17 @@ +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Models +{ + public class Constants + { + public const string CustomUniqueRequestHeaderName = "customuniquerequestheader"; + public const string AuthenticatorSessionExpireOffsetInPercent = "Authenticator_session__SessionExpireOffsetInPercent"; + public const string ValidTenantKey = "ValidTenant"; + public const string InvalidTenantKey = "InvalidTenant"; + public const string AccessTokenTenantKey = "AccessTokenTenant"; + public const string TimeStampFormat = "yyyy-MM-ddTHH:mm:ss"; + public const int RetryTimeOutInSeconds = 30; + public const string SymmetricKeyName = "VUESSOSYMMETRICKEY"; + public const string TimeZoneKeyName = "VUESSOTIMEZONE"; + public const string TokyoTimeZone = "Tokyo Standard Time"; + public const string UTCTimeZone = "UTC"; + } +} diff --git a/AutomationTest/Models/Enums.cs b/AutomationTest/Models/Enums.cs new file mode 100644 index 00000000..1e75fef8 --- /dev/null +++ b/AutomationTest/Models/Enums.cs @@ -0,0 +1,29 @@ +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Models +{ + public enum TestCategory + { + GatedSanity, + Nightly, + PostDeployment, + WithoutMultitenancy, + OrgNameInUrl, + OrgIdInHeader, + UserAccessToken, + ServiceIDAccessToken, + BrowserLogout, + SSOToken, + IAMTokenExchangeBroker, + IAMTokenExchangeBrokerPreCondition, + IDTokenValidator, + OnPrem, + UpgradeSanity, + IntegratedSanity + + } + + public enum ExecutionEnvironment + { + Local, + Production + } +} diff --git a/AutomationTest/Models/OpenIdConfigOrganizationCertificateMapping.cs b/AutomationTest/Models/OpenIdConfigOrganizationCertificateMapping.cs new file mode 100644 index 00000000..c076342b --- /dev/null +++ b/AutomationTest/Models/OpenIdConfigOrganizationCertificateMapping.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Models +{ + public class OpenIdConfigOrganizationCertificateMapping + { + [JsonProperty("organizationId")] + public string OrganizationId { get; set; } + + [JsonProperty("certificate")] + public string Certificate { get; set; } + } +} diff --git a/AutomationTest/Models/PipelineConfiguration.cs b/AutomationTest/Models/PipelineConfiguration.cs new file mode 100644 index 00000000..b8d3a8c4 --- /dev/null +++ b/AutomationTest/Models/PipelineConfiguration.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Models +{ + public class PipelineConfiguration + { + public string AuthUserName { get; set; } + public string AuthPassword { get; set; } + public string APIGatewayBaseUrl { get; set; } + public string OauthClientID { get; set; } + public string OauthClientSecret { get; set; } + public string CFOrgName { get; set; } + public string CFSpaceName { get; set; } + public string CFUserName { get; set; } + public string CFPassword { get; set; } + public string CookieName { get; set; } + public string CFOauthTokenUrl { get; set; } + public string CFBaseUrl { get; set; } + public string CFAuthenticatorAppName { get; set; } + public int OauthProxyCookieTimeoutInSeconds { get; set; } + public string AuthenticatorSessionExpireOffsetInPercent { get; set; } + public string AccessTokenTestRoleName { get; set; } + public string IAMAuthorizationUrl { get; set; } + public string IAMAccessTokenUrl { get; set; } + public string ServiceID { get; set; } + public string ServiceIDPrivateKey { get; set; } + public string IDMClientBaseUrl { get; set; } + public string OrgSymmetricKey { get; set; } + public string CDRBaseUrl { get; set; } + public string IAMGetUserUrl { get; set; } + public string OpenIdConfigurationBaseUrl { get; set; } + public string SSOTokenUserName { get; set; } + public string OpenIdConfigOrganizationCertificateMapping { get; set; } + public string TenantDetails { get; set; } + } +} diff --git a/AutomationTest/Models/TenantMapping.cs b/AutomationTest/Models/TenantMapping.cs new file mode 100644 index 00000000..23fdc424 --- /dev/null +++ b/AutomationTest/Models/TenantMapping.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Models +{ + public class TenantMapping + { + [JsonProperty("tenantName")] + public string TenantName { get; set; } + [JsonProperty("iamOrganizationId")] + public string IamOrganizationId { get; set; } + } +} diff --git a/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.csproj b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.csproj new file mode 100644 index 00000000..23868669 --- /dev/null +++ b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.csproj @@ -0,0 +1,70 @@ + + + + netcoreapp3.1 + .\Philips.EDI.Foundation.APIGateway.AutomationTest.nuspec + version=$(version);id=$(MSBuildProjectName) + .\Bin\$(configuration)\$(TargetFramework) + true + true + OnOutputUpdated + PH2075 + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + Always + + + Always + + + Always + + + Always + + + + + + + + + diff --git a/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.json b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.json new file mode 100644 index 00000000..b5c42f84 --- /dev/null +++ b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.json @@ -0,0 +1,36 @@ +{ + "PipelineConfiguration": { + "SSOTokenUserName": "", + "OrgSymmetricKey": "", + "AuthUserName": "sai.chand@philips.com", + "AuthPassword": "", + "ServiceID": "apigateway-nightly.apigateway-nightly.pf-nightly-tf@pf-nightly-tf.edi-platform-service.ediplatform.philips-healthsuite.com", + "ServiceIDPrivateKey": "", + + "IAMAuthorizationUrl": "https://iam-client-test.us-east.philips-healthsuite.com/authorize/oauth2/token", + "IAMAccessTokenUrl": "https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token", + "IDMClientBaseUrl": "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity", + "IAMGetUserUrl": "https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity/User?profileType=membership&userId=", + "CDRBaseUrl": "https://cdr-edisa-test.us-east.philips-healthsuite.com", + "OauthClientID": "auto-mfpbc", + "OauthClientSecret": "", + + "CFOrgName": "client-EDI-SolutionAccelerator", + "CFSpaceName": "", + "CFUserName": "solutionaccelerator-cicd-svc", + "CFPassword": "", + "CookieName": "", + "CFOauthTokenUrl": "https://login.cloud.pcftest.com/oauth/token", + "CFBaseUrl": "https://api.cloud.pcftest.com/v3", + "CFAuthenticatorAppName": "authenticator_service", + + "APIGatewayBaseUrl": "", + "OauthProxyCookieTimeoutInSeconds": "25", + "AuthenticatorSessionExpireOffsetInPercent": "99", + "AccessTokenTestRoleName": "TESTROLE", + + "OpenIdConfigurationBaseUrl": "https://foundation-client-test.us-east.philips-healthsuite.com", + "OpenIdConfigOrganizationCertificateMapping": "", + "TenantDetails": "" + } +} diff --git a/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.nuspec b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.nuspec new file mode 100644 index 00000000..6a24c912 --- /dev/null +++ b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.nuspec @@ -0,0 +1,34 @@ + + + + + + +$id$ + +$version$ + + + +<authors>Philips EDI Foundation APIGateway Automation</authors> + +<owners /> + +<requireLicenseAcceptance>false</requireLicenseAcceptance> + +<description>""</description> +<dependencies> + <group targetFramework="netcoreapp3.1" /> + </dependencies> + +</metadata> + +<files> + +<file src="**" exclude="*pdb*" target="lib\net3.1\" /> + + + +</files> + +</package> \ No newline at end of file diff --git a/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.sln b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.sln new file mode 100644 index 00000000..608ef854 --- /dev/null +++ b/AutomationTest/Philips.EDI.Foundation.APIGateway.AutomationTest.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30225.117 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Philips.EDI.Foundation.APIGateway.AutomationTest", "Philips.EDI.Foundation.APIGateway.AutomationTest.csproj", "{38895658-54E2-47D6-9AF9-552E658FBDDD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {38895658-54E2-47D6-9AF9-552E658FBDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38895658-54E2-47D6-9AF9-552E658FBDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38895658-54E2-47D6-9AF9-552E658FBDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38895658-54E2-47D6-9AF9-552E658FBDDD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A2BC2EB9-36EE-4105-86F7-2B8ECE632FAD} + EndGlobalSection +EndGlobal diff --git a/AutomationTest/README.md b/AutomationTest/README.md new file mode 100644 index 00000000..46ca72d3 --- /dev/null +++ b/AutomationTest/README.md @@ -0,0 +1,70 @@ +# API Gateway Automation + +## Pre-Condition + +Mountebank application should be deployed along with API Gateway deployment. +Mountebank deployment with terrform: https://github.com/philips-internal/hds-auth-gateway/blob/master/Automation/deploy/cloud/app_monteback.tf + +Allow Injection command should be passed while starting the mountebank application + +command = "node bin/mb --allowInjection" + +PostDeploymentTest has to executed first in order to create less payload and heavy payload mockservices for automation. +(TestCategory = PostDeployment) + +Mockservice configuration files https://github.com/philips-internal/hds-auth-gateway/tree/master/AutomationTest/Tests/Data/MockServiceConfigs + +## Automation Configuration + +https://github.com/philips-internal/hds-auth-gateway/blob/master/AutomationTest/Env.json + +AppConfiguration section is for static confguration values and drive name can be changed from C:\ to other drive for automation word reports and screenshots. + + "AppConfiguration": { } + +PipelineConfiguration section is for dynamic configuration which will be filled from tfs pipeline or user can fill before executing the automation. + + "PipelineConfiguration": { } + + +| Key | Description | Type | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| AuthUserName | IAM login user email address, For browser based login tests and also for access token authenticator tests | String | +| AuthPassword | IAM login password | String | +| APIGatewayBaseUrl | BaseUrl fo the API gateway <br/>Examples:<br/>For Cloud: https://covid-ver2-api-gateway.us-east.philips-healthsuite.com<br/>For On-Prem: https://localhost (or) https://hostname | String | +| OauthProxyCookieTimeoutInSeconds | Oauth proxy cookie time out in seconds ( default value: 15 ) | Integer | +| TenantDetails | Tenant information with user-friendly tenant sub-domain name and its relevant Iam organization-ID. <br/>Default Values:<br/>"ValidTenant":{"tenantName":"","iamOrganizationId":""}<br>"InvalidTenant":{"tenantName":"invalidtenant","iamOrganizationId":""}<br>"AccessTokenTenant":{"tenantName":"accesstokentenant","iamOrganizationId":""} | String | +| OauthClientID | IAM Oauth client id or User name. Used to get the Authorization Basic token, which will be used to get User or ServiceId Access token. | String | +| OauthClientSecret | IAM Oauth client password | String | +| CFSpaceName | Cloud foundry space name where the api gateway is deployed | String | +| CFUserName | Cloud foundry user name | String | +| CFPassword | Cloud foundry user password | String | +| CookieName | OAuth2 Proxy cookie name | String | +| CFLoginUrl | Cloud foundry login url (default: https://login.cloud.pcftest.com/oauth/token) | String | +| CFAppsUrl | Cloud foundry apps url (default: https://api.cloud.pcftest.com/v3/apps) | String | +| CFAuthenticatorAppName | Token Authenticator internal app name deployed in the space | String | +| AuthenticatorSessionExpireOffsetInPercent | Redis cache clear timeout offset for Authenticator app (default: 10), eg: if set to 99, then Redis cache will get cleared in 18 seconds ( if IAM access token timeout: 30 minutes) | String | +| ServiceID | Service identities ID in IAM | String | +| ServiceIDPrivateKey | ServiceID's private key in IAM | String | +| IDMClientBaseUrl | IDM client url till identiry (eg: https://idm-client-test.us-east.philips-healthsuite.com/authorize/identity) IAM | String | +String | +| IAMAuthorizationUrl | IAM client authorization url (eg: https://iam-client-test.us-east.philips-healthsuite.com/authorize/oauth2/token) IAM | String | +| IAMAccessTokenUrl | IAM client access token url (eg: https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token) IAM | String | +| AccessTokenTestRoleName | Role with BASIC.READ permission created for an organization ( eg: RoleName: TESTROLE ) which is used for access token authenticator tests | String | + +## Automation Test categories and corresponding Envoy configuration files to deploy + +| TestCategory | Envoy file | Comments | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| WithoutMultitenancy | https://github.com/philips-internal/hds-auth-gateway/tree/master/AutomationTest/Tests/Data/EnvoyConfigs/envoyconfig_without_multitenancy.yaml | With Standalone Redis | +| OrgNameInUrl | https://github.com/philips-internal/hds-auth-gateway/tree/master/AutomationTest/Tests/Data/EnvoyConfigs/envoyconfig_with_multitenancy.yaml | With Standalone Redis and APIGateway environment variable ORG_ID_SOURCE="url" | +| OrgIdInHeader | https://github.com/philips-internal/hds-auth-gateway/tree/master/AutomationTest/Tests/Data/EnvoyConfigs/envoyconfig_with_multitenancy.yaml | With Cluster Redis and APIGateway environment variable ORG_ID_SOURCE="header" | +| UserAccessToken | https://github.com/philips-internal/hds-auth-gateway/tree/master/AutomationTest/Tests/Data/EnvoyConfigs/envoyconfig_withoutMultitenancy_WithAuthenticator.yaml | With Standalone Redis" | +| ServiceIDAccessToken | https://github.com/philips-internal/hds-auth-gateway/tree/master/AutomationTest/Tests/Data/EnvoyConfigs/envoyconfig_withoutMultitenancy_WithAuthenticator.yaml | With Standalone Redis" | + +## Automation Test Reports +Automation test reports will be available as word report and trx report file which is interated with tfs release pipeline and displayed in test results dashboard. +https://tfsemea1.ta.philips.com/tfs/TPC_Region11/SAL/_dashboards/dashboard/76acadfe-3e1f-4225-9f4f-19af794bc95f + +## Manual Test cases suite: +https://tfsemea1.ta.philips.com/tfs/TPC_Region11/Healthcare%20IT/_testPlans/define?planId=1297712&suiteId=1298021 \ No newline at end of file diff --git a/AutomationTest/Tests/APITests/AccessTokenAuthenticationTests/AccessTokenAuthenticationTests.cs b/AutomationTest/Tests/APITests/AccessTokenAuthenticationTests/AccessTokenAuthenticationTests.cs new file mode 100644 index 00000000..b2022004 --- /dev/null +++ b/AutomationTest/Tests/APITests/AccessTokenAuthenticationTests/AccessTokenAuthenticationTests.cs @@ -0,0 +1,532 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Linq; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites; +using Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites.Models; +using Reporters; +using Utilities; +using Utilities.Wait; +using System.Threading.Tasks; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests.AccessTokenAuthenticationTests +{ + [TestClass] + public class AccessTokenAuthenticationTests : BaseTest + { + private readonly CFUtility _cfUtility = new CFUtility(pipelineConfigs.CFBaseUrl, pipelineConfigs.CFUserName, pipelineConfigs.CFPassword, pipelineConfigs.CFOauthTokenUrl); + private readonly HSPIAMBusinessLayer _iamBL = new HSPIAMBusinessLayer(); + + private const string _permission = "BASIC.READ"; + private static string _permissionsJson = "{\"permissions\":[\"PermissionPlaceHolder\"]}".Replace("PermissionPlaceHolder", _permission); + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APIGetCallWithUserAccessTokenTest() + { + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + APIGetCallWithAccessToken(userAccessTokenHeader); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APICallsWithMultipleUserAccessTokenAndVerifySpecificAccessTokenIntrospectValueTest() + { + var userAccessTokenHeader1 = CreateUserAccessTokenHeader(); + var userAccessTokenHeader2 = CreateUserAccessTokenHeader(); + var userAccessTokenHeader3 = CreateUserAccessTokenHeader(); + var userAccessTokenHeader4 = CreateUserAccessTokenHeader(); + var userAccessTokenHeader5 = CreateUserAccessTokenHeader(); + + APICallsWithMultipleAccessTokenAndVerifySpecificAccessTokenIntrospectValue(userAccessTokenHeader1, userAccessTokenHeader2, userAccessTokenHeader3, userAccessTokenHeader4, userAccessTokenHeader5); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void CRUDApiCallWithUserAccessTokenTest() + { + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + CRUDApiCallWithAccessToken(userAccessTokenHeader); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APIGetCallWithInvalidAccessTokenTest() + { + Report.Step(@"API call with access token", @"Should get the 401 Unauthorized response"); + var invalidAccessTokenHeader = new Dictionary<string, string>(); + invalidAccessTokenHeader.Add("Authorization", $"Bearer {Guid.NewGuid().ToString()}"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, invalidAccessTokenHeader, null).Result; + AssertTest.IsTrue(response.StatusCode == HttpStatusCode.Unauthorized, failMsg: "Not received HttpStatusCode.Unauthorized", passMsg: "Received HttpStatusCode.Unauthorized"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APIGetCallWithRefreshTokenTest() + { + Report.Step(@"API call with refresh token", @"Should get the 401 Unauthorized response"); + var userAccessTokenHeader = CreateUserAccessTokenHeader("refresh_token"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, userAccessTokenHeader, null).Result; + AssertTest.IsTrue(response.StatusCode == HttpStatusCode.Unauthorized, failMsg: "Not received HttpStatusCode.Unauthorized", passMsg: "Received HttpStatusCode.Unauthorized"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void ChangePermissionInRoleAndVerifyUserAccessTokenPermissionsBeforeRedisCacheTimeoutTest() + { + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + + AddPermissionInRole(userAccessTokenHeader, _permission); + + ChangePermissionInRoleAndVerifyAccessTokenPermissionsBeforeRedisCacheTimeoutTest(userAccessTokenHeader, _permission); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestMethod] + public void ChangePermissionInRoleAndVerifyUserAccessTokenPermissionsAfterRedisCacheTimeoutTest() + { + //By default Authenticator_session__SessionExpireOffsetInPercent is 10% and updating to 99%, so that the redis cache will clear in 18 seconds + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + + AddPermissionInRole(userAccessTokenHeader, _permission); + + ChangePermissionInRoleAndVerifyAccessTokenPermissionsAfterRedisCacheTimeoutTest(userAccessTokenHeader, _permission); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APIGetCallWithUserAccessTokenWithNoPermissionTest() + { + var accessTokenHeader = CreateUserAccessTokenHeader(); + APIGetCallWithAccessTokenWithNoPermission(accessTokenHeader); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.UserAccessToken))] + [TestMethod] + public void StopAccessTokenAuthenticatorAppAndMakeAPICallWithUserAccessTokenTest() + { + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + StopAccessTokenAuthenticatorAppAndMakeAPICallWithAccessTokenTest(userAccessTokenHeader); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APIGetCallWithServiceIDAccessTokenTest() + { + var serviceIDAccessTokenHeader = CreateServiceIDAccessTokenHeader(); + APIGetCallWithAccessToken(serviceIDAccessTokenHeader); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void CRUDApiCallWithServiceIDAccessTokenTest() + { + var serviceIDAccessTokenHeader = CreateServiceIDAccessTokenHeader(); + CRUDApiCallWithAccessToken(serviceIDAccessTokenHeader); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APICallsWithMultipleServiceIDAccessTokenAndVerifySpecificAccessTokenIntrospectValueTest() + { + var serviceIDAccessTokenHeader1 = CreateServiceIDAccessTokenHeader(); + var serviceIDAccessTokenHeader2 = CreateServiceIDAccessTokenHeader(); + var serviceIDAccessTokenHeader3 = CreateServiceIDAccessTokenHeader(); + var serviceIDAccessTokenHeader4 = CreateServiceIDAccessTokenHeader(); + var serviceIDAccessTokenHeader5 = CreateServiceIDAccessTokenHeader(); + + APICallsWithMultipleAccessTokenAndVerifySpecificAccessTokenIntrospectValue(serviceIDAccessTokenHeader1, serviceIDAccessTokenHeader2, serviceIDAccessTokenHeader3, serviceIDAccessTokenHeader4, serviceIDAccessTokenHeader5); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void ChangePermissionInRoleAndVerifyServiceIDAccessTokenPermissionsBeforeRedisCacheTimeoutTest() + { + var serviceIDAccessTokenHeader = CreateServiceIDAccessTokenHeader(); + + AddPermissionInRole(serviceIDAccessTokenHeader, _permission); + + ChangePermissionInRoleAndVerifyAccessTokenPermissionsBeforeRedisCacheTimeoutTest(serviceIDAccessTokenHeader, _permission); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestMethod] + public void ChangePermissionInRoleAndVerifyServiceIDAccessTokenPermissionsAfterRedisCacheTimeoutTest() + { + //By default Authenticator_session__SessionExpireOffsetInPercent is 10% and updating to 99%, so that the redis cache will clear in 18 seconds + var serviceIDAccessTokenHeader = CreateServiceIDAccessTokenHeader(); + + AddPermissionInRole(serviceIDAccessTokenHeader, _permission); + + ChangePermissionInRoleAndVerifyAccessTokenPermissionsAfterRedisCacheTimeoutTest(serviceIDAccessTokenHeader, _permission); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APIGetCallWithServiceIDAccessTokenWithNoPermissionTest() + { + var accessTokenHeader = CreateServiceIDAccessTokenHeader(); + APIGetCallWithAccessTokenWithNoPermission(accessTokenHeader); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.ServiceIDAccessToken))] + [TestMethod] + public void StopAccessTokenAuthenticatorAppAndMakeAPICallWithServiceIDAccessTokenTest() + { + var serviceIDAccessTokenHeader = CreateServiceIDAccessTokenHeader(); + StopAccessTokenAuthenticatorAppAndMakeAPICallWithAccessTokenTest(serviceIDAccessTokenHeader); + } + + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestMethod] + public async Task HttpToHttpsRedirectionUsingAPIGatewayTest() + { + var apiGatewayUrl = pipelineConfigs.APIGatewayBaseUrl.Replace("https","http"); + AssertTest.IsFalse(apiGatewayUrl.Contains("https"), failMsg: "Invalid Url", passMsg: "Valid Url"); + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + + var response = await HttpClientUtility.ExecuteAsyncWithoutHttpRedirection(HttpMethod.Get,apiGatewayUrl+appConfigs.LessPayloadMockservice, userAccessTokenHeader,null); + Assert.AreEqual(HttpStatusCode.MovedPermanently, response.StatusCode); + + var redirectedUrl = response.Headers.Location.AbsoluteUri; + var redirectedResponse = HttpClientUtility.ExecuteAndGetResponse(response.RequestMessage.Method, redirectedUrl, userAccessTokenHeader, null); + AssertTest.IsTrue(redirectedResponse != null && redirectedResponse["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + + } + + + #region Private methods + + private void APIGetCallWithAccessToken(Dictionary<string, string> accessTokenHeader) + { + Report.Step(@"API call with access token", @"Should get the valid upstream response"); + + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, accessTokenHeader, null); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + } + + private void CRUDApiCallWithAccessToken(Dictionary<string, string> accessTokenHeader) + { + Report.Step(@"GET API call with access token", @"Should get the valid upstream response"); + var getResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, accessTokenHeader, null); + AssertTest.IsTrue(getResponseBody != null && getResponseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + + + Report.Step(@"POST API call with access token", @"Should post successfully"); + var content = HttpClientUtility.CreateHttpContent(getResponseBody.ToString()); + var postResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Post, defaultEndpointUrl, accessTokenHeader, content); + AssertTest.IsTrue(postResponse != null && postResponse["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + + + Report.Step(@"PUT API call with access token", @"Should update successfully"); + string _heavyPayloadMockServiceUrl = $"{pipelineConfigs.APIGatewayBaseUrl}{ appConfigs.HeavyPayloadMockservice}"; + getResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, _heavyPayloadMockServiceUrl, accessTokenHeader, null); + content = HttpClientUtility.CreateHttpContent(getResponseBody.ToString()); + var putResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Put, defaultEndpointUrl, accessTokenHeader, content); + AssertTest.IsTrue(putResponse != null && putResponse["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + + + Report.Step(@"DELETE API call with access token", @"Should delete successfully"); + var deleteResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Delete, _heavyPayloadMockServiceUrl, accessTokenHeader, null); + AssertTest.IsTrue(deleteResponse != null && deleteResponse["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + } + + private void ChangePermissionInRoleAndVerifyAccessTokenPermissionsBeforeRedisCacheTimeoutTest(Dictionary<string, string> accessTokenHeader, string permission) + { + try + { + APIGetCallAndRemoveAccessTokenPermissionInRole(accessTokenHeader, permission); + + List<JToken> permissionsFromIntrospectValue = APICallAndGetPermissionsFromIntrospectValue(accessTokenHeader); + + Report.Step(@"Access token permissions should not change before redis cache timeout", + @"The permissions should not be changed"); + AssertTest.IsTrue(permissionsFromIntrospectValue.Contains(permission), failMsg: $"Permission: {permission} removed is updated in redis cache", passMsg: $"Permission: {permission} removed is not updated in redis cache"); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + finally + { + AddPermissionInRole(accessTokenHeader, permission); + } + } + + private void ChangePermissionInRoleAndVerifyAccessTokenPermissionsAfterRedisCacheTimeoutTest(Dictionary<string, string> userAccessTokenHeader, string permission) + { + Report.Step($"Create/Update the Environment variable Authenticator_session__SessionExpireOffsetInPercent={pipelineConfigs.AuthenticatorSessionExpireOffsetInPercent}, for access token authenticator application in CF", + @"Environment variable should get created/updated"); + + var response = _cfUtility.UpdateEnvironmentVariablesToCFApp(pipelineConfigs.CFOrgName, pipelineConfigs.CFSpaceName, pipelineConfigs.CFAuthenticatorAppName, + new Dictionary<string, string> { { Constants.AuthenticatorSessionExpireOffsetInPercent, pipelineConfigs.AuthenticatorSessionExpireOffsetInPercent } }); + AssertTest.IsTrue(response[Constants.AuthenticatorSessionExpireOffsetInPercent] == pipelineConfigs.AuthenticatorSessionExpireOffsetInPercent, failMsg: "Env variable create/update failed", passMsg: "Env variable created/updated successfully"); + + + try + { + APIGetCallAndRemoveAccessTokenPermissionInRole(userAccessTokenHeader, permission); + + //IAM Access Token timeout 30min (1800 seconds) + int cacheClearWaitTimeInSeconds = (int)Math.Round(1800 - (1800 * (Convert.ToDouble(pipelineConfigs.AuthenticatorSessionExpireOffsetInPercent) / 100))); + cacheClearWaitTimeInSeconds += 10; //(10 seconds buffer) + Report.Step($"Wait for { cacheClearWaitTimeInSeconds} seconds to get Redis cache clear", ""); + Sleep.Seconds(cacheClearWaitTimeInSeconds); + + var permissionsFromIntrospectValue = APICallAndGetPermissionsFromIntrospectValue(userAccessTokenHeader); + Report.Step(@"Access token permissions should change after redis cache timeout", + @"The permissions should be changed"); + AssertTest.IsTrue(!permissionsFromIntrospectValue.Contains(permission), failMsg: $"Permission: {permission} removed, is not updated in redis cache", passMsg: $"Permission: { permission} removed, is updated in redis cache"); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + finally + { + Report.Step("Executing the Finally Block", "Finally Block should get executed"); + AddPermissionInRole(userAccessTokenHeader, permission); + _cfUtility.UpdateEnvironmentVariablesToCFApp(pipelineConfigs.CFOrgName, pipelineConfigs.CFSpaceName, pipelineConfigs.CFAuthenticatorAppName, + new Dictionary<string, string> { { Constants.AuthenticatorSessionExpireOffsetInPercent, "10" } }); + } + } + + private void StopAccessTokenAuthenticatorAppAndMakeAPICallWithAccessTokenTest(Dictionary<string, string> userAccessTokenHeader) + { + Report.Step(@"Stop the access token authenticator application", @"Access token authenticator application should be stopped"); + AssertTest.IsTrue(_cfUtility.ChangingCFAppState(pipelineConfigs.CFOrgName, pipelineConfigs.CFSpaceName, pipelineConfigs.CFAuthenticatorAppName, AppState.stop), failMsg: "Failed to stop application", passMsg: "Application stopped successfully"); + + try + { + Report.Step(@"API call with access token", @"Should get the 403 forbidden response"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, userAccessTokenHeader, null).Result; + AssertTest.IsTrue(response.StatusCode == HttpStatusCode.Forbidden, failMsg: $"Failed to get 403 Forbidden status code, Actual: {response.StatusCode}", passMsg: "Recieved 403 Forbidden status code"); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + finally + { + Report.Step(@"Start the access token authenticator application", @"Access token authenticator application should be started"); + AssertTest.IsTrue(_cfUtility.ChangingCFAppState(pipelineConfigs.CFOrgName, pipelineConfigs.CFSpaceName, pipelineConfigs.CFAuthenticatorAppName, AppState.start), failMsg: "Failed to start application", passMsg: "Application started successfully"); + } + } + + private List<JToken> APICallAndGetPermissionsFromIntrospectValue(Dictionary<string, string> userAccessTokenHeader) + { + Report.Step(@"Another API call with the same access token", @"Should get the valid upstream response"); + string customUniqueRequestHeaderValue = Guid.NewGuid().ToString(); + userAccessTokenHeader.Add(Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, userAccessTokenHeader, null); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received success response"); + + + Report.Step(@"Get the recorded upstream call request from mountebank mockservice", + @"edisp-introspect-value should be available in the upstream request headers"); + CommonBL commonBL = new CommonBL(); + var getResponseBody = commonBL.GetImposterRecordedRequests(pipelineConfigs.APIGatewayBaseUrl, userAccessTokenHeader, "LessPayloadMockservice.json"); + var headers = getResponseBody["requests"].Where(x => x["headers"][Constants.CustomUniqueRequestHeaderName] != null && x["headers"][Constants.CustomUniqueRequestHeaderName].ToString() == customUniqueRequestHeaderValue).FirstOrDefault(); + var introspectEncodedValue = headers["headers"]["edisp-introspect-value"].ToString(); + AssertTest.IsTrue(!string.IsNullOrWhiteSpace(introspectEncodedValue), failMsg: "edisp-introspect-value is not found in upstream request headers", passMsg: "edisp-introspect-value is available in upstream request headers"); + + + byte[] data = Convert.FromBase64String(introspectEncodedValue); + string decodedString = Encoding.UTF8.GetString(data); + var permissionsFromIntrospectValue = JObject.Parse(decodedString)["organizations"]["organizationList"].Where(x => x["organizationId"].ToString() == tenantDetails[Constants.AccessTokenTenantKey].IamOrganizationId).FirstOrDefault()["permissions"].ToList(); + return permissionsFromIntrospectValue; + } + + private void APIGetCallAndRemoveAccessTokenPermissionInRole(Dictionary<string, string> accessTokenHeader, string permission) + { + Report.Step(@"API call with access token", @"Should get the valid upstream response"); + if (accessTokenHeader.ContainsKey("api-version")) + accessTokenHeader.Remove("api-version"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, accessTokenHeader, null); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: $"No Success response received, Response Body: '{responseBody}'", passMsg: "Received success response"); + + RemovePermissionInRole(accessTokenHeader, permission); + } + + private bool RemovePermissionInRole(Dictionary<string, string> accessTokenHeader, string permission) + { + Report.Step(@"Capture persmissions from Role in IAM for the access token", @"Should get all the permissions captured from Role in IAM"); + string roleId = _iamBL.GetRoleId(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, tenantDetails[Constants.AccessTokenTenantKey].IamOrganizationId, pipelineConfigs.AccessTokenTestRoleName); + List<string> capturedAllPermissions = _iamBL.GetAllPersmissionsFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId); + AssertTest.IsTrue(capturedAllPermissions.Count > 0, failMsg: "No permission catpured from role", passMsg: "Captured all persmission from role"); + + + if (capturedAllPermissions.Contains(permission)) + { + Report.Step(@"Remove permissions in Role", @"Permissions should be removed in the Role"); + _iamBL.RemovePermissionFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId, _permissionsJson); + Logger.Info($"Remove Permission: {permission}"); + bool isPermissionRemoved = !_iamBL.GetAllPersmissionsFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId).Contains(permission); + AssertTest.IsTrue(isPermissionRemoved, failMsg: $"Permission: {permission} not removed", passMsg: $"Permission: {permission} removed successfully"); + return isPermissionRemoved; + } + else + { + AssertTest.IsTrue(!capturedAllPermissions.Contains(permission), failMsg: $"Permission: {permission} exist ", passMsg: $"Permission: {permission} already not exists"); + return true; + } + } + + private bool AddPermissionInRole(Dictionary<string, string> accessTokenHeader, string permission) + { + Report.Step(@"Capture persmissions from Role in IAM for the access token", @"Should get all the permissions captured from Role in IAM"); + + string roleId = _iamBL.GetRoleId(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, tenantDetails[Constants.AccessTokenTenantKey].IamOrganizationId, pipelineConfigs.AccessTokenTestRoleName); + List<string> capturedAllPermissions = _iamBL.GetAllPersmissionsFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId); + AssertTest.IsTrue(capturedAllPermissions.Count > 0, failMsg: "No permission catpured from role", passMsg: "Captured all persmission from role"); + + + Report.Step(@"Add permissions in Role", @"Permissions should be added in the Role"); + if (!capturedAllPermissions.Contains(permission)) + { + _iamBL.AssignPermissionToRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId, _permissionsJson); + Logger.Info($"Assigned Permission: {permission}"); + bool isPermssionAdded = _iamBL.GetAllPersmissionsFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId).Contains(permission); + AssertTest.IsTrue(isPermssionAdded, failMsg: $"Permission: {permission} not added", passMsg: $"Permission: {permission} added successfully"); + return isPermssionAdded; + } + else + { + AssertTest.IsTrue(capturedAllPermissions.Contains(permission), failMsg: $"Permission: {permission} not exist", passMsg: $"Permission: {permission} already exists"); + return true; + } + } + + private Dictionary<string, string> CreateUserAccessTokenHeader(string tokenType = "access_token") + { + var userAccessTokenHeader = HttpClientUtility.CreateUserAccessTokenHeader(pipelineConfigs,tokenType); + AssertTest.IsTrue(userAccessTokenHeader != null, failMsg: "User Access Token header is null", passMsg: $"User Access Token header is not null"); + return userAccessTokenHeader; + } + + private Dictionary<string, string> CreateServiceIDAccessTokenHeader() + { + var serviceIDAccessTokenHeader = HttpClientUtility.CreateServiceIdAccessTokenHeader(pipelineConfigs); + AssertTest.IsTrue(serviceIDAccessTokenHeader != null, failMsg: "ServiceID Access Token header is null", passMsg: $"ServiceID Access Token header is not null:{serviceIDAccessTokenHeader}"); + return serviceIDAccessTokenHeader; + } + + private void APIGetCallWithAccessTokenWithNoPermission(Dictionary<string, string> accessTokenHeader) + { + Report.Step(@"Check and remove permissions in Role if exists", @"Permissions should be remove if exist in the Role"); + HSPIAMBusinessLayer iamBL = new HSPIAMBusinessLayer(); + string roleId = iamBL.GetRoleId(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, tenantDetails[Constants.AccessTokenTenantKey].IamOrganizationId, pipelineConfigs.AccessTokenTestRoleName); + var allPermissions = iamBL.GetAllPersmissionsFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId); + if (allPermissions.Contains(_permission)) + { + bool permissionRemovedStatus = iamBL.RemovePermissionFromRole(pipelineConfigs.IDMClientBaseUrl, accessTokenHeader, roleId, _permissionsJson); + AssertTest.IsTrue(permissionRemovedStatus, failMsg: "Failed to removed the permission {_permission} in role", passMsg: $"Removed permission {_permission} in role"); + } + + + Report.Step(@"API call with access token", @"Should get the 403 Forbidden response"); + string checkPermissionMockserviceUrl = $"{defaultEndpointUrl}/checkpermission"; + accessTokenHeader.Add("edisp-org-id", tenantDetails[Constants.AccessTokenTenantKey].IamOrganizationId); + accessTokenHeader.Add("permission-name", _permission); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, checkPermissionMockserviceUrl, accessTokenHeader, null).Result; + AssertTest.IsTrue(response.StatusCode == HttpStatusCode.Forbidden, failMsg: $"Did not receive 403 Forbidden response, Actual: {response.StatusCode}", passMsg: "Received 403 Forbidden response"); + } + + private void APICallsWithMultipleAccessTokenAndVerifySpecificAccessTokenIntrospectValue(Dictionary<string, string> accessTokenHeader1, Dictionary<string, string> accessTokenHeader2, Dictionary<string, string> accessTokenHeader3, Dictionary<string, string> userAccessTokenHeader4, Dictionary<string, string> userAccessTokenHeader5) + { + AddPermissionInRole(accessTokenHeader1, _permission); + + Report.Step(@"2 API calls with first 2 different access token", @"Should get the valid upstream response for all 2 api calls"); + APIGetCallWithAccessToken(accessTokenHeader1); + APIGetCallWithAccessToken(accessTokenHeader2); + + + Report.Step(@"Remove permission with the access token", @"Permissions should be removed in Role"); + RemovePermissionInRole(accessTokenHeader3, _permission); + + Sleep.Seconds(3); + Report.Step(@"API call with 3rd access token", @"Should get the valid upstream response for the 3rd api call"); + APIGetCallWithAccessToken(accessTokenHeader3); + + + Report.Step(@"Revert the permission which earlier did with access token", @"Permissions should be reverted in Role"); + RemovePermissionInRole(accessTokenHeader3, _permission); + + + Report.Step(@"API calls with last 2 access token", @"Should get the valid upstream response for the last 2 api calls"); + APIGetCallWithAccessToken(userAccessTokenHeader4); + APIGetCallWithAccessToken(userAccessTokenHeader5); + + var introspectValuePermissions = APICallAndGetPermissionsFromIntrospectValue(accessTokenHeader3); + + Report.Step(@"Should get the correct introspect value from Redis cache for the given access token", + @"Should get the permission removed introspect value from Redis"); + AssertTest.IsTrue(!introspectValuePermissions.Contains(_permission), failMsg: "Failed to get the correct introspect value from Reids", passMsg: "Got the correct introspect value from Reids"); + } + + #endregion + } +} diff --git a/AutomationTest/Tests/APITests/IntrospectionTests/IntrospectionTests.cs b/AutomationTest/Tests/APITests/IntrospectionTests/IntrospectionTests.cs new file mode 100644 index 00000000..45a27da1 --- /dev/null +++ b/AutomationTest/Tests/APITests/IntrospectionTests/IntrospectionTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Collections.Generic; +using Driver.UI.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests.IntrospectionTests +{ + [TestClass] + public class IntrospectionTests : BaseTest + { + private Cookies _cookie; + private Dictionary<string, string> _headers; + + [TestInitialize] + public void BeforeTest() + { + Logger.Info("Before test"); + CommonSteps commonSteps = new CommonSteps(); + commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + Report.Step(@"Get the cookie from browser", @"Should get the valid cookie"); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + _cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + _headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); ; + AssertTest.IsTrue(_cookie != null, failMsg: "Cookie is not available", passMsg: $"Cookie is available, Cookie value: {_cookie.CookieValue}"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void IntrospectValueToUpstreamRequestHeaderTest() + { + Report.Step(@"API call to Less payload mockservice and make an imposters api call with mockservice port number to get the recorded upstream request headers from API Gateway", + @"edisp-introspect-value should be available in the upstream request headers"); + string customUniqueRequestHeaderValue = Guid.NewGuid().ToString(); + _headers.Add(Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue); + Logger.Info($"Mockservice Url: { defaultEndpointUrl}"); + _ = HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, _headers, null); + CommonBL commonBL = new CommonBL(); + var getResponseBody = commonBL.GetImposterRecordedRequests(pipelineConfigs.APIGatewayBaseUrl, _headers, "LessPayloadMockservice.json"); + var isRecordRequest = (bool)getResponseBody["recordRequests"]; + AssertTest.IsTrue(isRecordRequest, failMsg: "Record requests is not enabled in Less payload mockservice imposter", passMsg: "Record request is enabled in Less payload mockservice imposter"); + var testHeaderRequest = getResponseBody["requests"].Where(x => x["headers"][Constants.CustomUniqueRequestHeaderName] != null && x["headers"][Constants.CustomUniqueRequestHeaderName].ToString() == customUniqueRequestHeaderValue).FirstOrDefault(); + var testHeaderValue = testHeaderRequest["headers"][Constants.CustomUniqueRequestHeaderName].ToString(); + var introspectValue = testHeaderRequest["headers"]["edisp-introspect-value"].ToString(); + AssertTest.IsTrue(!string.IsNullOrWhiteSpace(testHeaderValue) && !string.IsNullOrWhiteSpace(introspectValue), failMsg: "edisp-introspect-value is not found in upstream request headers", passMsg: "edisp-introspect-value is available in upstream request headers"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void IntrospectValueFromUpstreamResponseHeaderTest() + { + Report.Step(@"GET API call to the CDR service with cookie", + @"edisp-introspect-value should be available in the upstream response headers and Authorization should not be available in the upstream response header"); + string cdrServiceUrl = $"{pipelineConfigs.APIGatewayBaseUrl}{appConfigs.CDRSubscriptionUrlPath.Replace("OrgId", tenantDetails[Constants.ValidTenantKey].TenantName)}"; + Logger.Info($"CDR service Url: {cdrServiceUrl}"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, cdrServiceUrl, _headers, null).Result; + AssertTest.IsTrue(response != null && response.Headers.Contains("edisp-introspect-value") && !string.IsNullOrWhiteSpace(response.Headers.GetValues("edisp-introspect-value").FirstOrDefault()), + failMsg: "edisp-introspect-value is not found in upstream response headers", passMsg: "edisp-introspect-value is available in upstream request headers"); + AssertTest.IsTrue(response != null && !response.Headers.Contains("Authorization"), + failMsg: "'Authorization' header found in upstream response headers", passMsg: "Authorization header is not available in upstream response headers"); + } + } +} diff --git a/AutomationTest/Tests/APITests/MultitenancyStaticConfigurationTests/MultitenancyStaticConfigurationTests.cs b/AutomationTest/Tests/APITests/MultitenancyStaticConfigurationTests/MultitenancyStaticConfigurationTests.cs new file mode 100644 index 00000000..367108b1 --- /dev/null +++ b/AutomationTest/Tests/APITests/MultitenancyStaticConfigurationTests/MultitenancyStaticConfigurationTests.cs @@ -0,0 +1,143 @@ +using Driver.UI.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Utilities; +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests.MultitenancyStaticConfiguration +{ + [TestClass] + public class MultitenancyStaticConfigurationTests : BaseTest + { + + private string placeHolderForOrgReplacement; + + private Cookies _cookie; + private Dictionary<string, string> _headers; + private static string _apiGatewayBaseUrlWithOrgNamePlaceholder; + private static string _cdrServiceUrlBasePathWithoutOrgId; + private string _cdrSubscriptionUrlWithOrg1; + private string _cdrSubscriptionUrlWithInvalidOrgName; + private string _qidoStudyLevelUrlWithInvalidOrgName; + + + [TestInitialize] + public void BeforeTest() + { + Logger.Info("Before test"); + UriBuilder uriBuilder = new UriBuilder(pipelineConfigs.APIGatewayBaseUrl); + placeHolderForOrgReplacement = $"OrgName-{uriBuilder.Host.Split('.')[0]}"; + uriBuilder.Host = $"OrgName-{uriBuilder.Host}"; + _apiGatewayBaseUrlWithOrgNamePlaceholder = uriBuilder.Uri.AbsoluteUri; + _cdrServiceUrlBasePathWithoutOrgId = appConfigs.CDRImagingStudyUrlPath.Replace("/OrgId", string.Empty); + _cdrSubscriptionUrlWithOrg1 = $"{_apiGatewayBaseUrlWithOrgNamePlaceholder.Replace(placeHolderForOrgReplacement, tenantDetails[Constants.ValidTenantKey].TenantName, StringComparison.InvariantCultureIgnoreCase)}{_cdrServiceUrlBasePathWithoutOrgId}"; + _cdrSubscriptionUrlWithInvalidOrgName = $"{_apiGatewayBaseUrlWithOrgNamePlaceholder.Replace(placeHolderForOrgReplacement, tenantDetails[Constants.InvalidTenantKey].TenantName, StringComparison.InvariantCultureIgnoreCase)}{_cdrServiceUrlBasePathWithoutOrgId}"; + _qidoStudyLevelUrlWithInvalidOrgName = $"{_apiGatewayBaseUrlWithOrgNamePlaceholder.Replace(placeHolderForOrgReplacement, tenantDetails[Constants.InvalidTenantKey].TenantName, StringComparison.InvariantCultureIgnoreCase)}{appConfigs.QidoStudyLevelUrlPathWithoutOrgId}"; + + Report.Step(@"Browse endpoint url and get the cookie from browser", @"Should get the valid cookie"); + LoginToEndpoint(_apiGatewayBaseUrlWithOrgNamePlaceholder.Replace(placeHolderForOrgReplacement, tenantDetails[Constants.ValidTenantKey].TenantName, StringComparison.InvariantCultureIgnoreCase), pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + _cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + _headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + AssertTest.IsTrue(_cookie != null, failMsg: "Cookie is not available", passMsg: $"Cookie is available, Cookie value: {_cookie.CookieValue}"); + } + + [TestCategory(nameof(TestCategory.OrgNameInUrl))] + [TestMethod] + public void APICallWithValidTenantAsSubDomainTest() + { + Report.Step(@"GET API call to the CDR service with Org Name(org1) as sub domain in Url", @"Should get the valid CDR service response"); + CDRGetCallAndAssert(_cdrSubscriptionUrlWithOrg1); + } + + [TestCategory(nameof(TestCategory.OrgNameInUrl))] + [TestMethod] + public void APICallWithValidTenantAsSubDomainAndVerifyOrgIdInUpstreamUrlTest() + { + //pathMap= {["/mockserviceA/multitenancy"] = "/mockserviceA/multitenancy/orgId"} should be added in of global lua filter in envoy conifg + + Report.Step(@"API call to Less payload mockservice with Org Name (org1) and verify the OrgId in upstream url", @"The upstream url should have org1 orgId"); + var customUniqueRequestHeaderValue = Guid.NewGuid().ToString(); + _headers.Add(Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue); + string baseUrl = _apiGatewayBaseUrlWithOrgNamePlaceholder.Replace(placeHolderForOrgReplacement, tenantDetails[Constants.ValidTenantKey].TenantName, StringComparison.InvariantCultureIgnoreCase); + string multitenancyMockserviceUrl = $"{ baseUrl }{ appConfigs.MultitenancyMockservice}"; + Logger.Info($"Mockservice Url: {multitenancyMockserviceUrl}"); + _ = HttpClientUtility.ExecuteAsync(HttpMethod.Get, multitenancyMockserviceUrl, _headers, null); + CommonBL commonBL = new CommonBL(); + _headers.Remove(Constants.CustomUniqueRequestHeaderName); + var upstreamRequestUrl = commonBL.GetUpstreamRecordedRequest(baseUrl, _headers, Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue, "LessPayloadMockservice.json"); + string reqUrl = upstreamRequestUrl["path"].ToString(); + Logger.Info($"Upstream request url: { reqUrl}"); + AssertTest.IsTrue(upstreamRequestUrl != null && reqUrl.EndsWith(tenantDetails[Constants.ValidTenantKey].IamOrganizationId, StringComparison.InvariantCultureIgnoreCase), + failMsg: $"OrgId is not found in upstream request headers for OrgName: {tenantDetails[Constants.ValidTenantKey].TenantName}", passMsg: $"OrgId is available in upstream request headers for the OrgName: {tenantDetails[Constants.ValidTenantKey].TenantName}"); + } + + [TestCategory(nameof(TestCategory.OrgNameInUrl))] + [TestMethod] + public void APICallWithInValidTenantAsSubDomainAndOrgNameNotInStaticConfigurationTest() + { + Report.Step(@"GET API call to the CDR service with invalid Org Name as sub domain which is not in static configuration", @"Should get the 404 error response"); + Logger.Info($"CDR service invalid org name Url: {_cdrSubscriptionUrlWithInvalidOrgName}"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, _cdrSubscriptionUrlWithInvalidOrgName, _headers, null).Result; + AssertTest.IsTrue(!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.NotFound, + failMsg: "Failed to get 404 status code for the CDR service response", passMsg: "Received the 404 status code for the CDR service response"); + } + + [TestCategory(nameof(TestCategory.OrgNameInUrl))] + [TestMethod] + public void APICallWithInvalidTenantAsSubDomainAndInvalidOrgIdInHeaderTest() + { + Report.Step(@"GET API call to the CDR service with Org Name(org1) as sub domain in Url and invalid OrgId in headers", @"Should get the valid CDR service response"); + _headers.Add("edisp-org-id", tenantDetails[Constants.InvalidTenantKey].IamOrganizationId); + Logger.Info($"Invalid OrgId: {tenantDetails[Constants.InvalidTenantKey].IamOrganizationId}"); + CDRGetCallAndAssert(_cdrSubscriptionUrlWithOrg1); + } + + [TestCategory(nameof(TestCategory.OrgIdInHeader))] + [TestMethod] + public void APICallWithValidOrgIdInHeadersTest() + { + Report.Step(@"GET API call to the CDR service with valid OrgId in header", @"Should get the valid CDR service response"); + _headers.Add("edisp-org-id", tenantDetails[Constants.ValidTenantKey].IamOrganizationId); + Logger.Info($"Valid OrgId: {tenantDetails[Constants.ValidTenantKey].TenantName}"); + CDRGetCallAndAssert(_cdrSubscriptionUrlWithOrg1); + } + + [TestCategory(nameof(TestCategory.OrgIdInHeader))] + [TestMethod] + public void APICallWithInValidOrgIdInHeaderTest() + { + Report.Step(@"GET API call to the dicom service with invalid OrgId in header", @"Should get the 403 error response"); + _headers.Add("edisp-org-id", tenantDetails[Constants.InvalidTenantKey].IamOrganizationId); + Logger.Info($"Qido study level Url: {_qidoStudyLevelUrlWithInvalidOrgName}"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, _qidoStudyLevelUrlWithInvalidOrgName, _headers, null).Result; + AssertTest.IsTrue(!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.Forbidden, + failMsg: "Failed to get 403 status code in response for the Qido call with invalid OrgId in header", passMsg: "Received the 403 status code response for the qido call with invalid Orgid in header "); + } + + [TestCategory(nameof(TestCategory.OrgIdInHeader))] + [TestMethod] + public void APICallWithoutOrgIdInHeaderTest() + { + Report.Step(@"GET API call to the dicom service without OrgId in header", @"Should get the 404 error response"); + Logger.Info($"Qido study level Url: {_qidoStudyLevelUrlWithInvalidOrgName}"); + var response = HttpClientUtility.ExecuteAsync(HttpMethod.Get, _qidoStudyLevelUrlWithInvalidOrgName, _headers, null).Result; + AssertTest.IsTrue(!response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.NotFound, + failMsg: "Failed to get 404 status code in response for the Qido call without OrgId in header", passMsg: "Received the 404 status code response for the qido call without Orgid in header "); + } + + private void CDRGetCallAndAssert(string _crdSubscriptionUrl) + { + Logger.Info($"CDR subscription Url: {_crdSubscriptionUrl}"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, _crdSubscriptionUrl, _headers, null); + AssertTest.IsTrue(responseBody != null && responseBody.ContainsKey("resourceType") && responseBody["resourceType"].ToString().EqualsWithIgnoreCase("Bundle"), + failMsg: "Failed to get the CDR service success response", passMsg: "Received the CDR service success response"); + } + } +} diff --git a/AutomationTest/Tests/APITests/RequestTransformationHeadersTests/RequestTransformationHeadersTests.cs b/AutomationTest/Tests/APITests/RequestTransformationHeadersTests/RequestTransformationHeadersTests.cs new file mode 100644 index 00000000..3d01f76b --- /dev/null +++ b/AutomationTest/Tests/APITests/RequestTransformationHeadersTests/RequestTransformationHeadersTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using Driver.UI.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests.RequestTransformationHeadersTests +{ + [TestClass] + public class RequestTransformationHeadersTests : BaseTest + { + private Cookies _cookie; + private Dictionary<string, string> _headers; + + + [TestInitialize] + public void BeforeTest() + { + Logger.Info("Before test"); + CommonSteps _commonSteps = new CommonSteps(); + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + Report.Step(@"Get the cookie from browser", @"Should get the valid cookie"); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + _cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + _headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + AssertTest.IsTrue(_cookie != null, failMsg: "Cookie is not available", passMsg: $"Cookie is available, Cookie value: {_cookie.CookieValue}"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void CustomHeaderAddedToUpstreamRequestByAPIGatewayTest() + { + //custom-header:"100" should be added to less payload mockserviceA in envoy config + + Report.Step(@"API call to Less payload mockservice and make an imposters api call with mockservice port number to get the recorded upstream request from API Gateway", + @"custom-header should be available in the upstream request headers"); + Logger.Info($"Mockservice Url: {defaultEndpointUrl}"); + _ = HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, _headers, null); + CommonBL commonBL = new CommonBL(); + var getResponseBody = commonBL.GetImposterRecordedRequests(pipelineConfigs.APIGatewayBaseUrl, _headers, "LessPayloadMockservice.json"); + var isRecordRequest = (bool)getResponseBody["recordRequests"]; + AssertTest.IsTrue(isRecordRequest, failMsg: "Record requests is not enabled in Less payload mockservice imposter", passMsg: "Record request is enabled in Less payload mockservice imposter"); + var customHeaderRequest = getResponseBody["requests"].Where(x => x["headers"]["custom-header"] != null && x["headers"]["custom-header"].ToString() == "100").FirstOrDefault(); + + AssertTest.IsTrue(customHeaderRequest != null, failMsg: "custom-header is not found in upstream request headers", passMsg: "custom-header is available in upstream request headers"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void PrefixReWriteTest() + { + //Make sure envoy config should have prefix:"/prefixrewritetest" and prefix_rewrite:"/mockserviceA" + + Report.Step(@"API call with /prefixrewritetest/test url path", @"Url should be updated automatically to /mockserviceA/test and get the upstream response"); + var customUniqueRequestHeaderValue = Guid.NewGuid().ToString(); + _headers.Add(Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue); + string prefixReWriteMockserviceUrl = $"{ pipelineConfigs.APIGatewayBaseUrl}/prefixrewritetest/test"; + Logger.Info($"Mockservice Url: {prefixReWriteMockserviceUrl}"); + var upstreamResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, prefixReWriteMockserviceUrl, _headers, null); + CommonBL commonBL = new CommonBL(); + _headers.Remove(Constants.CustomUniqueRequestHeaderName); + var upstreamRequestUrl = commonBL.GetUpstreamRecordedRequest(pipelineConfigs.APIGatewayBaseUrl, _headers, Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue, "LessPayloadMockservice.json"); + string reqUrl = upstreamRequestUrl["path"].ToString(); + Logger.Info($"Upstream request url: { reqUrl}"); + AssertTest.IsTrue(upstreamRequestUrl != null && reqUrl.EndsWith(appConfigs.LessPayloadMockservice, StringComparison.InvariantCultureIgnoreCase), failMsg: "custom-header is not found in upstream request headers", passMsg: "custom-header is available in upstream request headers"); + AssertTest.IsTrue(upstreamResponse != null && upstreamResponse["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + } +} diff --git a/AutomationTest/Tests/APITests/ReverseProxyTests/ReverseProxyTests.cs b/AutomationTest/Tests/APITests/ReverseProxyTests/ReverseProxyTests.cs new file mode 100644 index 00000000..abb2e8fd --- /dev/null +++ b/AutomationTest/Tests/APITests/ReverseProxyTests/ReverseProxyTests.cs @@ -0,0 +1,216 @@ +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Driver.UI.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITest +{ + [TestClass] + public class ReverseProxyTests : BaseTest + { + private Cookies _cookie; + private readonly CommonSteps _commonSteps = new CommonSteps(); + private readonly string _heavyPayloadMockServiceUrl = $"{pipelineConfigs.APIGatewayBaseUrl}/{ appConfigs.HeavyPayloadMockservice}"; + private readonly string _serviceUnavailableMockServiceUrl = $"{pipelineConfigs.APIGatewayBaseUrl}/{ appConfigs.ServiceUnavailableMockservice}"; + + [TestInitialize] + public void BeforeTest() + { + Logger.Info("Before test"); + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + Report.Step(@"Get the cookie from browser", @"Should get the valid cookie"); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + _cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + AssertTest.IsTrue(_cookie != null, failMsg: "Cookie is not available", passMsg: $"Cookie is available, Cookie value: {_cookie.CookieValue}"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void PostCallWithLessPayloadTest() + { + Report.Step(@"POST API call to the less payload mockservice with the cookie", @"Should get the valid upstream response with Status: Success"); + var (headers, requestBody) = GetRequestBody(defaultEndpointUrl); + var postResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Post, defaultEndpointUrl, headers, requestBody); + AssertTest.IsTrue(postResponseBody != null && postResponseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void PostCallWithHeavyPayloadTest() + { + Report.Step(@"POST API call to the Heavy payload mockservice with the cookie", @"Should get the valid upstream response with Status: Success"); + var (headers, requestBody) = GetRequestBody(_heavyPayloadMockServiceUrl); + var postResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Post, _heavyPayloadMockServiceUrl, headers, requestBody); + AssertTest.IsTrue(postResponseBody != null && postResponseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void PostCallWhenUpstreamServiceDownTest() + { + Report.Step(@"POST API call to the Service unavailable mockservice with the cookie", @"Should get 503 Service Unavailable status"); + var (headers, requestBody) = GetRequestBody(defaultEndpointUrl); + var postResponseBody = HttpClientUtility.ExecuteAsync(HttpMethod.Post, _serviceUnavailableMockServiceUrl, headers, requestBody); + AssertTest.IsTrue(postResponseBody.Result.StatusCode == HttpStatusCode.ServiceUnavailable, failMsg: "No StatusCode:503 Service Unavailable status received", passMsg: "Received StatusCode:503 Service Unavailable status"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void GetCallWithLessPayloadTest() + { + Report.Step(@"GET API call to the less payload mockservice with the cookie", @"Should get the valid upstream response with Status: Success"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + Logger.Info($"GET endpoint Url: {defaultEndpointUrl}"); + var getResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, headers, null); + AssertTest.IsTrue(getResponseBody != null && getResponseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void GetCallWithHeavyPayloadTest() + { + Report.Step(@"GET API call to the Heavy payload mockservice with the cookie", @"Should get the valid upstream response with Status: Success"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + Logger.Info($"GET endpoint Url: {_heavyPayloadMockServiceUrl}"); + var getResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, _heavyPayloadMockServiceUrl, headers, null); + AssertTest.IsTrue(getResponseBody != null && getResponseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void GetCallWhenUpstreamServiceDownTest() + { + Report.Step(@"GET API call to the Service unavailable mockservice with the cookie", @"Should get 503 Service Unavailable status"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + Logger.Info($"GET endpoint Url: {_serviceUnavailableMockServiceUrl}"); + var getResponseBody = HttpClientUtility.ExecuteAsync(HttpMethod.Get, _serviceUnavailableMockServiceUrl, headers, null); + AssertTest.IsTrue(getResponseBody.Result.StatusCode == HttpStatusCode.ServiceUnavailable, failMsg: "No StatusCode:503 Service Unavailable status received", passMsg: "Received StatusCode:503 Service Unavailable status"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void PutCallWithHeavyPayloadTest() + { + Report.Step(@"PUT API call to the Heavy payload mockservice with the cookie", @"Should get the valid upstream response with Status: Success"); + var (headers, requestBody) = GetRequestBody(defaultEndpointUrl); + Logger.Info($"PUT call endpoint Url: {_heavyPayloadMockServiceUrl}"); + var putResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Put, _heavyPayloadMockServiceUrl, headers, requestBody); + AssertTest.IsTrue(putResponseBody != null && putResponseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void PutCallWhenUpstreamServiceDownTest() + { + Report.Step(@"PUT API call to the Service unavailable mockservice with the cookie", @"Should get 503 Service Unavailable status"); + var (headers, requestBody) = GetRequestBody(defaultEndpointUrl); + Logger.Info($"PUT call endpoint Url: {_serviceUnavailableMockServiceUrl}"); + var putResponseBody = HttpClientUtility.ExecuteAsync(HttpMethod.Put, _serviceUnavailableMockServiceUrl, headers, requestBody); + AssertTest.IsTrue(putResponseBody.Result.StatusCode == HttpStatusCode.ServiceUnavailable, failMsg: "No StatusCode:503 Service Unavailable status received", passMsg: "Received StatusCode:503 Service Unavailable status"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void DeleteCallTest() + { + Report.Step(@"DELETE API call to the Heavy payload mockservice with cookie", @"Should get the valid upstream response with Status: Success"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + Logger.Info($"DELETE call endpoint Url: {_heavyPayloadMockServiceUrl}"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Delete, _heavyPayloadMockServiceUrl, headers, null); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void DeleteCallWhenUpstreamServiceDownTest() + { + Report.Step(@"DELETE API call to the Service unavailable mockservice with cookie", @"Should get 503 Service Unavailable status"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + Logger.Info($"DELETE call endpoint Url: {_serviceUnavailableMockServiceUrl}"); + var responseBody = HttpClientUtility.ExecuteAsync(HttpMethod.Delete, _serviceUnavailableMockServiceUrl, headers, null); + AssertTest.IsTrue(responseBody.Result.StatusCode == HttpStatusCode.ServiceUnavailable, failMsg: "No StatusCode:503 Service Unavailable status received", passMsg: "Received StatusCode:503 Service Unavailable status"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void CDRUpstreamServiceAPICallTest() + { + Report.Step(@"GET API call to the CDR service with cookie", @"Should get the valid CDR service response"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + string cdrServiceUrl = $"{pipelineConfigs.APIGatewayBaseUrl}{appConfigs.CDRImagingStudyUrlPath.Replace("OrgId", tenantDetails[Constants.ValidTenantKey].IamOrganizationId)}"; + Logger.Info($"CDR service Url: {cdrServiceUrl}"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, cdrServiceUrl, headers, null); + AssertTest.IsTrue(responseBody != null && responseBody.ContainsKey("resourceType") && responseBody["resourceType"].ToString().EqualsWithIgnoreCase("Bundle"), + failMsg: "Failed to get the CDR service success response", passMsg: "Received the CDR service success response"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APICallsFromUIUpstreamInBrowserTest() + { + Report.Step(@"Browse the mountebank imposters endpoint url", @"Should get the mountebank imposters UI page in browser"); + var impostersUrl = $"{pipelineConfigs.APIGatewayBaseUrl}/imposters"; + Logger.Info($"Imposters page Url: {impostersUrl}"); + ReverseProxyBL reverseProxyBL = new ReverseProxyBL(WebDriver); + AssertTest.IsTrue(reverseProxyBL.NavigateToImpostersPageAndGetIsMockServiceLinksDisplayed(impostersUrl), failMsg: "Imposters page is not displayed", passMsg: "Imposters page is displayed"); + + + Report.Step(@"Click on less payload mockservice link and verify", @"Should get the less payload mockservice contents"); + var isMockserviceADisplayed = reverseProxyBL.ClickOnImposterAndGetMockServiceContentIsDisplayed("ServiceA"); + AssertTest.IsTrue(isMockserviceADisplayed, failMsg: "Less payload mockservice content is not displayed", passMsg: "Less payload mockservice content is displayed"); + + + Report.Step(@"Click on Heavy payload mockservice link and verify", @"Should get the Heavy payload mockservice contents"); + WebDriver.ClickOnBrowserBackButton(); + var isMockserviceBDisplayed = reverseProxyBL.ClickOnImposterAndGetMockServiceContentIsDisplayed("ServiceB"); + AssertTest.IsTrue(isMockserviceBDisplayed, failMsg: "Heavy payload mockservice content is not displayed", passMsg: "Heavy payload mockservice content is displayed"); + } + + private (Dictionary<string, string>, StringContent) GetRequestBody(string endpointUrl) + { + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, _cookie.CookieValue); + Logger.Info($"GET API call endpoint Url: {endpointUrl}"); + var getResponseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, headers, null); + var requestBody = HttpClientUtility.CreateHttpContent(getResponseBody.ToString()); + Logger.Info($"Request body: {requestBody}"); + return (headers, requestBody); + } + } +} diff --git a/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/IAMBrokerConfigurationTests.cs b/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/IAMBrokerConfigurationTests.cs new file mode 100644 index 00000000..649ed2d9 --- /dev/null +++ b/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/IAMBrokerConfigurationTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using TimeZoneConverter; +using Utilities; +using Utilities.Wait; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests +{ + [TestClass] + public class IAMBrokerConfigurationTests : BaseTest + { + private readonly string _setKeyUrl = $"{pipelineConfigs.APIGatewayBaseUrl}{appConfigs.IamBrokerConfigAPIRelativePath.Replace("OrgId", tenantDetails[Constants.ValidTenantKey].IamOrganizationId)}"; + private readonly VueSSOTokenBL _ssoTokenBL = new VueSSOTokenBL(); + + [TestCategory(nameof(TestCategory.OnPrem))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.IAMTokenExchangeBrokerPreCondition))] + [TestMethod] + public async Task SetSymmetricKeyWithValidAccessTokenTest() + { + Report.Step(@"Set symmetrickey with valid access token", @"Should get 204 NoContent response"); + var userAccessTokenHeader = HttpClientUtility.CreateUserAccessTokenHeader(pipelineConfigs); + string url = $"{_setKeyUrl}{Constants.SymmetricKeyName}"; + Logger.Info($"Symmetrickey set url: {url}"); + var content = HttpClientUtility.CreateHttpContent($"\"{pipelineConfigs.OrgSymmetricKey}\""); + var response = await HttpClientUtility.ExecuteAsync(HttpMethod.Post, url, userAccessTokenHeader, content); + AssertTest.IsTrue(response != null && response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.NoContent, failMsg: $"Failed to set the symmetric key, StatusCode: {response.StatusCode}", passMsg: "Symmetrickey set successfully"); + } + + [TestCategory(nameof(TestCategory.OnPrem))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.IAMTokenExchangeBroker))] + [TestMethod] + public async Task SetSymmetricKeyWithInvalidAccessTokenTest() + { + Report.Step(@"Set symmetrickey with invalid access token", @"Should get 401 Unauthorized response"); + var invalidAccessTokenHeader = new Dictionary<string, string>(); + invalidAccessTokenHeader.Add("Authorization", $"Bearer {Guid.NewGuid()}"); + string url = $"{_setKeyUrl}{Constants.SymmetricKeyName}"; + Logger.Info($"Symmetrickey set url: {url}"); + var content = HttpClientUtility.CreateHttpContent($"\"{pipelineConfigs.OrgSymmetricKey}\""); + var response = await HttpClientUtility.ExecuteAsync(HttpMethod.Post, _setKeyUrl, invalidAccessTokenHeader, content); + AssertTest.IsTrue(response.StatusCode == HttpStatusCode.Unauthorized, failMsg: $"Not received HttpStatusCode 401 Unauthorized, Actual: {response.StatusCode}", passMsg: "Received HttpStatusCode.Unauthorized"); + } + + [TestCategory(nameof(TestCategory.OnPrem))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.IAMTokenExchangeBroker))] + [TestMethod] + public async Task SetTimeZoneWithTokyoStandardTimeTest() + { + try + { + await SetTimeZoneAndValidate(Constants.TokyoTimeZone); + var response = await APICallWithSSOTokenAndGetResponse(Constants.TokyoTimeZone, defaultEndpointUrl); + AssertTest.IsTrue(response != null && response.IsSuccessStatusCode, failMsg: $"TimeZone:{Constants.TokyoTimeZone}, Not received HttpStatusCode 200, Actual: {response.StatusCode}", passMsg: "Received HttpStatusCode 200"); + + response = await APICallWithSSOTokenAndGetResponse(Constants.UTCTimeZone, defaultEndpointUrl); + AssertTest.IsTrue(response != null && response.StatusCode == HttpStatusCode.Unauthorized, failMsg: $"TimeZone:{Constants.UTCTimeZone}, Not received HttpStatusCode 401 Unauthorized, Actual: {response.StatusCode}", passMsg: "Received HttpStatusCode.Unauthorized"); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + } + finally + { + await SetTimeZoneAndValidate(Constants.UTCTimeZone); + var response = await APICallWithSSOTokenAndGetResponse(Constants.UTCTimeZone, defaultEndpointUrl); + AssertTest.IsTrue(response != null && response.IsSuccessStatusCode, failMsg: $"TimeZone:{Constants.UTCTimeZone}, Not received HttpStatusCode 200, Actual: {response.StatusCode}", passMsg: "Received HttpStatusCode 200"); + } + } + + #region private methods + + private async Task SetTimeZoneAndValidate(string timeZoneName) + { + Report.Step($"Set {timeZoneName} timezone", @"Should get 204 NoContent response"); + var userAccessTokenHeader = HttpClientUtility.CreateUserAccessTokenHeader(pipelineConfigs); + string url = $"{_setKeyUrl}{Constants.TimeZoneKeyName}"; + timeZoneName = $"\"{timeZoneName}\""; + Logger.Info($"Set Timezone url: {url}"); + var content = HttpClientUtility.CreateHttpContent(timeZoneName); + var response = await HttpClientUtility.ExecuteAsync(HttpMethod.Post, url, userAccessTokenHeader, content); + AssertTest.IsTrue(response.IsSuccessStatusCode && response.StatusCode == HttpStatusCode.NoContent, failMsg: $"Failed to set the time zone- {timeZoneName}, StatusCode: {response.StatusCode}", passMsg: $"TimeZone was set successfully, StatusCode: {response.StatusCode}"); + } + + private Task<HttpResponseMessage> APICallWithSSOTokenAndGetResponse(string timeZoneName, string url) + { + Report.Step($"API call with SSO token generated with {timeZoneName} timezone", @"Should get the upstream response"); + var tzi = TZConvert.GetTimeZoneInfo(timeZoneName); + string ssoToken = _ssoTokenBL.GenerateSsoToken("user", TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi).DateTime.ToString(Constants.TimeStampFormat), pipelineConfigs.OrgSymmetricKey); + Sleep.Seconds(appConfigs.MountibankTimeoutinSeconds); + var headers = new Dictionary<string, string>(); + headers.TryAdd("EDISP-vuesso", ssoToken); + headers.TryAdd("edisp-vuesso-orgid", tenantDetails[Constants.ValidTenantKey].IamOrganizationId); + return HttpClientUtility.ExecuteAsync(HttpMethod.Get, url, headers, null); + } + + #endregion + } +} diff --git a/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/IDTokenValidatorTests.cs b/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/IDTokenValidatorTests.cs new file mode 100644 index 00000000..7b353430 --- /dev/null +++ b/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/IDTokenValidatorTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Net.Http; +using Newtonsoft.Json; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities; +using IdentityModel.Jwk; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests +{ + [TestClass] + public class IDTokenValidatorTests : BaseTest + { + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.IDTokenValidator))] + [TestCategory(nameof(TestCategory.UpgradeSanity))] + [TestCategory(nameof(TestCategory.IntegratedSanity))] + [TestMethod] + public void GetOpenIdConfigurationAndVerifyJWKSDataTest() + { + + var openIdConfigOrgs = JsonConvert.DeserializeObject<List<OpenIdConfigOrganizationCertificateMapping>>(pipelineConfigs.OpenIdConfigOrganizationCertificateMapping); + + Report.Step(@"Get Jwks url from OpenId Configuration get call for multitenant ", @"Should get the respective tenant Jwks url"); + foreach (var org in openIdConfigOrgs) + { + string openIdConfigUrl = $"{pipelineConfigs.OpenIdConfigurationBaseUrl}{appConfigs.OpenIdConfigurationUrlPath.Replace("OrgId", org.OrganizationId)}"; + Logger.Info($"OpenId Configuration url: {openIdConfigUrl}"); + var openIdConfigResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, openIdConfigUrl, new Dictionary<string, string>(), null); + string jwksUrl = openIdConfigResponse["jwks_uri"]?.ToString(); + Logger.Info($"Jwks Url: {jwksUrl}"); + AssertTest.IsTrue(!string.IsNullOrWhiteSpace(jwksUrl) && jwksUrl.Contains($"/{org.OrganizationId}/"), failMsg: $"Failed to get Jwks url for the orgId: {org}", passMsg: "Jwks url is fetched successfully"); + + Report.Step(@"Get Jwks keys from the get call of jwks url", @"All the Jwks keys should not be null"); + var jwksResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, jwksUrl, new Dictionary<string, string>(), null); + var jwksData = JsonConvert.DeserializeObject<JsonWebKey>(jwksResponse["keys"]?.First.ToString()); + + string expectedCert = RemoveCertificateBoundaryAndLineBreaks(org.Certificate); + AssertTest.IsTrue(jwksData.X5c != null && jwksData.X5c[0].Equals(expectedCert), failMsg: $"Property 'x5c' value is null ", passMsg: $"Property 'x5c(certificate)' value is matching with expected value"); + AssertTest.IsTrue(jwksData.Kid != null, failMsg: $"Property 'Kid' value is null ", passMsg: $"Property 'Kid' is not null "); + AssertTest.IsTrue(jwksData.Kty != null, failMsg: $"Property 'Kty' value is null ", passMsg: $"Property 'Kty' is not null "); + AssertTest.IsTrue(jwksData.Alg != null, failMsg: $"Property 'alg' value is null ", passMsg: $"Property 'alg' is not null "); + AssertTest.IsTrue(jwksData.Use != null, failMsg: $"Property 'Use' value is null ", passMsg: $"Property 'Use' is not null "); + AssertTest.IsTrue(jwksData.N != null, failMsg: $"Property 'N' value is null ", passMsg: $"Property 'N' is not null "); + AssertTest.IsTrue(jwksData.E != null, failMsg: $"Property 'E' value is null ", passMsg: $"Property 'E' is not null "); + } + } + + #region private methods + private string RemoveCertificateBoundaryAndLineBreaks(string certData) + { + return certData.Replace("-----BEGIN CERTIFICATE-----", string.Empty) + .Replace("-----END CERTIFICATE-----", string.Empty) + .Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", string.Empty); + } + #endregion + } +} diff --git a/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/SSOTokenAuthenticatorTests.cs b/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/SSOTokenAuthenticatorTests.cs new file mode 100644 index 00000000..29288525 --- /dev/null +++ b/AutomationTest/Tests/APITests/SSOTokenAuthenticatorTests/SSOTokenAuthenticatorTests.cs @@ -0,0 +1,220 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using System; +using System.Collections.Generic; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using System.Net.Http; +using Reporters; +using System.Linq; +using System.Threading.Tasks; +using Utilities.Wait; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITests.SSOTokenAuthenticatorTests +{ + [TestClass] + public class SSOTokenAuthenticatorTests : BaseTest + { + private readonly VueSSOTokenBL _ssoTokenBL = new VueSSOTokenBL(); + private readonly HSPIAMBusinessLayer _iamBL = new HSPIAMBusinessLayer(); + private string ssoToken; + private string userName; + string cdrServiceUrl = $"{pipelineConfigs.APIGatewayBaseUrl}{appConfigs.CDRImagingStudyUrlPath.Replace("OrgId", tenantDetails[Constants.ValidTenantKey].IamOrganizationId)}"; + string getStudyDetailsUrl = $"{pipelineConfigs.CDRBaseUrl}{appConfigs.CDRImagingStudyUrlPath.Replace("OrgId", tenantDetails[Constants.ValidTenantKey].IamOrganizationId)}?_count=1"; + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void UserwithPermissionsTest() + { + Report.Step(@"CDR Call with SSO token header of user having required permission", @"Should get the valid upstream response"); + var response = GetUpstreamResponseWithSSOToken(pipelineConfigs.SSOTokenUserName, pipelineConfigs.OrgSymmetricKey); + AssertTest.IsTrue(response.ToString().Contains("StatusCode: 200"), failMsg: $"StatusCode was not 200, Resopnse Message: {response}", passMsg: "Received success response with statuscode as 200"); + } + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void CreateUserwithoutPermissionsTest() + { + try + { + Report.Step(@"CDR Call with SSO token header of a new user not having required permission", @"Should get 401 unauthorized upstream response"); + Random rand = new Random(); + userName = "autoTest"+ rand.Next(99, 9999); + var response = GetUpstreamResponseWithSSOToken(userName, pipelineConfigs.OrgSymmetricKey); + AssertTest.IsTrue(response.ToString().Contains("StatusCode: 401"), failMsg: "CDR call did not returned Unauthorized access", + passMsg: "CDR call returned Status code of 401 Unauthorized access as expected"); + } + catch (Exception e) + { + Report.ReportError("Exception",e.ToString()); + + } + finally + { + //Deleting the created automation user + var userAccessTokenHeader = CreateUserAccessTokenHeader(); + _ = _iamBL.DeleteUser(pipelineConfigs.IAMGetUserUrl, pipelineConfigs.IDMClientBaseUrl, userName+ "_rubyhealthiamte@vue.com", userAccessTokenHeader); + } + } + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void UserwithWrongSSOTokenHeaderTest() + { + Report.Step(@"CDR Call with with wrong SSO token header of a user", @"Should get invalid sso token upstream response"); + var headers = new Dictionary<string, string>(); + headers.TryAdd("EDISP-vuesso", "InvalidSSOToken"); + headers.TryAdd("edisp-vuesso-orgid", tenantDetails[Constants.ValidTenantKey].IamOrganizationId); + var response = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, cdrServiceUrl, headers, null); + AssertTest.IsTrue(response["Detail"].ToString().EqualsWithIgnoreCase("Please provide valid sso token and symmetric key"), + failMsg: "CDR call did not returned 'Please provide valid sso token and symmetric key' error response", + passMsg: "CDR call returned error message 'Please provide valid sso token and symmetric key' as expected"); + } + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void UserwithInvalidOrgIdHeaderTest() + { + Report.Step(@"CDR Call with with wrong orgId header", @"Should get the given key is not present in the dictionary upstream response"); + var headers = new Dictionary<string, string>(); + ssoToken = _ssoTokenBL.GenerateSsoToken(pipelineConfigs.SSOTokenUserName, DateTime.UtcNow.AddMinutes(0).ToString(Constants.TimeStampFormat), pipelineConfigs.OrgSymmetricKey); + headers.TryAdd("EDISP-vuesso", ssoToken); + headers.TryAdd("edisp-vuesso-orgid", string.Empty); + var response = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, cdrServiceUrl, headers, null); + var message = "orgId is empty, set it in edisp-vuesso-orgid header"; + AssertTest.IsTrue(response["Detail"].ToString().EqualsWithIgnoreCase(message), + failMsg: $"CDR call did not returned '{message}' error response", + passMsg: $"CDR call returned error message '{message}' as expected"); + } + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void UserwithExpiredSSOTokenTest() + { + Report.Step(@"CDR Call with with Expired SSO token header of a user with all required permission", @"Should get invalid sso token session timed out upstream response"); + ssoToken = _ssoTokenBL.GenerateSsoToken(pipelineConfigs.SSOTokenUserName, DateTime.UtcNow.AddMinutes(-31).ToString(Constants.TimeStampFormat), pipelineConfigs.OrgSymmetricKey); + var headers = new Dictionary<string, string>(); + headers.TryAdd("EDISP-vuesso", ssoToken); + headers.TryAdd("edisp-vuesso-orgid", tenantDetails[Constants.ValidTenantKey].IamOrganizationId); + var response = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, cdrServiceUrl, headers, null); + AssertTest.IsTrue(response["Detail"].ToString().EqualsWithIgnoreCase("SSO token session timed out"), + failMsg: "CDR call did not returned 'SSO token session timed out' error response", + passMsg: "CDR call returned error message 'SSO token session timed out' as expected"); + } + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public async Task AccessTokenValueisTakenFromCacheTest() + { + Report.Step(@"Verify that new session with same user uses same cached accesstoken", + @"same cached access token should be used"); + List<string> accessTokenValues = new List<string>(); + + for (int i = 0; i < 2; i++) + { + var headers = new Dictionary<string, string>(); + string customUniqueRequestHeaderValue = Guid.NewGuid().ToString(); + headers.Add(Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue); + ssoToken = _ssoTokenBL.GenerateSsoToken(pipelineConfigs.SSOTokenUserName, DateTime.UtcNow.ToString(Constants.TimeStampFormat), pipelineConfigs.OrgSymmetricKey); + + headers.TryAdd("EDISP-vuesso", ssoToken); + headers.TryAdd("edisp-vuesso-orgid", tenantDetails[Constants.ValidTenantKey].IamOrganizationId); + Sleep.Seconds(appConfigs.MountibankTimeoutinSeconds); + + _ = await HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, headers, null); + CommonBL commonBL = new CommonBL(); + var testHeaderRequest = commonBL.GetUpstreamRecordedRequest(pipelineConfigs.APIGatewayBaseUrl, headers, Constants.CustomUniqueRequestHeaderName, customUniqueRequestHeaderValue, "LessPayloadMockservice.json"); + accessTokenValues.Add(testHeaderRequest["headers"]["authorization"].ToString()); + } + AssertTest.IsTrue(accessTokenValues.First().Equals(accessTokenValues.Last()), failMsg: "New session with same user is not using same cached accesstoken", + passMsg: "New session with same user is using cached accesstoken"); + } + + [TestCategory(nameof(TestCategory.SSOToken))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestMethod] + public void InvalidSymmetricKeyTest() + { + Report.Step(@"CDR Call with SSO token header of Invalid Symmetric Key", @"Should get 401 unauthorized upstream response"); + var response = GetUpstreamResponseWithSSOToken(pipelineConfigs.SSOTokenUserName, "rpJupVvvHiX5kgrPllV8gWsurbzSu9D99kUwamFdL9I="); + AssertTest.IsTrue(response.ToString().Contains("StatusCode: 401"), failMsg: "CDR call did not returned Unauthorized access", + passMsg: "CDR call returned Status code of 401 Unauthorized access as expected"); + } + + /// <summary> + /// Method to get the Upstream response with SSO token + /// </summary> + /// <param name="userName">UserName to generate the SSO Token</param> + /// <param name="addMin">addMinutes value</param> + /// /// <param name="symmetricKey">SymmetricKey of org value</param> + /// <returns>HttpResponseMessage</returns> + private HttpResponseMessage GetUpstreamResponseWithSSOToken(String userName,string symmetricKey, int addMin = 0) + { + try + { + Report.Step(@"CDR Call with SSO token header of user having required permission", @"Should get the valid upstream response"); + ssoToken = _ssoTokenBL.GenerateSsoToken(userName, DateTime.UtcNow.AddMinutes(addMin).ToString(Constants.TimeStampFormat), symmetricKey); + var headers = new Dictionary<string, string>(); + Sleep.Seconds(appConfigs.MountibankTimeoutinSeconds); + headers.TryAdd("edisp-vuesso", ssoToken); + headers.TryAdd("edisp-vuesso-orgid", tenantDetails[Constants.ValidTenantKey].IamOrganizationId); + + return HttpClientUtility.ExecuteAsync(HttpMethod.Get, cdrServiceUrl + "/" + GetStudyUidDetails(), headers, null).Result; + } + catch (Exception e) + { + Report.ReportError("Exception", e.ToString()); + throw; + } + } + + /// <summary> + /// Method to get the single studyuid details based on organization + /// </summary> + /// <returns>string of study id</returns> + private string GetStudyUidDetails() + { + Report.Step(@"Get Study details for the Org", @"Should get the study details"); + var headers = CreateUserAccessTokenHeader(); + headers.TryAdd("api-version", "1"); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, getStudyDetailsUrl, headers, null); + var studyDetails = responseBody["entry"]?.Where(x => x["resource"]["id"] != null).Select(x => x["resource"]?["id"]?.ToString()).ToList()[0]; + + if (string.IsNullOrEmpty((studyDetails))) + { + AssertTest.IsTrue(false, "Fetched Study details were empty", "", false); + } + else + { + AssertTest.IsTrue(true, "", "Study details were fetched", false); + } + + return studyDetails; + } + + /// <summary> + /// Method to created Access Token header + /// </summary> + /// <returns>Dictionary<Key, value></returns> + private Dictionary<string, string> CreateUserAccessTokenHeader() + { + var userAccessTokenHeader = HttpClientUtility.CreateUserAccessTokenHeader(pipelineConfigs); + AssertTest.IsTrue(userAccessTokenHeader != null, failMsg: "User Access Token header is null", passMsg: $"User Access Token header is not null"); + return userAccessTokenHeader; + } + } +} diff --git a/AutomationTest/Tests/Common/BaseTest.cs b/AutomationTest/Tests/Common/BaseTest.cs new file mode 100644 index 00000000..dee77acb --- /dev/null +++ b/AutomationTest/Tests/Common/BaseTest.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities; +using Utilities.Enums; +using Driver.UI.Interfaces; +using Driver.UI.Selenium; +using Utilities.Wait; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common +{ + [TestClass] + public abstract class BaseTest + { + + public static IWebDriverUi WebDriver + { + get => _WebDriver; + } + public static ReportManager Report + { + get => _report; + } + public TestContext TestContext + { + get => _testContext; + set => _testContext = value; + } + + public static readonly AppConfiguration appConfigs = Settings.GetConfiguration<AppConfiguration>(typeof(AppConfiguration).Name); + public static readonly PipelineConfiguration pipelineConfigs = Settings.GetConfiguration<PipelineConfiguration>(typeof(PipelineConfiguration).Name); + public static readonly Dictionary<string, TenantMapping> tenantDetails = JsonConvert.DeserializeObject<Dictionary<string, TenantMapping>>(pipelineConfigs.TenantDetails); + public readonly string defaultEndpointUrl = $"{pipelineConfigs.APIGatewayBaseUrl}{ appConfigs.LessPayloadMockservice}"; + private ReporterBase _ReporterBase; + private static IWebDriverUi _WebDriver; + private static ReportManager _report; + private TestContext _testContext; + private readonly string _outputFolder = Path.Combine(appConfigs.RootFolder, "Logs"); + private readonly ReporterTestInfo _testInfo = new ReporterTestInfo(); + + [TestInitialize] + public void Before() + { + Logger.Info("######### Start Test ######### Test Name: " + _testContext.TestName + "\r\n"); + _ReporterBase = new ReporterBase(_outputFolder, appConfigs.ReporterList, appConfigs.RootFolder, + appConfigs.RootEvidencePath, appConfigs.DifidoFolderLocation, appConfigs.ProductName); + _report = _ReporterBase.GetReportMngInstance(_testContext.TestName, _testContext.FullyQualifiedTestClassName, _testInfo); + AssertTest.ConfigureServices(TestProjectType.MsTest); + AssertTest.InitAssertService(); + ProcessUtilities.KillChromeDriver(); + } + + [TestCleanup] + public void After() + { + if (!TestContext.CurrentTestOutcome.Equals(UnitTestOutcome.Passed) && + Report.CurrentStepStatus == Reporters.BaseReport.Enums.Enum.StepStatus.Passed) + Report.ReportError("The test has failed by throw an exception (not by any assert validation)"); + + _ReporterBase.CloseReports(_testInfo); + + if (Report != null && WebDriver != null) + { + _ = WebDriver.Quit(); + } + ProcessUtilities.KillChromeDriver(); + Logger.Info("######### End Test ######### Test Name: " + _testContext.TestName + "\r\n"); + } + + protected bool LoginToEndpoint(string endpointUrl, string userName, string password) + { + Logger.InfoStartMethod(); + try + { + Logger.Info($"Endpoint url: {endpointUrl}"); + _WebDriver = new SeleniumDriver(Driver.Enums.BrowserType.Chrome, endpointUrl, null); + + object userNameElement = null; + WaitUtils.WaitUntil(() => + { + userNameElement = WebDriver.FindElementById("idToken1"); + return WebDriver.IsExist(userNameElement); + }, 60); + + _ = WebDriver.TypeText(userNameElement, userName); + + _ = WebDriver.TypeText(WebDriver.FindElementById("idToken2"), password); + + _ = WebDriver.Click(WebDriver.FindElementById("loginButton_0")); + + var saveConsentCheckBox = WebDriver.FindElementById("saveConsent"); + + if (WebDriver.IsExist(saveConsentCheckBox)) + { + Logger.Info($"Save Consent exists"); + + _ = WebDriver.Click(saveConsentCheckBox); + + _ = WebDriver.Click(WebDriver.FindElementByXPath("//button[@value='allow']")); + } + + var bodyElement = WebDriver.FindElementByXPath("//body/pre | //a[text()='home']"); + + return WebDriver.IsExist(bodyElement); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + finally + { + WebDriver.TakeScreenshot(); + } + } + + protected JObject GetResponseFromUI() + { + try + { + var bodyElement = WebDriver.FindElementByXPath("//body/pre"); + if (WebDriver.IsExist(bodyElement)) + { + string responseJson = WebDriver.GetText(bodyElement); + return JObject.Parse(responseJson); + } + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + return null; + } + + protected bool IsLoginPageDisplayed() + { + var userNameElement = WebDriver.FindElementById("idToken1"); + var passwordElement = WebDriver.FindElementById("idToken2"); + return WebDriver.IsExist(userNameElement) && WebDriver.IsExist(passwordElement); + } + } +} diff --git a/AutomationTest/Tests/Common/CommonSteps.cs b/AutomationTest/Tests/Common/CommonSteps.cs new file mode 100644 index 00000000..0738c568 --- /dev/null +++ b/AutomationTest/Tests/Common/CommonSteps.cs @@ -0,0 +1,73 @@ +using System; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Support.UI; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities.Wait; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common +{ + public class CommonSteps : BaseTest + { + public void BrowseEndpointAndVerifyLogin(string endpoint, string userName, string password) + { + Report.Step(@"Browse the endpoint url and login", @"Should successfully login and get the upstream response"); + var isLoginSuccessful = LoginToEndpoint(endpoint, userName, password); + AssertTest.IsTrue(isLoginSuccessful, failMsg: "Login Unsuccessful", passMsg: "Login Successful"); + var responseBody = GetResponseFromUI(); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response", passMsg: "Received success response"); + } + + public bool LogoutWithPOST(string endpointUrl, string userName, string password) + { + try + { + IWebDriver _localWebDriver = new ChromeDriver(); + _localWebDriver.Url = defaultEndpointUrl; + _localWebDriver.Navigate(); + + WebDriverWait wait = new WebDriverWait(_localWebDriver,TimeSpan.FromSeconds(20)); + + wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(By.Id("idToken1"))); + + IWebElement userNameElement = null; + WaitUtils.WaitUntil(() => + { + userNameElement = _localWebDriver.FindElement(By.Id("idToken1")); + return userNameElement.Displayed; + }, 60); + + userNameElement.SendKeys(userName); + _localWebDriver.FindElement(By.Id("idToken2")).SendKeys(password); ; + + _localWebDriver.FindElement(By.Id("loginButton_0")).Click(); + + wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(By.XPath("//body/pre | //a[text()='home']"))); + IWebElement bodyElement = _localWebDriver.FindElement(By.XPath("//body/pre | //a[text()='home']")); + + if(bodyElement.Displayed) + { + IJavaScriptExecutor js = (IJavaScriptExecutor)_localWebDriver; + string title = (string)js.ExecuteScript("navigator.sendBeacon('/logout');"); + } + + _localWebDriver.Navigate().Refresh(); + wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(By.Id("idToken1"))); + userNameElement = _localWebDriver.FindElement(By.Id("idToken1")); + bool isLoginPage = userNameElement.Enabled; + _localWebDriver.Quit(); + return isLoginPage; + } + catch (Exception ex) + { + Report.ReportError(ex.ToString(), "LogoutWithPOST"); + throw; + } + } + } + + + + +} diff --git a/AutomationTest/Tests/Data/MockServiceConfigs/HeavyPayloadMockservice.json b/AutomationTest/Tests/Data/MockServiceConfigs/HeavyPayloadMockservice.json new file mode 100644 index 00000000..987671dc --- /dev/null +++ b/AutomationTest/Tests/Data/MockServiceConfigs/HeavyPayloadMockservice.json @@ -0,0 +1,285 @@ +{ + "protocol": "http", + "port": 4546, + "name": "ServiceB", + "recordRequests": false, + "defaultResponse": { + "statusCode": 404 + }, + "stubs": [ + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "POST" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + }, + { + "matches": { + "body": ".*" + } + } + ], + "responses": [ + { + "is": { + "statusCode": 201, + "body": { + "status": "Success", + "message": "POST request received for sample payload" + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "GET" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + } + ], + "responses": [ + { + "is": { + "statusCode": 200, + "body": { + "status": "Success", + "SamplePayload": { + "PickupTruckA": { + "options": [ + "manual transmission", + "sunroof", + "premium stereo" + ], + "manufactureDate": "2019-01-15", + "current_location": { + "lat": 37.773972, + "lon": -122.431297 + }, + "fullLength": 230.25, + "used": true, + "price": 35000, + "previousOwner": { + "firstName": "John", + "lastName": "Smith" + } + }, + "PickupTruckB": { + "options": [ + "automatic transmission", + "keyless entry" + ], + "manufactureDate": "2018-06-12", + "current_location": { + "lat": 39.742043, + "lon": -104.991531 + }, + "fullLength": 215.75, + "used": true, + "price": 29500, + "previousOwner": { + "firstName": "Jane", + "lastName": "Jones" + } + }, + "PickupTruckC": { + "options": [ + "automatic transmission", + "keyless entry" + ], + "manufactureDate": "2018-06-12", + "current_location": { + "lat": 39.742043, + "lon": -104.991531 + }, + "fullLength": 215.75, + "used": true, + "price": 29500, + "previousOwner": { + "firstName": "Jane", + "lastName": "Jones" + } + }, + "PickupTruckD": { + "options": [ + "automatic transmission", + "keyless entry" + ], + "manufactureDate": "2018-06-12", + "current_location": { + "lat": 39.742043, + "lon": -104.991531 + }, + "fullLength": 215.75, + "used": true, + "price": 29500, + "previousOwner": { + "firstName": "Jane", + "lastName": "Jones" + } + }, + "PickupTruckE": { + "options": [ + "automatic transmission", + "keyless entry" + ], + "manufactureDate": "2018-06-12", + "current_location": { + "lat": 39.742043, + "lon": -104.991531 + }, + "fullLength": 215.75, + "used": true, + "price": 29500, + "previousOwner": { + "firstName": "Jane", + "lastName": "Jones" + } + } + } + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "PUT" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + }, + { + "matches": { + "body": ".*" + } + } + ], + "responses": [ + { + "is": { + "statusCode": 201, + "body": { + "status": "Success", + "message": "PUT request received for sample payload" + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "DELETE" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + } + ], + "responses": [ + { + "is": { + "statusCode": 201, + "body": { + "status": "Success", + "message": "DELETE request received" + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/AutomationTest/Tests/Data/MockServiceConfigs/LessPayloadMockservice.json b/AutomationTest/Tests/Data/MockServiceConfigs/LessPayloadMockservice.json new file mode 100644 index 00000000..09bc5995 --- /dev/null +++ b/AutomationTest/Tests/Data/MockServiceConfigs/LessPayloadMockservice.json @@ -0,0 +1,284 @@ +{ + "protocol": "http", + "port": 4545, + "name": "ServiceA", + "numberOfRequests": 0, + "recordRequests": true, + "requests": [ + + ], + "defaultResponse": { + "statusCode": 404 + }, + "stubs": [ + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "POST" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + }, + { + "matches": { + "body": ".*" + } + } + ], + "responses": [ + { + "is": { + "statusCode": 201, + "body": { + "status": "Success", + "message": "POST request received for sample payload" + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "GET" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + } + ], + "responses": [ + { + "is": { + "statusCode": 200, + "body": { + "status": "Success" + } + } + } + ] + }, + { + "predicates": [ + { + "contains": { + "path": "/multitenancy" + } + }, + { + "equals": { + "method": "GET" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + } + ], + "responses": [ + { + "is": { + "statusCode": 200, + "body": { + "status": "Success" + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "PUT" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + }, + { + "matches": { + "body": ".*" + } + } + ], + "responses": [ + { + "is": { + "statusCode": 201, + "body": { + "status": "Success", + "message": "PUT request received for sample payload" + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test" + } + }, + { + "equals": { + "method": "DELETE" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + } + ], + "responses": [ + { + "is": { + "statusCode": 204, + "body": { + "status": "Success", + "message": "DELETE request received" + } + } + } + ] + }, + { + "predicates": [ + { + "endsWith": { + "path": "/test/checkpermission" + } + }, + { + "equals": { + "method": "GET" + } + }, + { + "or": [ + { + "contains": { + "headers": { + "cookie": "edi_session" + } + } + }, + { + "startsWith": { + "headers": { + "authorization": "bearer" + } + } + } + ] + }, + { + "inject": "function (config) {\r\n\r\n var base64DecodedIntrospectValue = Buffer.from(config.request.headers[\"edisp-introspect-value\"], \"base64\").toString();\r\n\tvar orgList = JSON.parse(base64DecodedIntrospectValue )[\"organizations\"][\"organizationList\"];\r\n\r\n\tfor (let i in orgList) { \r\n\t\tif(orgList[i][\"organizationId\"] === config.request.headers[\"edisp-org-id\"])\r\n\t\t{\r\n\t\t\treturn !orgList[i][\"permissions\"].includes(config.request.headers[\"permission-name\"]); \r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}" + } + ], + "responses": [ + { + "is": { + "statusCode": 403, + "body": { + "status": "Forbidden", + "message": "Access Forbidden" + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/AutomationTest/Tests/PostDeploymentTests/PostDeploymentTests.cs b/AutomationTest/Tests/PostDeploymentTests/PostDeploymentTests.cs new file mode 100644 index 00000000..80f7ce4c --- /dev/null +++ b/AutomationTest/Tests/PostDeploymentTests/PostDeploymentTests.cs @@ -0,0 +1,54 @@ +using System.IO; +using System.Net; +using System.Net.Http; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.APITest +{ + [TestClass] + public class PostDeploymentTests : BaseTest + { + [TestCategory(nameof(TestCategory.PostDeployment))] + [TestMethod] + public void PostDeploymentTest() + { + Report.Step(@"Create the mockservices with mountebank", @"Should create the new mockservices"); + bool isLoginSuccessful = LoginToEndpoint(pipelineConfigs.APIGatewayBaseUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + AssertTest.IsTrue(isLoginSuccessful, failMsg: "Login Unsuccessful", passMsg: "Login Successful"); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + var cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + string[] mockJsonFiles = Directory.GetFiles(Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Tests", "Data", "MockServiceConfigs"), "*.json"); + + if (mockJsonFiles == null || mockJsonFiles.Length <= 0) + { + AssertTest.IsTrue(false, "No mock json files available"); + } + + foreach (var mockJsonFile in mockJsonFiles) + { + string mockJsonString = File.ReadAllText(mockJsonFile); + var impostersUrl = $"{pipelineConfigs.APIGatewayBaseUrl}/imposters"; + string port = JObject.Parse(mockJsonString)["port"].ToString(); + Logger.Info($"Mockservice File: {mockJsonFile}, Port: {port}"); + + var httpContent = HttpClientUtility.CreateHttpContent(mockJsonString); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, cookie.CookieValue); + string deleteMockServiceApiUrl = $"{ impostersUrl }/{ port}"; + Logger.Info($"Delete mockservice Url: {deleteMockServiceApiUrl}"); + var deleteResponse = HttpClientUtility.ExecuteAsync(HttpMethod.Delete, deleteMockServiceApiUrl, headers, httpContent).Result; + AssertTest.IsTrue(deleteResponse.IsSuccessStatusCode || deleteResponse.StatusCode == HttpStatusCode.NotFound, failMsg: "Failed to delete the mockservice imposter", passMsg: "Deleted the mockservice imposter"); + Logger.Info($"Create mockservice url: {impostersUrl}"); + var postCallResponse = HttpClientUtility.ExecuteAsync(HttpMethod.Post, impostersUrl, headers, httpContent).Result; + AssertTest.IsTrue(postCallResponse.IsSuccessStatusCode, failMsg: "Failed to create mockservice", passMsg: "Created mockservice successfully"); + } + } + } +} diff --git a/AutomationTest/Tests/UITests/AuthenticationTests/AuthenticationTests.cs b/AutomationTest/Tests/UITests/AuthenticationTests/AuthenticationTests.cs new file mode 100644 index 00000000..f5d2467b --- /dev/null +++ b/AutomationTest/Tests/UITests/AuthenticationTests/AuthenticationTests.cs @@ -0,0 +1,125 @@ +using System.Net.Http; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using Utilities.Wait; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.UITests.AuthenticationTests +{ + [TestClass] + public class AuthenticationTests : BaseTest + { + private readonly CommonSteps _commonSteps = new CommonSteps(); + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void BrowseEndpointAndLoginWithValidCredentialsTest() + { + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void BrowseEndpointAndLoginWithInvalidCredentialsTest() + { + Report.Step(@"Browse the endpoint url and login with invalid UserName", @"Login should be unsuccessfull"); + var isLoginSuccessful = LoginToEndpoint(defaultEndpointUrl, userName: "abc@philips.com", "abc123"); + AssertTest.IsFalse(isLoginSuccessful, failMsg: "Login successful with invalid credentials", passMsg: "Unsuccessful Login"); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APICallWithValidCookieTest() + { + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + + Report.Step(@"API call with cookie taken from browser", @"Should get the valid upstream response"); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + var cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + AssertTest.IsTrue(cookie != null, failMsg: "Cookie is not available", passMsg: $"Cookie is available, Cookie value: {cookie.CookieValue}"); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, cookie.CookieValue); + var responseBody = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, headers, null); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void APICallInBrowserWithInValidCookieTest() + { + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + + Report.Step(@"Delete/Modify the cookie in browser and browse again the same endpoint url", @"Should get a new cookie and that passes to upstream and get the valid upstream response"); + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + var beforeCookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + beforeCookie.CookieValue = $"abc123{beforeCookie.CookieValue}"; + authenticationBL.SetCookie(beforeCookie); + WebDriver.Goto(defaultEndpointUrl); + var afterCookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + var responseBody = GetResponseFromUI(); + AssertTest.AreNotEqual(beforeCookie.CookieValue, afterCookie.CookieValue); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received after cookie deleted/modified", passMsg: "Received Success response after cookie deleted/modified"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void AccessUpstreamJustBeforeSessionTimeoutTest() + { + //Make sure to set "OAUTH2_PROXY_COOKIE_REFRESH" = "0h0m15s" (15 seconds) in Oauth proxy service for automaiton + + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + + Report.Step($"Wait till before session timeout ({pipelineConfigs.OauthProxyCookieTimeoutInSeconds} seconds)", @"Cookie should not change before the session timeout"); + int waitTimeInSeconds = -18; + AssertTest.IsFalse(WaitForGivenTimeAndGetIsCookieChanged(waitTimeInSeconds), failMsg: $"Cookie has changed before the session timeout: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds} and wait time: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds + waitTimeInSeconds}", + passMsg: $"Cookie has not changed before the session timeout: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds} and wait time: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds + waitTimeInSeconds}"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void AccessUpstreamAfterSessionTimeoutTest() + { + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + + + Report.Step($"Wait till after session timeout ({pipelineConfigs.OauthProxyCookieTimeoutInSeconds} seconds)", @"Cookie should change after the session timeout"); + int waitTimeInSeconds = 2; + AssertTest.IsTrue(WaitForGivenTimeAndGetIsCookieChanged(waitTimeInSeconds), failMsg: $"Cookie has not changed after the session timeout: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds} and wait time: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds + waitTimeInSeconds}", + passMsg: $"Cookie has changed after the session timeout: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds} and wait time: {pipelineConfigs.OauthProxyCookieTimeoutInSeconds + waitTimeInSeconds}"); + } + + private bool WaitForGivenTimeAndGetIsCookieChanged(int waitTimeInSeconds) + { + AuthenticationBL authenticationBL = new AuthenticationBL(WebDriver); + var beforeCookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + int totalWaitTime = pipelineConfigs.OauthProxyCookieTimeoutInSeconds + waitTimeInSeconds; + if (totalWaitTime > 0) + { + Sleep.Seconds(totalWaitTime); + } + WebDriver.Goto(defaultEndpointUrl); + var afterCookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + AssertTest.IsTrue(true,failMsg:"",passMsg:$"Before cookie:{beforeCookie.CookieValue}\nAfterCookie:{afterCookie.CookieValue}"); + return !beforeCookie.CookieValue.Equals(afterCookie.CookieValue); + } + } +} diff --git a/AutomationTest/Tests/UITests/LogoutTests/BrowserLogoutTests.cs b/AutomationTest/Tests/UITests/LogoutTests/BrowserLogoutTests.cs new file mode 100644 index 00000000..4c23da66 --- /dev/null +++ b/AutomationTest/Tests/UITests/LogoutTests/BrowserLogoutTests.cs @@ -0,0 +1,135 @@ +using Driver.UI.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Philips.EDI.Foundation.APIGateway.AutomationTest.BusinessLayer; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Reporters; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.UITests.LogoutTests +{ + [TestClass] + public class BrowserLogoutTests : BaseTest + { + #region Tests + CommonSteps _commonSteps = new CommonSteps(); + [TestInitialize] + public void BeforeTest() + { + Report.Step(@"Login and open default endpoint url", @"should open the default mockservice page"); + + _commonSteps.BrowseEndpointAndVerifyLogin(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword); + } + + [TestCategory(nameof(TestCategory.GatedSanity))] + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.BrowserLogout))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void LogoutSuccessTest() + { + Report.Step(@"Call Logout api", @"should be redirected to the login page"); + AssertLogoutSuccess(); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.BrowserLogout))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public async Task ApiRequestBeforeAndAfterLogoutTest() + { + var cookie = FetchAndAssertCookieFromBrowser(); + var headers = HttpClientUtility.CreateCookieHeader(pipelineConfigs.CookieName, cookie.CookieValue); + + Report.Step(@"API call with cookie taken from browser", @"Should get the valid upstream response"); + var responseBeforeLogout = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, defaultEndpointUrl, headers, null); + AssertTest.IsTrue(responseBeforeLogout != null && responseBeforeLogout["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + + Report.Step(@"Call Logout api", @"should be redirected to the login page"); + AssertLogoutSuccess(); + + Report.Step(@"API call with older cookie taken from browser before logout", @"Should receive the HTML response of the IAM Login page"); + var responseAfterLogout = await HttpClientUtility.ExecuteAsync(HttpMethod.Get, defaultEndpointUrl, headers, null); + var body = await responseAfterLogout?.Content?.ReadAsStringAsync(); + //Assuming login page is HSDP IAM's Login page + AssertTest.IsTrue(responseAfterLogout != null && responseAfterLogout.IsSuccessStatusCode && body != null && body.Contains("<title>Philips"), failMsg: "IAM login page's title is not displayed", passMsg: "IAM login page's title is displayed"); + + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.BrowserLogout))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void BrowserRequestBeforeandAfterLogoutTest() + { + Report.Step(@"Browser request to the same endpoint", @"Should get the upstream response and login page should not be displayed"); + WebDriver.Goto(defaultEndpointUrl); + Assert.IsFalse(IsLoginPageDisplayed()); + var responseBody = GetResponseFromUI(); + AssertTest.IsTrue(responseBody != null && responseBody["status"].ToString().EqualsWithIgnoreCase("success"), failMsg: "No Success response received", passMsg: "Received Success response"); + + + Report.Step(@"Call Logout api", @"should be redirected to the login page"); + AssertLogoutSuccess(); + + Report.Step(@"Browser request to the default endpoint url", @"Should receive IAM Login page"); + WebDriver.Goto(defaultEndpointUrl); + //Assuming login page is HSDP IAM's Login page + AssertTest.IsTrue(IsLoginPageDisplayed(), + failMsg: "Not redirected back to login page", passMsg: "Redirected to login page"); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.BrowserLogout))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void LogoutAfterTheSessionTerminatedTest() + { + Report.Step(@"Call Logout api", @"should be redirected to the login page"); + AssertLogoutSuccess(); + Report.Step(@"Calling Logout api again", @"should be redirected to the login page"); + //If we don't specify any redirect-uri it will give 422 Http response code + AssertLogoutSuccess(); + } + + [TestCategory(nameof(TestCategory.Nightly))] + [TestCategory(nameof(TestCategory.WithoutMultitenancy))] + [TestCategory(nameof(TestCategory.BrowserLogout))] + [TestCategory(nameof(TestCategory.OnPrem))] + [TestMethod] + public void POST_HTTP_CallforLogoutAPITest() + { + Report.Step(@"Perform a POST Http call for Logout API", @"should be redirected to the login page"); + AssertTest.IsTrue(_commonSteps.LogoutWithPOST(defaultEndpointUrl, pipelineConfigs.AuthUserName, pipelineConfigs.AuthPassword),"Failed to Logout using PostCall", + "Successfully logged out and redirected to login page with POST call"); + } + + + #endregion Tests + + #region Private Methods + private static Cookies FetchAndAssertCookieFromBrowser() + { + Report.Step(@"Get cookie from browser", @"Should get cookie from the current page"); + var authenticationBL = new AuthenticationBL(WebDriver); + var cookie = authenticationBL.GetCookie(pipelineConfigs.CookieName); + AssertTest.IsTrue(cookie != null, failMsg: "Cookie is not available", passMsg: $"Cookie is available, Cookie value: {cookie.CookieValue}"); + return cookie; + } + + private void AssertLogoutSuccess() + { + string GatewayLogoutEndpoint = $"{pipelineConfigs.APIGatewayBaseUrl}{appConfigs.LogoutPath}"; + WebDriver.Goto(GatewayLogoutEndpoint); + AssertTest.IsTrue(IsLoginPageDisplayed(), + failMsg: "Logout unsuccessful, not redirected back to login page", passMsg: "Successfully logged out and redirected to login page"); + } + #endregion + } +} diff --git a/AutomationTest/Utilities/CFUtility/CFUtility.cs b/AutomationTest/Utilities/CFUtility/CFUtility.cs new file mode 100644 index 00000000..560651a2 --- /dev/null +++ b/AutomationTest/Utilities/CFUtility/CFUtility.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites.Models; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities; +using Utilities; +using Utilities.Wait; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites +{ + public class CFUtility + { + private readonly string _cfBaseUrl; + private readonly string _cfUserName; + private readonly string _cfPassword; + private readonly string _cfAccessTokenUrl; + + + public CFUtility(string cfBaseUrl, string cfUserName, string cfPassword, string cfAccessTokenUrl) + { + _cfBaseUrl = cfBaseUrl; + _cfUserName = cfUserName; + _cfPassword = cfPassword; + _cfAccessTokenUrl = cfAccessTokenUrl; + } + + public bool ChangingCFAppState(string orgName, string spaceName, string appName, AppState appState) + { + try + { + string appGuid = GetCFAppGuid(orgName, spaceName, appName); + string postUrl = $"{_cfBaseUrl}/apps/{appGuid }/actions/{appState.ToString()}"; + Logger.Info($"CF app state Url: { postUrl}"); + using (var apiResponse = HttpClientUtility.ExecuteAsync(HttpMethod.Post, postUrl, CFRequestHeadersWithAuth(), null).Result) + { + if (!apiResponse.IsSuccessStatusCode || apiResponse.Content == null) + { + Logger.Error("Response Code :" + apiResponse.StatusCode.ToString()); + Logger.Error($"Failed to change the state of the service to {appState.ToString()} response for : {postUrl}"); + return false; + } + bool appCurrentStatus = false; + if (appState == AppState.start || appState == AppState.restart) + { + appCurrentStatus = WaitUtils.WaitUntil(() => GetCFAppStatus(appGuid).EqualsWithIgnoreCase("STARTED"), CFConstants.AppStateChangeTimeoutInSeconds, CFConstants.AppStateChangeCheckFequencyInMilliSeconds); + } + else if (appState == AppState.stop) + { + appCurrentStatus = WaitUtils.WaitUntil(() => GetCFAppStatus(appGuid).EqualsWithIgnoreCase("STOPPED"), CFConstants.AppStateChangeTimeoutInSeconds, CFConstants.AppStateChangeCheckFequencyInMilliSeconds); + } + Sleep.Seconds(10); + return appCurrentStatus; + } + } + catch (Exception ex) + { + Logger.Error(ex.Message); + return false; + } + } + + public Dictionary GetEnvironmentVariablesFromCFApp(string orgName, string spaceName, string appName) + { + try + { + string appGuid = GetCFAppGuid(orgName, spaceName, appName); + string getUrl = $"{_cfBaseUrl}/apps/{appGuid}/environment_variables"; + Logger.Info($"CF Get env url: {getUrl}"); + using (HttpResponseMessage apiResponse = HttpClientUtility.ExecuteAsync(HttpMethod.Get, getUrl, CFRequestHeadersWithAuth(), null).Result) + { + if (!apiResponse.IsSuccessStatusCode || apiResponse.Content == null) + { + Logger.Error("Response Code :" + apiResponse.StatusCode.ToString()); + Logger.Error($"Failed toget env variables of the application: {appName}"); + + return null; + } + return GetEnvironmentVariablesFromResponse(apiResponse); + + } + } + catch (Exception ex) + { + Logger.Error(ex.Message); + return null; + } + } + public Dictionary UpdateEnvironmentVariablesToCFApp(string orgName, string spaceName, string appName, Dictionary envVariables) + { + try + { + string appGuid = GetCFAppGuid(orgName, spaceName, appName); + string updateUrl = $"{_cfBaseUrl}/apps/{appGuid}/environment_variables"; + Logger.Info($"CF update env url: {updateUrl}"); + var envVariablesContent = new CFEnvironmentVariable() + { + EnvironmentVariables = envVariables + }; + + string json = JsonConvert.SerializeObject(envVariablesContent); + + using (HttpResponseMessage apiResponse = PatchEnvironmentVariablesToApp(updateUrl, json)) + { + if (!apiResponse.IsSuccessStatusCode || apiResponse.Content == null) + { + Logger.Error("Response Code :" + apiResponse.StatusCode.ToString()); + Logger.Error($"Failed to update env variables for the application: {appName}"); + return null; + } + ChangingCFAppState(orgName, spaceName, appName, AppState.restart); + return GetEnvironmentVariablesFromResponse(apiResponse); + } + } + catch (Exception ex) + { + Logger.Error(ex.Message); + return null; + } + } + + #region private methods + private string GetOrganizationGuid(string orgName) + { + string getOrgsUrl = $"{_cfBaseUrl}/organizations"; + Logger.Info($"Get Orgs url: {getOrgsUrl}"); + return GetResourceGuid(getOrgsUrl, orgName); ; + } + + private string GetSpaceGuid(string orgGuid, string spaceName) + { + string getSpacesUrl = $"{_cfBaseUrl}/spaces?organization_guids={orgGuid}&page=2&per_page=50"; + Logger.Info($"Get spaces url: {getSpacesUrl}"); + return GetResourceGuid(getSpacesUrl, spaceName); + } + + private string GetResourceGuid(string getUrl, string name) + { + var spacesList = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, getUrl, CFRequestHeadersWithAuth(), null)["resources"]; + var spaceGuid = spacesList.Where(x => x["name"].ToString().Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault()["guid"].ToString(); + return spaceGuid; + } + + private string GetCFAppGuid(string orgName, string spaceName, string appName) + { + string orgGuid = GetOrganizationGuid(orgName); + string spaceGuid = GetSpaceGuid(orgGuid, spaceName); + + string getAppsUrl = $"{_cfBaseUrl}/apps?organization_guids={orgGuid}&space_guids={spaceGuid}&names={appName}"; + Logger.Info($"Get apps url: {getAppsUrl}"); + return GetResourceGuid(getAppsUrl, appName); + } + + private string GetCFAppStatus(string cfAppGuid) + { + string postUrl = _cfBaseUrl + "/apps/" + cfAppGuid; + var apiResponse = HttpClientUtility.ExecuteAndGetResponse(HttpMethod.Get, postUrl, CFRequestHeadersWithAuth(), null); + return apiResponse["state"].ToString(); + } + + private CFTokenResponse GetCFOauthToken() + { + var postData = new[] + { + new KeyValuePair("grant_type","password"), + new KeyValuePair("username",_cfUserName), + new KeyValuePair("password",_cfPassword) + }; + Logger.Info($"CF access token Url: {_cfAccessTokenUrl}"); + var token = PostFormUrlEncoded(_cfAccessTokenUrl, postData).Result; + return token; + } + + private async Task PostFormUrlEncoded(string url, IEnumerable> postData) where T : class + { + using (var httpClient = new HttpClient()) + { + string authInfo = "cf" + ":" + ""; + authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo); + + using (var content = new FormUrlEncodedContent(postData)) + { + HttpResponseMessage response = await httpClient.PostAsync(url, content); + string apiResponseString = response.Content.ReadAsStringAsync().Result; + var tokenResponse = JsonConvert.DeserializeObject(apiResponseString); + return tokenResponse; + } + } + } + private Dictionary CFRequestHeadersWithAuth() + { + Dictionary headers = new Dictionary(); + var authToken = GetCFOauthToken(); + if (!string.IsNullOrEmpty(authToken.AccessToken.ToString())) + { + headers.Add("Authorization", "Bearer " + authToken.AccessToken); + } + headers.Add("Content-Type", "application/json"); + headers.Add("Accept", "application/json"); + headers.Add("api-version", "1"); + return headers; + } + + private static Dictionary GetEnvironmentVariablesFromResponse(HttpResponseMessage apiResponse) + { + var envVariables = JObject.Parse(apiResponse.Content.ReadAsStringAsync().Result)["var"].ToString(); + return JsonConvert.DeserializeObject>(envVariables); + } + + private HttpResponseMessage PatchEnvironmentVariablesToApp(string url, string content) + { + var httpClient = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Patch, url); + request.Content = new StringContent(content, Encoding.UTF8, "application/json"); + var authToken = GetCFOauthToken(); + if (!string.IsNullOrEmpty(authToken.AccessToken.ToString())) + { + request.Headers.Add("Authorization", "Bearer " + authToken.AccessToken); + } + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.Add("api-version", "1"); + return httpClient.SendAsync(request).Result; + } + #endregion + } +} diff --git a/AutomationTest/Utilities/CFUtility/Models/AppState.cs b/AutomationTest/Utilities/CFUtility/Models/AppState.cs new file mode 100644 index 00000000..7df561cc --- /dev/null +++ b/AutomationTest/Utilities/CFUtility/Models/AppState.cs @@ -0,0 +1,9 @@ +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites.Models +{ + public enum AppState + { + start, + stop, + restart + } +} diff --git a/AutomationTest/Utilities/CFUtility/Models/CFConstants.cs b/AutomationTest/Utilities/CFUtility/Models/CFConstants.cs new file mode 100644 index 00000000..0064c699 --- /dev/null +++ b/AutomationTest/Utilities/CFUtility/Models/CFConstants.cs @@ -0,0 +1,8 @@ +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites +{ + public class CFConstants + { + public const int AppStateChangeTimeoutInSeconds = 30; + public const int AppStateChangeCheckFequencyInMilliSeconds = 500; + } +} diff --git a/AutomationTest/Utilities/CFUtility/Models/CFEnvironmentVariable.cs b/AutomationTest/Utilities/CFUtility/Models/CFEnvironmentVariable.cs new file mode 100644 index 00000000..81fe93bd --- /dev/null +++ b/AutomationTest/Utilities/CFUtility/Models/CFEnvironmentVariable.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites.Models +{ + public class CFEnvironmentVariable + { + [JsonProperty("var")] + public Dictionary EnvironmentVariables { get; set; } + } +} diff --git a/AutomationTest/Utilities/CFUtility/Models/CFTokenResponse.cs b/AutomationTest/Utilities/CFUtility/Models/CFTokenResponse.cs new file mode 100644 index 00000000..16ae43ee --- /dev/null +++ b/AutomationTest/Utilities/CFUtility/Models/CFTokenResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.CFUtilites.Models +{ + + public class CFTokenResponse + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + } + +} diff --git a/AutomationTest/Utilities/ConfigurationReader.cs b/AutomationTest/Utilities/ConfigurationReader.cs new file mode 100644 index 00000000..fa882edc --- /dev/null +++ b/AutomationTest/Utilities/ConfigurationReader.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.IO; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities +{ + public class ConfigurationReader + { + public IConfiguration Configuration { get; } + + public ConfigurationReader(string jsonFilePath) + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile(jsonFilePath); + Configuration = builder.Build(); + } + + public ConfigurationReader() + { + var builder = new ConfigurationBuilder(); + builder.AddEnvironmentVariables(); + Configuration = builder.Build(); + } + } +} diff --git a/AutomationTest/Utilities/Extensions.cs b/AutomationTest/Utilities/Extensions.cs new file mode 100644 index 00000000..00613a8b --- /dev/null +++ b/AutomationTest/Utilities/Extensions.cs @@ -0,0 +1,56 @@ +using Driver.UI.Interfaces; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Tests.Common; +using System; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities +{ + public static class Extensions + { + public static bool EqualsWithIgnoreCase(this string actual, string expected, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) + { + Logger.InfoStartMethod(); + try + { + return actual.Equals(expected, stringComparison); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public static bool StartsWithIgnoreCase(this string actual, string expected, StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase) + { + Logger.InfoStartMethod(); + try + { + return actual.StartsWith(expected, stringComparison); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public static string TakeScreenshot(this IWebDriverUi webDriver) + { + try + { + string evidenceFolderPath = BaseTest.Report.GetEvidencePath(); + string newEvidenceFolderName = BaseTest.Report.GetNewEvidenceFolderName(); + string newEvidenceFilePath = string.Format("{0}/{1}.png", evidenceFolderPath, newEvidenceFolderName); + webDriver.TakesScreenShot(newEvidenceFilePath); + return newEvidenceFilePath; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + } +} + diff --git a/AutomationTest/Utilities/HttpClientUtility.cs b/AutomationTest/Utilities/HttpClientUtility.cs new file mode 100644 index 00000000..448acc7b --- /dev/null +++ b/AutomationTest/Utilities/HttpClientUtility.cs @@ -0,0 +1,147 @@ + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Driver.Api.HttpClientApi; +using Newtonsoft.Json.Linq; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Utilities; +using Utilities.Common; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities +{ + public class HttpClientUtility + { + private static readonly HttpClient _httpClient = new HttpClient(); + private static readonly HttpClient _httpClientWithoutRedirection = new HttpClient( new HttpClientHandler { AllowAutoRedirect = false }); + private static readonly HttpClientUtilities _httpClientUtility = new HttpClientUtilities(); + + public static async Task ExecuteAsync(HttpMethod httpMethod, string url, Dictionary headers, HttpContent content) + { + return await ExecuteAsync(_httpClient, httpMethod, url, headers, content); + } + + public static async Task ExecuteAsyncWithoutHttpRedirection(HttpMethod httpMethod, string url, Dictionary headers, HttpContent content) + { + return await ExecuteAsync(_httpClientWithoutRedirection, httpMethod, url, headers, content); + } + + private static async Task ExecuteAsync(HttpClient client,HttpMethod httpMethod, string url, Dictionary headers, HttpContent content) + { + try + { + return (httpMethod.ToString()) switch + { + "POST" => await _httpClientUtility.HttpPostAsync(client, url, headers, content), + "PUT" => await _httpClientUtility.HttpPutAsync(client, url, headers, content), + "DELETE" => await _httpClientUtility.HttpDeleteAsync(client, url, headers), + _ => await _httpClientUtility.HttpGetAsyncResp(client, url, headers),//GET + }; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + } + + return null; + } + + public static StringContent CreateHttpContent(string content, string MediaType = "application/json") + { + Logger.InfoStartMethod(); + try + { + return new StringContent(content, Encoding.UTF8, MediaType); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public static Dictionary CreateCookieHeader(string cookieName, string cookieValue) + { + Logger.InfoStartMethod(); + try + { + Dictionary cookieHeader = new Dictionary + { + { "Cookie", $"{cookieName}={cookieValue}" } + }; + return cookieHeader; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public static Dictionary CreateUserAccessTokenHeader(PipelineConfiguration pipelineConfiguration, string tokenType = "access_token") + { + Logger.InfoStartMethod(); + try + { + Logger.Info($"Access token url: {pipelineConfiguration.IAMAuthorizationUrl}"); + var headers = new Dictionary(); + headers.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes($"{pipelineConfiguration.OauthClientID}:{pipelineConfiguration.OauthClientSecret}"))}"); + string content = $"grant_type=password&username={pipelineConfiguration.AuthUserName}&password={pipelineConfiguration.AuthPassword}"; + var httpContent = CreateHttpContent(content, "application/x-www-form-urlencoded"); + var responseBody = ExecuteAndGetResponse(HttpMethod.Post, pipelineConfiguration.IAMAuthorizationUrl, headers, httpContent); + string authToken = responseBody[tokenType]?.ToString(); + if(authToken == null) + { + return null; + } + var userAccessTokenHeader = new Dictionary(); + userAccessTokenHeader.Add("Authorization", $"Bearer {authToken}"); + return userAccessTokenHeader; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public static Dictionary CreateServiceIdAccessTokenHeader(PipelineConfiguration pipelineConfig) + { + Logger.InfoStartMethod(); + try + { + string serviceIDAccessToken = CommonFunctionality.GetAccessToken(pipelineConfig.ServiceID, pipelineConfig.ServiceIDPrivateKey, pipelineConfig.IAMAccessTokenUrl, pipelineConfig.IAMAuthorizationUrl); + var serviceIDAccessTokenHeader = new Dictionary(); + serviceIDAccessTokenHeader.Add("Authorization", $"Bearer {serviceIDAccessToken}"); + return serviceIDAccessTokenHeader; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + + public static JObject ExecuteAndGetResponse(HttpMethod httpMethod, string url, Dictionary headers, HttpContent content) + { + Logger.InfoStartMethod(); + try + { + var response = ExecuteAsync(httpMethod, url, headers, content).Result.Content.ReadAsStringAsync().Result; + if (string.IsNullOrEmpty(response)) + { + return null; + } + return JObject.Parse(response); + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + } +} diff --git a/AutomationTest/Utilities/Settings.cs b/AutomationTest/Utilities/Settings.cs new file mode 100644 index 00000000..f0d22095 --- /dev/null +++ b/AutomationTest/Utilities/Settings.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Philips.EDI.Foundation.APIGateway.AutomationTest.Models; +using Utilities; + +namespace Philips.EDI.Foundation.APIGateway.AutomationTest.Utilities +{ + public class Settings + { + private const string _envConfigFile = "Env.json"; + + private static readonly ConfigurationReader _envConfigReader = new ConfigurationReader( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), _envConfigFile)); + + private static readonly string _executionEnvironment = _envConfigReader.Configuration.GetSection("ExecutionEnvironment").Value; + private static ConfigurationReader _environmentVairableConfigReader; + + public static T GetConfiguration(string configSectionName) where T : new() + { + try + { + Logger.Info($"Config section name: {configSectionName}"); + T config = new T(); + + if (configSectionName.StartsWith("AppConfiguration", StringComparison.InvariantCultureIgnoreCase) || _executionEnvironment.Equals(nameof(ExecutionEnvironment.Local), StringComparison.InvariantCultureIgnoreCase)) + { + _envConfigReader.Configuration.GetSection(configSectionName).Bind(config); + } + else if (_executionEnvironment.Equals(nameof(ExecutionEnvironment.Production), StringComparison.InvariantCultureIgnoreCase)) + { + _environmentVairableConfigReader = new ConfigurationReader(); + _environmentVairableConfigReader.Configuration.Bind(config); + } + return config; + } + catch (Exception ex) + { + Logger.InfoFailedWithException(ex); + throw; + } + } + } +} diff --git a/AutomationTest/automation.packages.proj b/AutomationTest/automation.packages.proj new file mode 100644 index 00000000..890eae96 --- /dev/null +++ b/AutomationTest/automation.packages.proj @@ -0,0 +1,9 @@ + + + netcoreapp3.1 + + + + + + \ No newline at end of file diff --git a/AutomationTest/log4net.config b/AutomationTest/log4net.config new file mode 100644 index 00000000..f0fd3f3f --- /dev/null +++ b/AutomationTest/log4net.config @@ -0,0 +1,27 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/deploy/CD/api-gateway/terragrunt.hcl b/deploy/CD/api-gateway/terragrunt.hcl new file mode 100644 index 00000000..f6a1ee95 --- /dev/null +++ b/deploy/CD/api-gateway/terragrunt.hcl @@ -0,0 +1,52 @@ +include { + path = find_in_parent_folders() +} + +terraform { + source = "../../dependent_modules//api-gateway" +} + +inputs = { + + proposition_id = dependency.core.outputs.foundation_envoy_proposition_id + cf_org = "client-EDI-SolutionAccelerator" + cf_space = "envoyci" + cf_deploy_user = dependency.core.outputs.cf_deploy_user + cf_deploy_password = dependency.core.outputs.cf_deploy_password + cf_domain = "us-east.philips-healthsuite.com" + redis_credentials = dependency.cloudfoundry.outputs.redis_credentials + logdrainer = dependency.cloudfoundry.outputs.logdrainer_service_id + oauth_proxy_redis = dependency.cloudfoundry.outputs.redis_service_id + org_id_source = get_env("SOURCE", "url") + config_file_location = get_env("CONFIG", "./envoyconfig_without_multitenancy.yml") + envoy_tag = dependency.core.outputs.envoy_tag + oauth_tag = dependency.core.outputs.oauth_tag + authenticator_tag = dependency.core.outputs.authenticator_tag + tokenexchange_tag = dependency.core.outputs.tokenexchange_tag +} + +dependency "core" { + config_path = "../core/" +} + +dependency "cloudfoundry" { + config_path = "../cloudfoundry/" +} + +generate "provider" { + path = "provider.tf" + if_exists = "overwrite_terragrunt" + contents = <