diff --git a/.azuredevops/pipelines/build-no-tests.yml b/.azuredevops/pipelines/build-no-tests.yml
index 7dc9ddc..0a5cf0d 100644
--- a/.azuredevops/pipelines/build-no-tests.yml
+++ b/.azuredevops/pipelines/build-no-tests.yml
@@ -66,7 +66,7 @@ steps:
displayName: 'Install dotnet-ef'
condition: always()
inputs:
- script: 'dotnet tool install --global dotnet-ef'
+ script: 'dotnet tool install --version 7.0.13 --global dotnet-ef'
- task: CmdLine@2
displayName: 'Check dotnet-ef version'
diff --git a/.azuredevops/pipelines/build-v2.yml b/.azuredevops/pipelines/build-v2.yml
index e08653c..b129292 100644
--- a/.azuredevops/pipelines/build-v2.yml
+++ b/.azuredevops/pipelines/build-v2.yml
@@ -8,6 +8,7 @@ variables:
containerTag: develop
baseSourceDirectory: $(Build.SourcesDirectory)/Source
+ dockerComposeDirectory: $(baseSourceDirectory)/DockerCompose
trigger:
- develop
@@ -51,7 +52,7 @@ jobs:
docker pull $(AcrBaseUrl).azurecr.io/mock-data-holder-energy:$(containerTag)
docker tag $(AcrBaseUrl).azurecr.io/mock-register:$(containerTag) mock-register:latest
docker tag $(AcrBaseUrl).azurecr.io/mock-data-holder:$(containerTag) mock-data-holder:latest
- docker tag $(AcrBaseUrl).azurecr.io/mock-data-holder-energy:$(containerTag) mock-data-holder-energy:latest
+ docker tag $(AcrBaseUrl).azurecr.io/mock-data-holder-energy:$(containerTag) mock-data-holder-energy:latest
# List docker images
- task: Docker@2
@@ -61,48 +62,37 @@ jobs:
command: images
# Run unit tests
- - task: DockerCompose@0
- displayName: Unit tests - Up
- inputs:
- action: Run a Docker Compose command
- dockerComposeFile: $(baseSourceDirectory)/docker-compose.UnitTests.yml
- dockerComposeCommand: up --abort-on-container-exit --exit-code-from mock-data-recipient-unit-tests
+ - script: |
+ docker compose --file $(dockerComposeDirectory)/docker-compose.UnitTests.yml up --abort-on-container-exit --exit-code-from mock-data-recipient-unit-tests
+ displayName: Unit Tests - Up
+ condition: always()
# Remove unit tests
- - task: DockerCompose@0
- displayName: Unit tests - Down
- condition: always()
- inputs:
- action: Run a Docker Compose command
- dockerComposeFile: $(baseSourceDirectory)/docker-compose.UnitTests.yml
- dockerComposeCommand: down
+ - script: |
+ docker compose --file $(dockerComposeDirectory)/docker-compose.UnitTests.yml down
+ displayName: 'Unit Tests - Down'
+ condition: always()
# Run integration tests
- - task: DockerCompose@0
+ - script: |
+ docker compose --file $(dockerComposeDirectory)/docker-compose.IntegrationTests.yml up --abort-on-container-exit --exit-code-from mock-data-recipient-integration-tests
displayName: Integration tests - Up
condition: always()
- inputs:
- action: Run a Docker Compose command
- dockerComposeFile: $(baseSourceDirectory)/docker-compose.IntegrationTests.yml
- dockerComposeCommand: up --abort-on-container-exit --exit-code-from mock-data-recipient-integration-tests
# Output Docker Logs
- script: |
- docker logs mock-register
- docker logs mock-data-holder
- docker logs mock-data-holder-energy
- docker logs mock-data-recipient
+ docker logs mock-register-mdr-int
+ docker logs mock-data-holder-mdr-int
+ docker logs mock-data-holder-energy-mdr-int
+ docker logs mock-data-recipient-mdr-int
displayName: 'Output Docker Logs'
condition: always()
# Remove integration tests
- - task: DockerCompose@0
- displayName: Integration tests - Down
- condition: always()
- inputs:
- action: Run a Docker Compose command
- dockerComposeFile: $(baseSourceDirectory)/docker-compose.IntegrationTests.yml
- dockerComposeCommand: down
+ - script: |
+ docker compose --file $(dockerComposeDirectory)/docker-compose.IntegrationTests.yml down
+ displayName: 'Integration tests - Down'
+ condition: always()
# Surface Integration tests TRX results to Devops
- task: PublishTestResults@2
@@ -111,37 +101,31 @@ jobs:
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/results.trx'
- searchFolder: $(baseSourceDirectory)/_temp/mock-data-recipient-integration-tests/testresults
+ searchFolder: $(dockerComposeDirectory)/_temp/mock-data-recipient-integration-tests/testresults
mergeTestResults: true
testRunTitle: 'mock-data-recipient-Integration-tests'
publishRunAttachments: true
# Run e2e tests
- - task: DockerCompose@0
+ - script: |
+ docker compose --file $(dockerComposeDirectory)/docker-compose.E2ETests.yml up --abort-on-container-exit --exit-code-from mock-data-recipient-e2e-tests
displayName: E2E tests - Up
condition: always()
- inputs:
- action: Run a Docker Compose command
- dockerComposeFile: $(baseSourceDirectory)/docker-compose.E2ETests.yml
- dockerComposeCommand: up --abort-on-container-exit --exit-code-from mock-data-recipient-e2e-tests
# Output Docker Logs
- script: |
- docker logs mock-register
- docker logs mock-data-holder
- docker logs mock-data-holder-energy
- docker logs mock-data-recipient
+ docker logs mock-register-mdr-e2e
+ docker logs mock-data-holder-mdr-e2e
+ docker logs mock-data-holder-energy-mdr-e2e
+ docker logs mock-data-recipient-mdr-e2e
displayName: 'Output Docker Logs'
condition: always()
# Remove e2e tests
- - task: DockerCompose@0
- displayName: E2E tests - Down
- condition: always()
- inputs:
- action: Run a Docker Compose command
- dockerComposeFile: $(baseSourceDirectory)/docker-compose.E2ETests.yml
- dockerComposeCommand: down
+ - script: |
+ docker compose --file $(dockerComposeDirectory)/docker-compose.E2ETests.yml down
+ displayName: 'E2E tests - Down'
+ condition: always()
# Surface E2E tests TRX results to Devops
- task: PublishTestResults@2
@@ -150,7 +134,7 @@ jobs:
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/results.trx'
- searchFolder: $(baseSourceDirectory)/_temp/mock-data-recipient-e2e-tests/testresults
+ searchFolder: $(dockerComposeDirectory)/_temp/mock-data-recipient-e2e-tests/testresults
mergeTestResults: true
testRunTitle: 'mock-data-recipient-E2E-tests'
publishRunAttachments: true
@@ -173,36 +157,8 @@ jobs:
path: $(build.artifactstagingdirectory)
artifact: Container Images
- # FIXME - MJS - See dockercompose, volume no longer mapped as 1001:121 (vsts:docker) in build pipeline and causes issue with chown in dockerfile (appuser:appgroup), ie stops register from starting because of different user
- # # Publish mock-register logs
- # - publish: $(baseSourceDirectory)/_temp/mock-register/tmp
- # displayName: Publish MockRegister logs
- # condition: always()
- # artifact: Mock-Register - Logs
-
- # FIXME - MJS - See dockercompose, volume no longer mapped as 1001:121 (vsts:docker) in build pipeline and causes issue with chown in dockerfile (appuser:appgroup), ie stops register from starting because of different user
- # # Publish mock-data-holder logs
- # - publish: $(baseSourceDirectory)/_temp/mock-data-holder/tmp
- # displayName: Publish MockDataHolder logs
- # condition: always()
- # artifact: Mock-Data-Holder - Logs
-
- # FIXME - MJS - See dockercompose, volume no longer mapped as 1001:121 (vsts:docker) in build pipeline and causes issue with chown in dockerfile (appuser:appgroup), ie stops register from starting because of different user
- # # Publish mock-data-holder-energy logs
- # - publish: $(baseSourceDirectory)/_temp/mock-data-holder-energy/tmp
- # displayName: Publish MockDataHolder-Energy logs
- # condition: always()
- # artifact: Mock-Data-Holder-Energy - Logs
-
- # FIXME - MJS - See dockercompose, volume no longer mapped as 1001:121 (vsts:docker) in build pipeline and causes issue with chown in dockerfile (appuser:appgroup), ie stops register from starting because of different user
- # # Publish mock-data-recipient logs
- # - publish: $(baseSourceDirectory)/_temp/mock-data-recipient/tmp
- # displayName: Publish MockDataRecipient logs
- # condition: always()
- # artifact: Mock-Data-Recipient - Logs
-
# Publish mock-data-recipient unit tests results
- - publish: $(baseSourceDirectory)/_temp/mock-data-recipient-unit-tests/testresults
+ - publish: $(dockerComposeDirectory)/_temp/mock-data-recipient-unit-tests/testresults
displayName: Publish unit tests
condition: always()
artifact: Mock-Data-Recipient - Unit tests
@@ -210,14 +166,14 @@ jobs:
# Run trx formatter to output .MD and .CSV
- script: |
docker run \
- -v=$(baseSourceDirectory)/_temp/mock-data-recipient-integration-tests/testresults/results.trx:/app/results.trx:ro \
- -v=$(baseSourceDirectory)/_temp/mock-data-recipient-integration-tests/testresults/formatted/:/app/out/:rw \
+ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-integration-tests/testresults/results.trx:/app/results.trx:ro \
+ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-integration-tests/testresults/formatted/:/app/out/:rw \
$(AcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MDR" --outputprefix "MDR" -o out/
displayName: 'Run trx-formatter for integration tests'
condition: always()
# Publish mock-data-recipient integration tests results
- - publish: $(baseSourceDirectory)/_temp/mock-data-recipient-integration-tests/testresults
+ - publish: $(dockerComposeDirectory)/_temp/mock-data-recipient-integration-tests/testresults
displayName: Publish integration tests
condition: always()
artifact: Mock-Data-Recipient - Integration tests
@@ -225,14 +181,14 @@ jobs:
# Run trx formatter to output .MD and .CSV
- script: |
docker run \
- -v=$(baseSourceDirectory)/_temp/mock-data-recipient-e2e-tests/testresults/results.trx:/app/results.trx:ro \
- -v=$(baseSourceDirectory)/_temp/mock-data-recipient-e2e-tests/testresults/formatted/:/app/out/:rw \
+ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-e2e-tests/testresults/results.trx:/app/results.trx:ro \
+ -v=$(dockerComposeDirectory)/_temp/mock-data-recipient-e2e-tests/testresults/formatted/:/app/out/:rw \
$(AcrBaseUrl).azurecr.io/trx-formatter -i results.trx -t "MDR-E2E" --outputprefix "MDR-E2E" -o out/
displayName: 'Run trx-formatter for E2E tests'
condition: always()
# Publish mock-data-recipient e2e tests results
- - publish: $(baseSourceDirectory)/_temp/mock-data-recipient-e2e-tests/testresults
+ - publish: $(dockerComposeDirectory)/_temp/mock-data-recipient-e2e-tests/testresults
displayName: Publish e2e tests
condition: always()
artifact: Mock-Data-Recipient - E2E tests
@@ -249,7 +205,7 @@ jobs:
displayName: 'Install dotnet-ef'
condition: always()
inputs:
- script: 'dotnet tool install --global dotnet-ef'
+ script: 'dotnet tool install --version 7.0.13 --global dotnet-ef'
- task: CmdLine@2
displayName: 'Check dotnet-ef version'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 45de3e7..eb3bd9e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -59,7 +59,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,9 +70,9 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v2
- # ℹ️ Command-line programs to run using the OS shell.
+ # ℹ️ Command-line programs to run using the OS shell
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
@@ -84,4 +84,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 66754d8..4cce631 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -78,3 +78,12 @@ jobs:
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
+
+ - name: Docker Hub Description
+ if: ${{ github.repository_owner == 'ConsumerDataRight' && github.ref_name == 'main' }}
+ uses: peter-evans/dockerhub-description@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ repository: ${{ env.DOCKER_IMAGE }}
+ enable-url-completion: true
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 0f9fd61..43666af 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -101,12 +101,12 @@ jobs:
# Run integration tests
- name: Run integration tests
run: |
- docker-compose -f './mock-data-recipient/Source/docker-compose.IntegrationTests.yml' up --abort-on-container-exit --exit-code-from mock-data-recipient-integration-tests
+ docker compose -f './mock-data-recipient/Source/DockerCompose/docker-compose.IntegrationTests.yml' up --abort-on-container-exit --exit-code-from mock-data-recipient-integration-tests
# Remove integration tests
- name: Remove integration tests
run: |
- docker-compose -f './mock-data-recipient/Source/docker-compose.IntegrationTests.yml' down
+ docker compose -f './mock-data-recipient/Source/DockerCompose/docker-compose.IntegrationTests.yml' down
# List docker images
- name: List Docker images
@@ -121,12 +121,12 @@ jobs:
# Run e2e tests
- name: Run e2e tests
run: |
- docker-compose -f './mock-data-recipient/Source/docker-compose.E2ETests.yml' up --abort-on-container-exit --exit-code-from mock-data-recipient-e2e-tests
+ docker compose -f './mock-data-recipient/Source/DockerCompose/docker-compose.E2ETests.yml' up --abort-on-container-exit --exit-code-from mock-data-recipient-e2e-tests
# Remove e2e tests
- name: Remove e2e tests
run: |
- docker-compose -f './mock-data-recipient/Source/docker-compose.E2ETests.yml' down
+ docker compose -f './mock-data-recipient/Source/DockerCompose/docker-compose.E2ETests.yml' down
# Build mock-data-recipient-unit-tests image
- name: Build the mock-data-recipient-unit-tests image
@@ -136,12 +136,12 @@ jobs:
# Run unit tests
- name: Run unit tests
run: |
- docker-compose -f './mock-data-recipient/Source/docker-compose.UnitTests.yml' up --abort-on-container-exit --exit-code-from mock-data-recipient-unit-tests
+ docker compose -f './mock-data-recipient/Source/DockerCompose/docker-compose.UnitTests.yml' up --abort-on-container-exit --exit-code-from mock-data-recipient-unit-tests
# Remove unit tests
- name: Remove unit tests
run: |
- docker-compose -f './mock-data-recipient/Source/docker-compose.UnitTests.yml' down
+ docker compose -f './mock-data-recipient/Source/DockerCompose/docker-compose.UnitTests.yml' down
# Archive unit test results
- name: Archive unit test results
@@ -149,7 +149,7 @@ jobs:
if: always()
with:
name: unit-test-results
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-data-recipient-unit-tests/testresults
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-data-recipient-unit-tests/testresults
# Archive integration test results
- name: Archive integration test results
@@ -157,7 +157,7 @@ jobs:
if: always()
with:
name: integration-test-results
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-data-recipient-integration-tests/testresults
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-data-recipient-integration-tests/testresults
# Archive e2e test results
- name: Archive e2e test results
@@ -165,7 +165,7 @@ jobs:
if: always()
with:
name: e2e-test-results
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-data-recipient-e2e-tests/testresults
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-data-recipient-e2e-tests/testresults
# Archive mock data recipient logs
- name: Archive mock data recipient logs
@@ -173,7 +173,7 @@ jobs:
if: always()
with:
name: integration-test-artifacts
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-data-recipient/tmp
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-data-recipient/tmp
# Archive mock data holder logs
- name: Archive mock data holder logs
@@ -181,7 +181,7 @@ jobs:
if: always()
with:
name: integration-test-artifacts
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-data-holder/tmp
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-data-holder/tmp
# Archive mock data holder energy logs
- name: Archive mock data holder energy logs
@@ -189,7 +189,7 @@ jobs:
if: always()
with:
name: integration-test-artifacts
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-data-holder-energy/tmp
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-data-holder-energy/tmp
# Archive mock register logs
- name: Archive mock register logs
@@ -197,4 +197,4 @@ jobs:
if: always()
with:
name: integration-test-artifacts
- path: ${{ github.workspace }}/mock-data-recipient/Source/_temp/mock-register/tmp
+ path: ${{ github.workspace }}/mock-data-recipient/Source/DockerCompose/_temp/mock-register/tmp
diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml
index 9f24148..e4b9503 100644
--- a/.github/workflows/sonarcloud-analysis.yml
+++ b/.github/workflows/sonarcloud-analysis.yml
@@ -31,17 +31,18 @@ jobs:
runs-on: windows-latest
steps:
- - name: Set up JDK 11
- uses: actions/setup-java@v1
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
if: ${{ env.sonarSecret != 0 }} # Only run scan if secret use is allowed - requires secrets to run successfully
with:
- java-version: 1.11
- - uses: actions/checkout@v2
+ java-version: 17
+ distribution: 'zulu' # Alternative distribution options are available.
+ - uses: actions/checkout@v3
if: ${{ env.sonarSecret != 0 }} # Only run scan if secret use is allowed - requires secrets to run successfully
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Cache SonarCloud packages
- uses: actions/cache@v2.1.6
+ uses: actions/cache@v3
if: ${{ env.sonarSecret != 0 }} # Only run scan if secret use is allowed - requires secrets to run successfully
with:
path: ~\sonar\cache
@@ -49,7 +50,7 @@ jobs:
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud scanner
id: cache-sonar-scanner
- uses: actions/cache@v2.1.6
+ uses: actions/cache@v3
if: ${{ env.sonarSecret != 0 }} # Only run scan if secret use is allowed - requires secrets to run successfully
with:
path: .\.sonar\scanner
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0803f22..9a1cdb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+## [1.2.5] - 2023-11-29
+### Fixed
+- Refactored code and minor bug fixes
+- The address property in the userinfo endpoint structure is updated from string to object. [Mock Data Recipient Issue 67](https://github.com/ConsumerDataRight/mock-data-recipient/issues/67)
+
## [1.2.4] - 2023-08-22
### Fixed
- Refactored code and fixed code smells
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 9768323..7501fdc 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -115,8 +115,8 @@ the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.0, available at
-[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
@@ -126,7 +126,7 @@ For answers to common questions about this code of conduct, see the FAQ at
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
-[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
diff --git a/Help/azurefunctions/HELP.md b/Help/azurefunctions/HELP.md
index afae373..b2c3431 100644
--- a/Help/azurefunctions/HELP.md
+++ b/Help/azurefunctions/HELP.md
@@ -44,8 +44,8 @@ azurite --silent --location c:\azurite --debug c:\azurite\debug.log
```
-navigate to .\mock-data-holder-energy\Source\CDR.GetDataRecipients
-func start --verbose
+navigate to .\mock-data-holder\Source\CDR.GetDataRecipients
+func start --verbose
```
diff --git a/Help/container/HELP.md b/Help/container/HELP.md
index 8d1c390..b233ddc 100644
--- a/Help/container/HELP.md
+++ b/Help/container/HELP.md
@@ -24,7 +24,7 @@ docker pull consumerdataright/mock-data-recipient:0.5.0
## Run a multi-container Mock CDR Ecosystem
-Multiple containers can be run concurrently to simulate a CDR ecosystem. The [Mock Register](https://github.com/ConsumerDataRight/mock-register), [Mock Data Holder](https://github.com/ConsumerDataRight/mock-data-holder), [Mock Data Holder Energy](https://github.com/ConsumerDataRight/mock-data-holder-energy) and [Mock Data Recipient](https://github.com/ConsumerDataRight/mock-data-recipient) containers can be run by using the `docker-compose.yml` file.
+Multiple containers can be run concurrently to simulate a CDR ecosystem. The [Mock Register](https://github.com/ConsumerDataRight/mock-register), Banking and Energy [Mock Data Holders](https://github.com/ConsumerDataRight/mock-data-holder) and [Mock Data Recipient](https://github.com/ConsumerDataRight/mock-data-recipient) containers can be run by using the `docker-compose.yml` file.
The Mock CDR Ecosystem relies on SQL Server for data persistence so the container has a dependency on the SQL Server container image provided by Microsoft.
diff --git a/README.md b/README.md
index f0acd06..10abca6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
-![Consumer Data Right Logo](https://raw.githubusercontent.com/ConsumerDataRight/mock-data-recipient/main/cdr-logo.png)
+![Consumer Data Right Logo](./cdr-logo.png?raw=true)
-[![Consumer Data Standards v1.25.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.25.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.25.0/#introduction)
-[![Conformance Test Suite 3.2](https://img.shields.io/badge/Conformance%20Test%20Suite-v3.2-darkblue.svg)](https://www.cdr.gov.au/for-providers/conformance-test-suite-data-recipients)
+[![Consumer Data Standards v1.27.0](https://img.shields.io/badge/Consumer%20Data%20Standards-v1.27.0-blue.svg)](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.27.0/#introduction)
[![made-with-dotnet](https://img.shields.io/badge/Made%20with-.NET-1f425Ff.svg)](https://dotnet.microsoft.com/)
[![made-with-csharp](https://img.shields.io/badge/Made%20with-C%23-1f425Ff.svg)](https://docs.microsoft.com/en-us/dotnet/csharp/)
[![MIT License](https://img.shields.io/github/license/ConsumerDataRight/mock-data-recipient)](./LICENSE)
@@ -14,12 +13,11 @@ This repository contains a mock implementation of a Data Recipient and is offere
## Mock Data Recipient - Alignment
The Mock Data Recipient in this release:
-* aligns to [v1.25.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.25.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.25.0/#introduction);
-* passed v3.2 of the [Conformance Test Suite for Data Recipients](https://www.cdr.gov.au/for-providers/conformance-test-suite-data-recipients); and
+* aligns to [v1.27.0](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.27.0/#introduction) of the [Consumer Data Standards](https://consumerdatastandardsaustralia.github.io/standards-archives/standards-1.27.0/#introduction);
* can connect to and complete authentication against both [FAPI 1.0 Migration Phase 2 and Phase 3](https://consumerdatastandardsaustralia.github.io/standards/#authentication-flows) compliant data holders.
## Getting Started
-The Mock Data Recipient uses the [Mock Register](https://github.com/ConsumerDataRight/mock-register), the [Mock Data Holder](https://github.com/ConsumerDataRight/mock-data-holder) and the [Mock Data Holder Energy](https://github.com/ConsumerDataRight/mock-data-holder-energy). You can swap out any of the Mock Data Holders, Mock Data Register and Mock Data Recipient solutions with a solution of your own.
+The Mock Data Recipient uses the [Mock Register](https://github.com/ConsumerDataRight/mock-register) and the Banking and Energy [Mock Data Holders](https://github.com/ConsumerDataRight/mock-data-holder). You can swap out any of the Mock Data Holders, Mock Data Register and Mock Data Recipient solutions with a solution of your own.
There are a number of ways that the artefacts within this project can be used:
1. Build and deploy the source code
@@ -37,9 +35,9 @@ git clone https://github.com/ConsumerDataRight/mock-data-recipient.git
Set your debug profile to CDR.DataRecipient.Web
````
-To get help on launching and debugging the solution, see the [help guide](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Help/debugging/HELP.md).
+To get help on launching and debugging the solution, see the [help guide](./Help/debugging/HELP.md).
-If you would like to contribute features or fixes back to the Mock Data Recipient repository, consult the [contributing guidelines](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/CONTRIBUTING.md).
+If you would like to contribute features or fixes back to the Mock Data Recipient repository, consult the [contributing guidelines](./CONTRIBUTING.md).
### Use the pre-built image
@@ -51,28 +49,28 @@ A version of the Mock Data Recipient is built into a single Docker image that is
docker pull consumerdataright/mock-data-recipient
```
-To get help on launching and debugging the solutions as containers and switching out your solution(s), see the [help guide](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Help/container/HELP.md).
+To get help on launching and debugging the solutions as containers and switching out your solution(s), see the [help guide](./Help/container/HELP.md).
#### Certificate Management
-Consult the [Certificate Management](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/CertificateManagement/README.md) documentation for more information about how certificates are used for the Mock Data Recipient.
+Consult the [Certificate Management](./CertificateManagement/README.md) documentation for more information about how certificates are used for the Mock Data Recipient.
### Use the docker compose file to run a multi-container mock CDR Ecosystem
-The [docker compose file](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Source/DockerCompose/docker-compose.yml) can be used to run multiple containers from the Mock CDR Ecosystem.
+The [docker compose file](./Source/DockerCompose/docker-compose.yml) can be used to run multiple containers from the Mock CDR Ecosystem.
-**Note:** the [docker compose file](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Source/DockerCompose/docker-compose.yml) utilises the Microsoft SQL Server Image from Docker Hub. The Microsoft EULA for the Microsoft SQL Server Image must be accepted to use the [docker compose file](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Source/DockerCompose/docker-compose.yml). See the Microsoft SQL Server Image on Docker Hub for more information.
+**Note:** the [docker compose file](./Source/DockerCompose/docker-compose.yml) utilises the Microsoft SQL Server Image from Docker Hub. The Microsoft EULA for the Microsoft SQL Server Image must be accepted to use the [docker compose file](./Source/DockerCompose/docker-compose.yml). See the Microsoft SQL Server Image on Docker Hub for more information.
-To get help on launching and debugging the solutions as containers and switching out your solution(s), see the [help guide](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Help/container/HELP.md).
+To get help on launching and debugging the solutions as containers and switching out your solution(s), see the [help guide](./Help/container/HELP.md).
## Mock Data Recipient - Architecture
The following diagram outlines the high level architecture of the Mock Data Recipient:
-[
](https://raw.githubusercontent.com/ConsumerDataRight/mock-data-recipient/main/mock-data-recipient-architecture.png)
+![Mock Data Recipient - Architecture](./mock-data-recipient-architecture.png?raw=true)
Dynamic Client Registration Interface:
-[
](https://raw.githubusercontent.com/ConsumerDataRight/mock-data-recipient/main/mock-data-recipient-dcr-architecture.png)
+![Dynamic Client Registration Interface](./mock-data-recipient-dcr-architecture.png?raw=true)
## Mock Data Recipient - Components
The Mock Data Recipient contains the following components:
@@ -92,7 +90,7 @@ The Mock Data Recipient contains the following components:
- Azure Functions
- Azure Functions that can automate the continuous Get Data Holders discovery and Dynamic Client Registration process.
- For each Data Holder retrieved from the Register, a message will be added to the DynamicClietnRegistration queue. A function listening to the queue, will pick up the message and attempt to register the Data Recipient with the Data Holder.
- - To get help on the Azure Functions, see the [help guide](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Help/azurefunctions/HELP.md).
+ - To get help on the Azure Functions, see the [help guide](./Help/azurefunctions/HELP.md).
- Repository
- A SQL repository is included that contains local data used within the Mock Data Recipient.
- Includes the following collections:
@@ -108,21 +106,23 @@ The following technologies have been used to build the Mock Data Recipient:
# Testing
The Mock Data Recipient has been built as a test harness to demonstrate the interactions between the Register and Data Holders.
The Mock Data Recipient allows the end user to test the various interactions by changing input values, executing and viewing the response.
-The Mock Data Recipient requires a [Mock Register](https://github.com/ConsumerDataRight/mock-register),
-a [Mock Data Holder](https://github.com/ConsumerDataRight/mock-data-holder) and
-a [Mock Data Holder Energy](https://github.com/ConsumerDataRight/mock-data-holder-energy) to completely mimic the CDR Ecosystem.
- You can [swap out](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/Help/container/HELP.md) any of the Mock Data Holders, Mock Data Register and Mock Data Recipient solutions with a solution of your own.
+The Mock Data Recipient requires a [Mock Register](https://github.com/ConsumerDataRight/mock-register)
+and a Banking and Energy [Mock Data Holder](https://github.com/ConsumerDataRight/mock-data-holder) to completely mimic the CDR Ecosystem.
+ You can [swap out](./Help/container/HELP.md) any of the Mock Data Holders, Mock Data Register and Mock Data Recipient solutions with a solution of your own.
Consents and Authorisation flow of FAPI 1.0 Migration Phase 1 is no longer supported. Use the Pushed Authorisation Request (PAR) flow when testing against data holders that have implemented FAPI 1.0 Migration Phase 2 or Phase 3.
# Contribute
-We encourage contributions from the community. See our [contributing guidelines](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/CONTRIBUTING.md).
+We encourage contributions from the community. See our [contributing guidelines](./CONTRIBUTING.md).
# Code of Conduct
-This project has adopted the **Contributor Covenant**. For more information see the [code of conduct](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/CODE_OF_CONDUCT.md).
+This project has adopted the **Contributor Covenant**. For more information see the [code of conduct](./CODE_OF_CONDUCT.md).
+
+# Security Policy
+See our [security policy](./SECURITY.md) for information on security controls, reporting a vulnerability and supported versions.
# License
-[MIT License](https://github.com/ConsumerDataRight/mock-data-recipient/blob/main/LICENSE)
+[MIT License](./LICENSE)
# Notes
-The Mock Data Recipient is provided as a development tool only. It conforms to the Consumer Data Standards.
\ No newline at end of file
+The Mock Data Recipient is provided as a development tool only. It conforms to the Consumer Data Standards.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..084cf77
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,38 @@
+# Security Policy
+If you have discovered a potential security vulnerability within the [Consumer Data Right GitHub Organisation](https://github.com/ConsumerDataRight) or [Consumer Data Right Sandbox](https://cdrsandbox.gov.au/)
+operated by the ACCC, we encourage you to disclose it to us as quickly as possible and in a responsible manner in accordance with our [Responsible disclosure of security vulnerabilities policy](https://www.cdr.gov.au/resources/responsible-disclosure-security-vulnerabilities-policy).
+
+Visit our [Responsible disclosure of security vulnerabilities policy](https://www.cdr.gov.au/resources/responsible-disclosure-security-vulnerabilities-policy) for:
+ - A full view of our Responsible disclosure of security vulnerabilities policy
+ - Your responsibilities if you find a vulnerability
+ - Steps required for reporting a vulnerability
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 1.2.x | :white_check_mark: |
+| 1.1.x | :x: |
+| 1.0.x | :x: |
+
+
+## Reporting a Vulnerability
+Visit our [Responsible disclosure of security vulnerabilities policy](https://www.cdr.gov.au/resources/responsible-disclosure-security-vulnerabilities-policy) for steps required for reporting a vulnerability.
+
+
+## What controls are in place
+### SonarCloud
+Code repositories in [Consumer Data Right GitHub Organisation](https://github.com/ConsumerDataRight) utilise [SonarCloud](https://docs.sonarcloud.io/). Whenever a code change is made to this repository, GitHub actions are used to scan the code using SonarCloud.
+The SonarCloud results are then assessed. High impact issues, that are not false positives, will be remediated.
+ - [mock-register results](https://sonarcloud.io/project/overview?id=ConsumerDataRight_mock-register)
+ - [mock-data-holder results](https://sonarcloud.io/project/overview?id=ConsumerDataRight_mock-data-holder)
+ - [mock-data-holder-energy results](https://sonarcloud.io/project/overview?id=ConsumerDataRight_mock-data-holder-energy)
+ - [mock-data-recipient results](https://sonarcloud.io/project/overview?id=ConsumerDataRight_mock-data-recipient)
+ - [authorisation-server results](https://sonarcloud.io/project/overview?id=ConsumerDataRight_authorisation-server)
+ - [mock-solution-test-automation results](https://sonarcloud.io/project/overview?id=ConsumerDataRight_mock-solution-test-automation)
+
+### GitHub Security Features
+Code repositories in [Consumer Data Right GitHub Organisation](https://github.com/ConsumerDataRight) utilise [GitHub security features](https://docs.github.com/en/code-security/getting-started/github-security-features).
+
+### Keeping up to date
+Code repositories in [Consumer Data Right GitHub Organisation](https://github.com/ConsumerDataRight) are routinely updated with new features and dependency updates.
\ No newline at end of file
diff --git a/Source/.dockerignore b/Source/.dockerignore
index 742d166..b5e44e0 100644
--- a/Source/.dockerignore
+++ b/Source/.dockerignore
@@ -23,4 +23,5 @@
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
-README.md
\ No newline at end of file
+README.md
+_temp/
\ No newline at end of file
diff --git a/Source/CDR.DCR/CDR.DCR.csproj b/Source/CDR.DCR/CDR.DCR.csproj
index 31682a6..7d979dd 100644
--- a/Source/CDR.DCR/CDR.DCR.csproj
+++ b/Source/CDR.DCR/CDR.DCR.csproj
@@ -3,9 +3,9 @@
net6.0
v4
<_FunctionsSkipCleanOutput>true
-
1.2.4
-
1.2.4
-
1.2.4
+
1.2.5
+
1.2.5
+
1.2.5
diff --git a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj
index 1323b0e..2d62a1a 100644
--- a/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj
+++ b/Source/CDR.DataRecipient.API.Logger/CDR.DataRecipient.API.Logger.csproj
@@ -4,9 +4,9 @@
net6.0
enable
enable
- 1.2.4
- 1.2.4
- 1.2.4
+ 1.2.5
+ 1.2.5
+ 1.2.5
diff --git a/Source/CDR.DataRecipient.E2ETests/AATestPlaywrightInstallation.cs b/Source/CDR.DataRecipient.E2ETests/AATestPlaywrightInstallation.cs
index 67c8c34..33670eb 100644
--- a/Source/CDR.DataRecipient.E2ETests/AATestPlaywrightInstallation.cs
+++ b/Source/CDR.DataRecipient.E2ETests/AATestPlaywrightInstallation.cs
@@ -5,7 +5,7 @@
namespace CDR.DataRecipient.E2ETests
{
- public class AATestPlaywrightInstallation : BaseTest_v3, IClassFixture
+ public class AATestPlaywrightInstallation : BaseTest, IClassFixture
{
[Fact]
public async Task ShouldDisplayGoogleHomePage()
diff --git a/Source/CDR.DataRecipient.E2ETests/BaseTest.cs b/Source/CDR.DataRecipient.E2ETests/BaseTest.cs
index e39627b..8b5fb34 100644
--- a/Source/CDR.DataRecipient.E2ETests/BaseTest.cs
+++ b/Source/CDR.DataRecipient.E2ETests/BaseTest.cs
@@ -1,7 +1,4 @@
-#undef DEPRECATED // instead see BaseTest_v2
-#if DEPRECATED
-
-// #define TEST_DEBUG_MODE // Run Playwright in non-headless mode for debugging purposes (ie show a browser)
+#define TEST_DEBUG_MODE // Run Playwright in non-headless mode for debugging purposes (ie show a browser)
// In docker (Ubuntu container) Playwright will fail if running in non-headless mode, so we ensure TEST_DEBUG_MODE is undef'ed
#if !DEBUG
@@ -12,11 +9,13 @@
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using CDR.DataRecipient.E2ETests.Pages;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
@@ -31,11 +30,15 @@ namespace CDR.DataRecipient.E2ETests
[DisplayTestMethodName]
public class BaseTest
{
+ static public bool RUNNING_IN_CONTAINER => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER")?.ToUpper() == "TRUE";
+
// Customers
public const string CUSTOMERID_BANKING = "jwilson";
- public const string CUSTOMERACCOUNTS_BANKING = "Personal Loan xxx-xxx xxxxx987,Transactions and Savings Account xxx-xxx xxxxx988";
+ //public const string CUSTOMERACCOUNTS_BANKING = "Personal Loan xxx-xxx xxxxx987,Transactions and Savings Account xxx-xxx xxxxx988";
+ public const string CUSTOMERACCOUNTS_BANKING = "Personal Loan,Transactions and Savings Account";
+
public const string CUSTOMERID_ENERGY = "mmoss";
- public const string CUSTOMERACCOUNTS_ENERGY = "'ELECTRICITY ACCOUNT','ELECTRICITY ACCOUNT 2',ELECTRICITY ACCOUNT 3,ELECTRICITY ACCOUNT 4,ELECTRICITY ACCOUNT 5,ELECTRICITY ACCOUNT 6,ELECTRICITY ACCOUNT 7,ELECTRICITY ACCOUNT 8,ELECTRICITY ACCOUNT 9,ELECTRICITY ACCOUNT 10,ELECTRICITY ACCOUNT 11,ELECTRICITY ACCOUNT 12,ELECTRICITY ACCOUNT 13,ELECTRICITY ACCOUNT 14,ELECTRICITY ACCOUNT 15,ELECTRICITY ACCOUNT 16,ELECTRICITY ACCOUNT 17,ELECTRICITY ACCOUNT 18,ELECTRICITY ACCOUNT 19,ELECTRICITY ACCOUNT 20,ELECTRICITY ACCOUNT 21";
+ public const string CUSTOMERACCOUNTS_ENERGY = "ELECTRICITY ACCOUNT,ELECTRICITY ACCOUNT 2,ELECTRICITY ACCOUNT 3,ELECTRICITY ACCOUNT 4,ELECTRICITY ACCOUNT 5,ELECTRICITY ACCOUNT 6,ELECTRICITY ACCOUNT 7,ELECTRICITY ACCOUNT 8,ELECTRICITY ACCOUNT 9,ELECTRICITY ACCOUNT 10,ELECTRICITY ACCOUNT 11,ELECTRICITY ACCOUNT 12,ELECTRICITY ACCOUNT 13,ELECTRICITY ACCOUNT 14,ELECTRICITY ACCOUNT 15,ELECTRICITY ACCOUNT 16,ELECTRICITY ACCOUNT 17,ELECTRICITY ACCOUNT 18,ELECTRICITY ACCOUNT 19,ELECTRICITY ACCOUNT 20,ELECTRICITY ACCOUNT 21";
// Data Holder
public const string DH_BRANDID = "804fc2fb-18a7-4235-9a49-2af393d18bc7";
@@ -49,7 +52,7 @@ public class BaseTest
static public string REGISTER_MTLS_BaseURL => Configuration["MTLS_BaseURL"]
?? throw new Exception($"{nameof(REGISTER_MTLS_BaseURL)} - configuration setting not found");
- static public string REGISTER_IDENTITYSERVER_URL = REGISTER_MTLS_BaseURL + "/idp/connect/token";
+ public static readonly string REGISTER_IDENTITYSERVER_URL = REGISTER_MTLS_BaseURL + "/idp/connect/token";
// Client certificates
protected const string CERTIFICATE_FILENAME = "Certificates/client.pfx";
@@ -58,6 +61,7 @@ public class BaseTest
public const string JWT_CERTIFICATE_FILENAME = "Certificates/jwks.pfx";
public const string JWT_CERTIFICATE_PASSWORD = "#M0ckDataRecipient#";
+ public const string SHARING_DURATION = "100000";
public bool CreateMedia { get; set; } = true;
@@ -126,29 +130,91 @@ protected BaseTest()
}
private bool inArrange = false;
- protected delegate Task ArrangeDelegate();
- protected async Task Arrange(ArrangeDelegate arrange)
+ protected delegate Task ArrangeDelegate(IPage page);
+ protected async Task ArrangeAsync(string testName, ArrangeDelegate arrange)
{
if (inArrange)
return;
inArrange = true;
+
+ static void DeleteFile(string filename)
+ {
+ if (File.Exists(filename))
+ {
+ File.Delete(filename);
+ }
+ }
+
+ if (CreateMedia == true)
+ {
+ // Remove video/screens if they exist
+ DeleteFile($"{MEDIAFOLDER}/{testName}-arrange.webm");
+ }
+
try
{
- CreateMedia = false;
+ // Setup Playwright
+ using var playwright = await Playwright.CreateAsync();
+
+ // Setup browser
+ await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
+ {
+ SlowMo = 250,
+#if TEST_DEBUG_MODE
+ Headless = false,
+ Timeout = 5000 // DEBUG - 5 seconds
+#endif
+ });
+
+ // Setup browser context
+ var context = await browser.NewContextAsync(new BrowserNewContextOptions
+ {
+ IgnoreHTTPSErrors = true,
+ RecordVideoDir = CreateMedia == true ? $"{MEDIAFOLDER}" : null,
+ ViewportSize = new ViewportSize
+ {
+ Width = 1200,
+ Height = 1600
+ }
+ });
+
+ string? videoPath = null;
+ var page = await context.NewPageAsync();
try
{
- // PatchRegister(); // No longer needed - seed data is correct
- PurgeMDR();
- PurgeMDHIdentityServer();
- PurgeMDHEnergyIdentityServer();
- PurgeMDH();
- PurgeMDHE();
- await arrange();
+
+ page.Close += async (_, page) =>
+ {
+ // Page is closed, so save videoPath
+ if (CreateMedia == true)
+ {
+ if (page.Video != null)
+ {
+ videoPath = await page.Video.PathAsync();
+ }
+ }
+ };
+
+ using (new AssertionScope())
+ {
+ page.SetDefaultTimeout(TEST_TIMEOUT);
+ await arrange(page);
+ }
}
finally
{
- CreateMedia = CREATE_MEDIA;
+
+ await context.CloseAsync();
+ await browser.CloseAsync();
+ // Rename video file
+ if (CreateMedia == true)
+ {
+ if (videoPath != null)
+ {
+ File.Move(videoPath, $"{MEDIAFOLDER}/{testName}-arrange.webm");
+ }
+ }
}
}
finally
@@ -161,10 +227,7 @@ protected async Task Arrange(ArrangeDelegate arrange)
protected delegate Task CleanupDelegate(IPage page);
protected async Task CleanupAsync(CleanupDelegate cleanup)
{
- if (inArrange) // shouldn't cleanup if arranging
- return;
-
- if (inCleanup)
+ if (inArrange || inCleanup)
return;
inCleanup = true;
@@ -464,42 +527,10 @@ public static void PurgeMDR()
Purge(mdrConnection, "Registration");
}
- public static void PurgeMDH()
- {
- // using var mdhConnection = new SqlConnection(BaseTest.DATAHOLDER_CONNECTIONSTRING);
- // mdhConnection.Open();
- // Purge(mdhConnection, "Brand");
- // Purge(mdhConnection, "SoftwareProduct");
- }
-
- public static void PurgeMDHE()
- {
- // using var mdheConnection = new SqlConnection(BaseTest.DATAHOLDER_ENERGY_CONNECTIONSTRING);
- // mdheConnection.Open();
- // Purge(mdheConnection, "Brand");
- // Purge(mdheConnection, "SoftwareProduct");
- }
-
- // public static void PurgeMDR_CDRArrangements()
- // {
- // using var mdrConnection = new SqlConnection(BaseTest.DATARECIPIENT_CONNECTIONSTRING);
- // mdrConnection.Open();
- // Purge(mdrConnection, "CdrArrangement");
- // }
-
private static void PurgeIdentityServer(string connectionString)
{
using var identityServerConnection = new SqlConnection(connectionString);
identityServerConnection.Open();
-
- // Purge(identityServerConnection, "ApiResourceClaims");
- // Purge(identityServerConnection, "ApiResourceProperties");
- // Purge(identityServerConnection, "ApiResources");
- // Purge(identityServerConnection, "ApiResourceScopes");
- // Purge(identityServerConnection, "ApiResourceSecrets");
- // Purge(identityServerConnection, "ApiScopeClaims");
- // Purge(identityServerConnection, "ApiScopeProperties");
- // Purge(identityServerConnection, "ApiScopes");
Purge(identityServerConnection, "ClientClaims");
Purge(identityServerConnection, "ClientCorsOrigins");
Purge(identityServerConnection, "ClientGrantTypes");
@@ -510,10 +541,6 @@ private static void PurgeIdentityServer(string connectionString)
Purge(identityServerConnection, "Clients");
Purge(identityServerConnection, "ClientScopes");
Purge(identityServerConnection, "ClientSecrets");
- // Purge(identityServerConnection, "DeviceCodes");
- // Purge(identityServerConnection, "IdentityResourceClaims");
- // Purge(identityServerConnection, "IdentityResourceProperties");
- // Purge(identityServerConnection, "IdentityResources");
Purge(identityServerConnection, "PersistedGrants");
}
@@ -526,7 +553,237 @@ public static void PurgeMDHEnergyIdentityServer()
{
PurgeIdentityServer(BaseTest.DATAHOLDER_ENERGY_IDENTITYSERVER_CONNECTIONSTRING);
}
- }
-}
-#endif
\ No newline at end of file
+ static protected async Task DataHolders_Discover(IPage page, string industry = "ALL", string version = "2", int? expectedRecords = 32, string? expectedError = null)
+ {
+ // Arrange - Goto home page, click menu button, check page loaded
+ await page.GotoAsync(WEB_URL);
+ await page.Locator("a >> text=Discover Data Holders").ClickAsync();
+ await page.Locator("h2 >> text=Discover Data Holders").TextContentAsync();
+
+ // Arrange - Set industry
+ if (String.IsNullOrEmpty(industry)) // Clear industry
+ {
+ await page.Locator("select[name=\"Industry\"]").SelectOptionAsync(new SelectOptionValue[] { });
+ }
+ else
+ {
+ await page.Locator("select[name=\"Industry\"]").SelectOptionAsync(new[] { industry switch
+ {
+ // "" => "", // Doesn't work for clearing, use SelectOptionAsync(new SelectOptionValue[] { }) instead (see above)
+ "ALL" => "0",
+ "BANKING" => "1",
+ "ENERGY" => "2",
+ "TELCO" => "3",
+ _ => throw new ArgumentOutOfRangeException($"{nameof(industry)}")
+ }});
+ }
+
+ // Arrange - Set version
+ await page.Locator("input[name=\"Version\"]").FillAsync(version);
+
+ // Act - Click Refresh button
+ await page.Locator(@"h5:has-text(""Refresh Data Holders"") ~ div.card-body >> input:has-text(""Refresh"")").ClickAsync();
+
+ // Assert - Check refresh was successful
+ var footer = page.Locator(@"h5:has-text(""Refresh Data Holders"") ~ div.card-footer");
+ var text = await footer.InnerTextAsync();
+ if (expectedError != null)
+ {
+ text.Should().Be(expectedError);
+ }
+ else
+ {
+ if (expectedRecords != -1) // -1 = don't bother checking
+ {
+ text.Should().Be($"OK: {expectedRecords} data holder brands added. 0 data holder brands updated.");
+ }
+ }
+ }
+
+ static protected async Task SSA_Get(IPage page, string industry, string version, string drBrandId, string drSoftwareProductId, string expectedMessage)
+ {
+ // Arrange - Goto home page, click menu button, check page loaded
+ await page.GotoAsync(WEB_URL);
+ await page.Locator("a >> text=Get SSA").ClickAsync();
+ await page.Locator("h2 >> text=Get Software Statement Assertion").TextContentAsync();
+
+ // Set version
+ await page.Locator("input[name=\"Version\"]").FillAsync(version);
+ // Set brandId
+ await page.Locator("input[name=\"BrandId\"]").FillAsync(drBrandId);
+ // Set softwareProductId
+ await page.Locator("input[name=\"SoftwareProductId\"]").FillAsync(drSoftwareProductId);
+ // Set industry
+ await page.Locator("select[name=\"Industry\"]").SelectOptionAsync(new[] { industry switch
+ {
+ // "" => "", // Doesn't work for clearing, use SelectOptionAsync(new SelectOptionValue[] { }) instead (see above)
+ "ALL" => "0",
+ "BANKING" => "1",
+ "ENERGY" => "2",
+ "TELCO" => "3",
+ _ => throw new ArgumentOutOfRangeException($"{nameof(industry)}")
+ }});
+
+ // Act - Click Refresh button
+ await page.Locator(@"h5:has-text(""Get SSA"") ~ div.card-body >> input:has-text(""Get SSA"")").ClickAsync();
+
+ // Assert - Check refresh was successful, card-footer should be showing OK - SSA Generated
+ var footer = page.Locator(@"h5:has-text(""Get SSA"") ~ div.card-footer");
+ var text = await footer.InnerTextAsync();
+ text.Should().StartWith(expectedMessage);
+ }
+
+ // Create Client Registration returning DH client ID of client that was registered
+ static protected async Task ClientRegistration_Create(IPage page,
+ string dhBrandId,
+ string drBrandId,
+ string drSoftwareProductId,
+ string? jarmSigningAlgo = null,
+ string responseTypes = "code id_token",
+ string? jarmEncrypAlg = null,
+ string? jarmEncryptEnc = null)
+ {
+ // Arrange - Goto home page, click menu button, check page loaded
+ await page.GotoAsync(WEB_URL);
+ await page.Locator("a >> text=Dynamic Client Registration").ClickAsync();
+ await page.Locator("h2 >> text=Dynamic Client Registration").TextContentAsync();
+
+ // Set data holder brand id
+ await page.Locator("select[name=\"DataHolderBrandId\"]").SelectOptionAsync(new[] { dhBrandId });
+
+ // Assert - Check software product id
+ (await page.Locator("input[name=\"SoftwareProductId\"]").InputValueAsync()).Should().Be(drSoftwareProductId);
+
+ await page.Locator("input[name=\"ResponseTypes\"]").FillAsync(responseTypes);
+
+ if (jarmSigningAlgo != null)
+ {
+ await page.Locator("input[name=\"AuthorizationSignedResponseAlg\"]").FillAsync(jarmSigningAlgo);
+ }
+ if (jarmEncrypAlg != null)
+ {
+ await page.Locator("input[name=\"AuthorizationEncryptedResponseAlg\"]").FillAsync(jarmEncrypAlg);
+ }
+ if (jarmEncryptEnc != null)
+ {
+ await page.Locator("input[name=\"AuthorizationEncryptedResponseEnc\"]").FillAsync(jarmEncryptEnc);
+ }
+
+ // Act - Click create button
+ await page.Locator(@"h5:has-text(""Create Client Registration"") ~ div.card-body >> input:has-text(""Register"")").ClickAsync();
+
+ // Assert - Check client was registered
+ await page.Locator(@"h5:has-text(""Create Client Registration"") ~ div.card-footer:has-text(""Created - Registered"")").TextContentAsync();
+
+ // Assert - Get json result
+ var json = await page.Locator(@"h5:has-text(""Create Client Registration"") ~ div.card-footer >> pre").InnerTextAsync();
+
+ // Deserialise response and return DH client id
+ DCRResponse dcrResponse = JsonConvert.DeserializeObject(json) ?? throw new NullReferenceException(nameof(json));
+ return dcrResponse.ClientId ?? throw new NullReferenceException(nameof(dcrResponse.ClientId));
+ }
+
+ static protected async Task ConsentAndAuthorisation2(IPage page, string customerId = CUSTOMERID_BANKING, string customerAccounts = CUSTOMERACCOUNTS_BANKING)
+ {
+
+ ConsentAndAuthorisationPages consentAndAuthorisationPages = new ConsentAndAuthorisationPages(page);
+
+ await consentAndAuthorisationPages.EnterCustomerId(customerId);
+ await consentAndAuthorisationPages.ClickContinue();
+
+ await consentAndAuthorisationPages.EnterOtp("000789");
+ await consentAndAuthorisationPages.ClickContinue();
+
+ await consentAndAuthorisationPages.SelectAccounts(customerAccounts);
+ await consentAndAuthorisationPages.ClickContinue();
+
+ await consentAndAuthorisationPages.ClickAuthorise();
+
+ // Assert - Check callback is shown and get arrangement ID
+ await page.Locator("text=Consent and Authorisation - Callback").TextContentAsync();
+
+ return new ConsentAndAuthorisationResponse
+ {
+ IDToken = await page.Locator(@"dt:has-text(""Id Token"") + dd ").TextContentAsync(),
+ AccessToken = await page.Locator(@"dt:has-text(""Access Token"") + dd ").TextContentAsync(),
+ RefreshToken = await page.Locator(@"dt:has-text(""Refresh Token"") + dd ").TextContentAsync(),
+ ExpiresIn = await page.Locator(@"dt:has-text(""Expires In"") + dd ").TextContentAsync(),
+ Scope = await page.Locator(@"dt:has-text(""Scope"") + dd ").TextContentAsync(),
+ TokenType = await page.Locator(@"dt:has-text("" Token Type"") + dd ").TextContentAsync(),
+ CDRArrangementID = await page.Locator(@"dt:has-text(""CDR Arrangement Id"") + dd ").TextContentAsync()
+ };
+ }
+
+ static protected async Task NewConsentAndAuthorisationWithPAR(
+ IPage page,
+ string dhClientId,
+ string customerId = CUSTOMERID_BANKING,
+ string customerAccounts = CUSTOMERACCOUNTS_BANKING,
+ string dhBrandId = DH_BRANDID)
+ {
+ // Arrange - Goto home page, click menu button, check page loaded
+ await page.GotoAsync(WEB_URL);
+ ParPage parPage = new ParPage(page);
+ await parPage.GotoPar();
+ await parPage.CompleteParForm(dhClientId, dhBrandId, sharingDuration: SHARING_DURATION);
+ await parPage.ClickInitiatePar();
+ await parPage.ClickAuthorizeUrl();
+
+ return await ConsentAndAuthorisation2(page, customerId, customerAccounts);
+ }
+
+ private class DCRResponse
+ {
+ [JsonProperty("client_id")]
+ public string? ClientId { get; set; }
+ }
+ static protected async Task ClientRegistration_Delete(IPage page)
+ {
+ await page.GotoAsync(WEB_URL);
+ await page.Locator("a >> text=Dynamic Client Registration").ClickAsync();
+ await page.Locator("h2 >> text=Dynamic Client Registration").TextContentAsync();
+ await page.Locator("text=Delete").ClickAsync();
+ await page.Locator("text=No existing registrations found.").TextContentAsync();
+ }
+ static protected async Task Consents_DeleteLocal(IPage page)
+ {
+ await TestInfo(page, "Delete (local)", "Delete Arrangement", "204");
+ }
+ public static async Task TestInfo(IPage page, string menuText, string modalTitle, string expectedStatusCode, (string name, string? value)[]? expectedPayload = null)
+ {
+ // Arrange - Goto home page, click menu button, check page loaded
+ await page.GotoAsync(WEB_URL);
+ await page.Locator("a >> text=Consents").ClickAsync();
+ await page.Locator("h2 >> text=Consents").TextContentAsync();
+
+ // Act - Click actions button and submenu item
+ await page.Locator("button:has-text(\"Actions\")").ClickAsync();
+ await page.Locator($"a >> text={menuText}").ClickAsync();
+
+ // Act - Check modal opens
+ await page.Locator($"div#modal-info >> h5.modal-title >> text={modalTitle}").TextContentAsync();
+
+ // Assert - Check statuscode
+ var statusCode = await page.Locator(@$"div#modal-info >> div.modal-statusCode >> text={expectedStatusCode}").TextContentAsync();
+ statusCode.Should().Be(expectedStatusCode);
+
+ // Assert - Check payload is what's expected
+ if (expectedPayload != null)
+ {
+ var payload = await page.Locator(@"div#modal-info >> pre.modal-payload").TextContentAsync();
+ Assert_Json2(payload, expectedPayload);
+ }
+ }
+ public class ConsentAndAuthorisationResponse
+ {
+ public string? IDToken { get; init; }
+ public string? AccessToken { get; init; }
+ public string? RefreshToken { get; init; }
+ public string? ExpiresIn { get; init; }
+ public string? Scope { get; init; }
+ public string? TokenType { get; init; }
+ public string? CDRArrangementID { get; init; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/CDR.DataRecipient.E2ETests/BaseTest_v2.cs b/Source/CDR.DataRecipient.E2ETests/BaseTest_v2.cs
deleted file mode 100644
index 8991cb9..0000000
--- a/Source/CDR.DataRecipient.E2ETests/BaseTest_v2.cs
+++ /dev/null
@@ -1,558 +0,0 @@
-#undef DEPRECATED // instead see BaseTest_v3
-#if DEPRECATED
-
-#define TEST_DEBUG_MODE // Run Playwright in non-headless mode for debugging purposes (ie show a browser)
-
-// In docker (Ubuntu container) Playwright will fail if running in non-headless mode, so we ensure TEST_DEBUG_MODE is undef'ed
-#if !DEBUG
-#undef TEST_DEBUG_MODE
-#endif
-
-using System;
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using FluentAssertions;
-using FluentAssertions.Execution;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Playwright;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Xunit;
-
-#nullable enable
-
-namespace CDR.DataRecipient.E2ETests
-{
- // Put all tests in same collection because we need them to run sequentially since some tests are mutating DB.
- [Collection("E2ETests")]
- [TestCaseOrderer("CDR.DataRecipient.E2ETests.XUnit.Orderers.AlphabeticalOrderer", "CDR.DataRecipient.E2ETests")]
- [DisplayTestMethodName]
- public class BaseTest_v2
- {
- static public bool RUNNING_IN_CONTAINER => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER")?.ToUpper() == "TRUE";
-
- // Customers
- public const string CUSTOMERID_BANKING = "jwilson";
- public const string CUSTOMERACCOUNTS_BANKING = "Personal Loan xxx-xxx xxxxx987,Transactions and Savings Account xxx-xxx xxxxx988";
- public const string CUSTOMERID_ENERGY = "mmoss";
- public const string CUSTOMERACCOUNTS_ENERGY = "'ELECTRICITY ACCOUNT','ELECTRICITY ACCOUNT 2',ELECTRICITY ACCOUNT 3,ELECTRICITY ACCOUNT 4,ELECTRICITY ACCOUNT 5,ELECTRICITY ACCOUNT 6,ELECTRICITY ACCOUNT 7,ELECTRICITY ACCOUNT 8,ELECTRICITY ACCOUNT 9,ELECTRICITY ACCOUNT 10,ELECTRICITY ACCOUNT 11,ELECTRICITY ACCOUNT 12,ELECTRICITY ACCOUNT 13,ELECTRICITY ACCOUNT 14,ELECTRICITY ACCOUNT 15,ELECTRICITY ACCOUNT 16,ELECTRICITY ACCOUNT 17,ELECTRICITY ACCOUNT 18,ELECTRICITY ACCOUNT 19,ELECTRICITY ACCOUNT 20,ELECTRICITY ACCOUNT 21";
-
- // Data Holder
- public const string DH_BRANDID = "804fc2fb-18a7-4235-9a49-2af393d18bc7";
- public const string DH_BRANDID_ENERGY = "cfcaf0df-401b-47f2-98af-94787289eca8"; // Mock Data Holder (Energy)
-
- // Data Recipient
- public const string DR_BRANDID = "ffb1c8ba-279e-44d8-96f0-1bc34a6b436f";
- public const string DR_SOFTWAREPRODUCTID = "c6327f87-687a-4369-99a4-eaacd3bb8210";
-
- // URLs
- static public string REGISTER_MTLS_BaseURL => Configuration["MTLS_BaseURL"]
- ?? throw new Exception($"{nameof(REGISTER_MTLS_BaseURL)} - configuration setting not found");
-
- static public string REGISTER_IDENTITYSERVER_URL = REGISTER_MTLS_BaseURL + "/idp/connect/token";
-
- // Client certificates
- protected const string CERTIFICATE_FILENAME = "Certificates/client.pfx";
- protected const string CERTIFICATE_PASSWORD = "#M0ckDataRecipient#";
-
- public const string JWT_CERTIFICATE_FILENAME = "Certificates/jwks.pfx";
- public const string JWT_CERTIFICATE_PASSWORD = "#M0ckDataRecipient#";
-
-
- public bool CreateMedia { get; set; } = true;
-
- // Test settings.
- static public bool CREATE_MEDIA => Configuration.GetValue("CreateMedia", true);
- static public int TEST_TIMEOUT => Configuration.GetValue("TestTimeout", 30000);
-
- // URL of the web UI
- static public string WEB_URL => Configuration["Web_URL"]
- ?? throw new Exception($"{nameof(WEB_URL)} - configuration setting not found");
-
- // Hostnames
- static public string HOSTNAME_REGISTER => Configuration["Hostnames:Register"]
- ?? throw new Exception($"{nameof(HOSTNAME_REGISTER)} - configuration setting not found");
- static public string HOSTNAME_DATAHOLDER => Configuration["Hostnames:DataHolder"]
- ?? throw new Exception($"{nameof(HOSTNAME_DATAHOLDER)} - configuration setting not found");
- static public string HOSTNAME_DATAHOLDER_ENERGY => Configuration["Hostnames:DataHolderEnergy"]
- ?? throw new Exception($"{nameof(HOSTNAME_DATAHOLDER_ENERGY)} - configuration setting not found");
- static public string HOSTNAME_DATARECIPIENT => Configuration["Hostnames:DataRecipient"]
- ?? throw new Exception($"{nameof(HOSTNAME_DATARECIPIENT)} - configuration setting not found");
-
- // Connection strings
- static public string DATAHOLDER_CONNECTIONSTRING => Configuration["ConnectionStrings:DataHolder"]
- ?? throw new Exception($"{nameof(DATAHOLDER_CONNECTIONSTRING)} - configuration setting not found");
- static public string DATAHOLDER_ENERGY_CONNECTIONSTRING => Configuration["ConnectionStrings:DataHolderEnergy"]
- ?? throw new Exception($"{nameof(DATAHOLDER_ENERGY_CONNECTIONSTRING)} - configuration setting not found");
- static public string DATAHOLDER_IDENTITYSERVER_CONNECTIONSTRING => Configuration["ConnectionStrings:DataHolderIdentityServer"]
- ?? throw new Exception($"{nameof(DATAHOLDER_IDENTITYSERVER_CONNECTIONSTRING)} - configuration setting not found");
- static public string DATAHOLDER_ENERGY_IDENTITYSERVER_CONNECTIONSTRING => Configuration["ConnectionStrings:DataHolderEnergyIdentityServer"]
- ?? throw new Exception($"{nameof(DATAHOLDER_ENERGY_IDENTITYSERVER_CONNECTIONSTRING)} - configuration setting not found");
- static public string REGISTER_CONNECTIONSTRING => Configuration["ConnectionStrings:Register"]
- ?? throw new Exception($"{nameof(REGISTER_CONNECTIONSTRING)} - configuration setting not found");
- static public string DATARECIPIENT_CONNECTIONSTRING => Configuration["ConnectionStrings:DataRecipient"]
- ?? throw new Exception($"{nameof(DATARECIPIENT_CONNECTIONSTRING)} - configuration setting not found");
-
- // Media folder (for videos and screenshots)
- static public string MEDIAFOLDER => Configuration["MediaFolder"]
- ?? throw new Exception($"{nameof(MEDIAFOLDER)} - configuration setting not found");
-
- // Dataholder - Access token lifetime seconds
- static public string ACCESSTOKENLIFETIMESECONDS => Configuration["DataHolder:AccessTokenLifetimeSeconds"]
- ?? throw new Exception($"{nameof(ACCESSTOKENLIFETIMESECONDS)} - configuration setting not found");
-
- static private IConfigurationRoot? configuration;
- static protected IConfigurationRoot Configuration
- {
- get
- {
- if (configuration == null)
- {
- configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json")
- .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", true)
- .Build();
- }
-
- return configuration;
- }
- }
-
- protected BaseTest_v2()
- {
- // Default from config.
- this.CreateMedia = CREATE_MEDIA;
- }
-
- private bool inArrange = false;
- protected delegate Task ArrangeDelegate(IPage page);
- protected async Task ArrangeAsync(ArrangeDelegate arrange)
- {
- if (inArrange)
- return;
-
- inArrange = true;
- try
- {
- // Setup Playwright
- using var playwright = await Playwright.CreateAsync();
-
- // Setup browser
- await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
- {
- SlowMo = 250,
-#if TEST_DEBUG_MODE
- Headless = false,
- Timeout = 5000 // DEBUG - 5 seconds
-#endif
- });
-
- // Setup browser context
- var context = await browser.NewContextAsync(new BrowserNewContextOptions
- {
- IgnoreHTTPSErrors = true,
- RecordVideoDir = null,
- ViewportSize = new ViewportSize
- {
- Width = 1200,
- Height = 1600
- }
- });
-
- try
- {
- var page = await context.NewPageAsync();
- page.Close += async (_, page) =>
- {
- };
-
- using (new AssertionScope())
- {
- page.SetDefaultTimeout(TEST_TIMEOUT);
- await arrange(page);
- }
- }
- finally
- {
- await context.CloseAsync();
- await browser.CloseAsync();
- }
- }
- finally
- {
- inArrange = false;
- }
- }
-
- private bool inCleanup = false;
- protected delegate Task CleanupDelegate(IPage page);
- protected async Task CleanupAsync(CleanupDelegate cleanup)
- {
- if (inArrange || inCleanup)
- return;
-
- inCleanup = true;
- try
- {
- // Setup Playwright
- using var playwright = await Playwright.CreateAsync();
-
- // Setup browser
- await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
- {
- SlowMo = 250,
-#if TEST_DEBUG_MODE
- Headless = false,
- Timeout = 5000 // DEBUG - 5 seconds
-#endif
- });
-
- // Setup browser context
- var context = await browser.NewContextAsync(new BrowserNewContextOptions
- {
- IgnoreHTTPSErrors = true,
- RecordVideoDir = null,
- ViewportSize = new ViewportSize
- {
- Width = 1200,
- Height = 1600
- }
- });
-
- try
- {
- var page = await context.NewPageAsync();
- page.Close += async (_, page) =>
- {
- };
-
- using (new AssertionScope())
- {
- page.SetDefaultTimeout(TEST_TIMEOUT);
- await cleanup(page);
- }
- }
- finally
- {
- await context.CloseAsync();
- await browser.CloseAsync();
- }
- }
- finally
- {
- inCleanup = false;
- }
- }
-
- protected delegate Task TestDelegate(IPage page);
- protected async Task TestAsync(string testName, TestDelegate testDelegate) //, bool? CreateMedia = true)
- {
- _testName = testName;
-
- static void DeleteFile(string filename)
- {
- if (File.Exists(filename))
- {
- File.Delete(filename);
- }
- }
-
- if (CreateMedia == true)
- {
- // Remove video/screens if they exist
- DeleteFile($"{MEDIAFOLDER}/{testName}.webm");
- DeleteFile($"{MEDIAFOLDER}/{testName}.png");
- DeleteFile($"{MEDIAFOLDER}/{testName}-exception.png");
- }
-
- // Setup Playwright
- using var playwright = await Playwright.CreateAsync();
-
- // Setup browser
- await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
- {
- SlowMo = 250,
-#if TEST_DEBUG_MODE
- Headless = false,
- Timeout = 5000 // DEBUG - 5 seconds
-#endif
- });
-
- // Setup browser context
- var context = await browser.NewContextAsync(new BrowserNewContextOptions
- {
- IgnoreHTTPSErrors = true,
- RecordVideoDir = CreateMedia == true ? $"{MEDIAFOLDER}" : null,
-
- //#if !TEST_DEBUG_MODE
- ViewportSize = new ViewportSize
- {
- Width = 1200,
- Height = 1600
- }
- //#endif
- });
-
- string? videoPath = null;
- try
- {
- var page = await context.NewPageAsync();
- try
- {
- page.Close += async (_, page) =>
- {
- // Page is closed, so save videoPath
- if (CreateMedia == true)
- {
- if (page.Video != null)
- {
- videoPath = await page.Video.PathAsync();
- }
- }
- };
-
- using (new AssertionScope())
- {
- page.SetDefaultTimeout(TEST_TIMEOUT);
- await testDelegate(page);
- }
- }
- finally
- {
- // Save a screenshot
- if (CreateMedia == true)
- {
- await ScreenshotAsync(page, "");
- }
- }
- }
- finally
- {
- // Wait 1 second so that video captures final state of page
- await Task.Delay(1000);
-
- await context.CloseAsync();
-
- // Rename video file
- if (CreateMedia == true)
- {
- if (videoPath != null)
- {
- File.Move(videoPath, $"{MEDIAFOLDER}/{testName}.webm");
- }
- }
-
- await browser.CloseAsync();
- }
- }
-
- private string? _testName;
- public async Task ScreenshotAsync(IPage page, string name)
- {
- await page.ScreenshotAsync(new PageScreenshotOptions { Path = $"{MEDIAFOLDER}/{_testName}{name}.png" });
- }
-
- protected static string? StripJsonProperty(string? json, string propertyName)
- {
- if (String.IsNullOrEmpty(json))
- return json;
-
- return Regex.Replace(json, @$"""{propertyName}"".*", "");
- }
-
- protected static void Assert_Json(string? expectedJson, string? actualJson)
- {
- static object? Deserialize(string json)
- {
- try { return JsonConvert.DeserializeObject