From bca8fe23c7dc97d3bbafc4aa5d0176067777a907 Mon Sep 17 00:00:00 2001 From: Emanuel Gaspar Date: Wed, 24 May 2023 16:52:20 +0100 Subject: [PATCH] Prod release 1.0.1 (#95) * chore: Bump trakx/bump-version-action from 8 to 9.0.0 (#22) Bumps [trakx/bump-version-action](https://github.com/trakx/bump-version-action) from 8 to 9.0.0. - [Release notes](https://github.com/trakx/bump-version-action/releases) - [Commits](https://github.com/trakx/bump-version-action/compare/v8...v9.0.0) --- updated-dependencies: - dependency-name: trakx/bump-version-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Bump anothrNick/github-tag-action from 1.38.0 to 1.39.0 (#23) Bumps [anothrNick/github-tag-action](https://github.com/anothrNick/github-tag-action) from 1.38.0 to 1.39.0. - [Release notes](https://github.com/anothrNick/github-tag-action/releases) - [Commits](https://github.com/anothrNick/github-tag-action/compare/1.38.0...1.39.0) --- updated-dependencies: - dependency-name: anothrNick/github-tag-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Bump Trakx.Utils.Testing from 0.3.1 to 0.3.11 (#25) Bumps Trakx.Utils.Testing from 0.3.1 to 0.3.11. --- updated-dependencies: - dependency-name: Trakx.Utils.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monsieurleberre * chore: Bump Trakx.Utils from 0.3.1 to 0.3.11 (#26) Bumps Trakx.Utils from 0.3.1 to 0.3.11. --- updated-dependencies: - dependency-name: Trakx.Utils dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monsieurleberre * chore: Bump FluentAssertions from 6.5.1 to 6.6.0 (#24) Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.5.1 to 6.6.0. - [Release notes](https://github.com/fluentassertions/fluentassertions/releases) - [Changelog](https://github.com/fluentassertions/fluentassertions/blob/develop/AcceptApiChanges.ps1) - [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.5.1...6.6.0) --- updated-dependencies: - dependency-name: FluentAssertions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monsieurleberre * chore: Bump trakx/bump-version-action from 9.0.0 to 9.0.1 (#27) Bumps [trakx/bump-version-action](https://github.com/trakx/bump-version-action) from 9.0.0 to 9.0.1. - [Release notes](https://github.com/trakx/bump-version-action/releases) - [Commits](https://github.com/trakx/bump-version-action/compare/v9.0.0...v9.0.1) --- updated-dependencies: - dependency-name: trakx/bump-version-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Bump GitHubActionsTestLogger from 1.3.0 to 1.4.1 (#29) Bumps [GitHubActionsTestLogger](https://github.com/Tyrrrz/GitHubActionsTestLogger) from 1.3.0 to 1.4.1. - [Release notes](https://github.com/Tyrrrz/GitHubActionsTestLogger/releases) - [Changelog](https://github.com/Tyrrrz/GitHubActionsTestLogger/blob/master/Changelog.md) - [Commits](https://github.com/Tyrrrz/GitHubActionsTestLogger/compare/1.3...1.4.1) --- updated-dependencies: - dependency-name: GitHubActionsTestLogger dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Bump Trakx.Utils from 0.3.11 to 0.3.13 (#36) Bumps Trakx.Utils from 0.3.11 to 0.3.13. --- updated-dependencies: - dependency-name: Trakx.Utils dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Bump Serilog from 2.10.0 to 2.11.0 (#32) Bumps [Serilog](https://github.com/serilog/serilog) from 2.10.0 to 2.11.0. - [Release notes](https://github.com/serilog/serilog/releases) - [Changelog](https://github.com/serilog/serilog/blob/dev/CHANGES.md) - [Commits](https://github.com/serilog/serilog/compare/v2.10.0...v2.11.0) --- updated-dependencies: - dependency-name: Serilog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monsieurleberre * chore: Bump Trakx.Utils.Testing from 0.3.11 to 0.3.13 (#35) Bumps Trakx.Utils.Testing from 0.3.11 to 0.3.13. --- updated-dependencies: - dependency-name: Trakx.Utils.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monsieurleberre * update bump-version * update packages * refactor: rename from/to symbol to base/quote * Update to .Net 7 Update pipeline to support .net7 * PUMP Trakx.WebSocket to latest version * Fix unit test * Update pipeline * Update src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Integration/CryptoCompareWebsocketHandlerTests.cs Co-authored-by: Emanuel Gaspar * Update .github/workflows/automerge.yml Co-authored-by: Emanuel Gaspar * Update .github/workflows/nuget.yml Co-authored-by: Emanuel Gaspar * fix issue in pipeline * Update to get the configuration in test from AWS * refactor: explicit method and strongly typed deserialize when AddInternalAsync is called with SubRemove * chore: version bumps * chore: testing coverlet bump to v6 * feat: explicit RemoveAsync with single TopicSubscription; more test coverage * Gonen's feedback (thanks) * ensure configuration is loaded from AWS, from CiCd or Development * ensure tests pass at all times * remove Development environment from test config * increase buffer window to 30 seconds * Update .github/workflows/delete.packages.yml Co-authored-by: monsieurleberre * Update .github/workflows/delete.packages.yml Co-authored-by: monsieurleberre --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monsieurleberre Co-authored-by: monsieurleberre Co-authored-by: TiagoFroisPereira --- .github/workflows/automerge.yml | 17 ++++ .github/workflows/delete.packages.yml | 28 ++++++ .github/workflows/dotnet-core.yml | 56 ----------- .github/workflows/nuget.yml | 65 +++++++++++++ .github/workflows/publish.nuget.yml | 94 ------------------- .github/workflows/test.yml | 22 +++++ README.md | 5 + .../Clients/CryptoCompareClientTestsBase.cs | 9 +- .../Core/ThrottledHttpClientHandlerTests.cs | 8 +- ...Trakx.CryptoCompare.ApiClient.Tests.csproj | 13 ++- .../CryptoCompareWebsocketHandlerTests.cs | 34 ++++--- ...toCompare.ApiClient.Websocket.Tests.csproj | 10 +- .../CryptoCompareWebsocketHandlerTests.cs | 81 ++++++++++++---- .../CryptoCompareWebsocketHandler.cs | 71 ++++++++------ .../ICryptoCompareWebsocketHandler.cs | 7 +- ...x.CryptoCompare.ApiClient.Websocket.csproj | 2 +- .../CryptoCompareApiConfiguration.cs | 22 ++--- .../Trakx.CryptoCompare.ApiClient.csproj | 4 +- 18 files changed, 306 insertions(+), 242 deletions(-) create mode 100644 .github/workflows/automerge.yml create mode 100644 .github/workflows/delete.packages.yml delete mode 100644 .github/workflows/dotnet-core.yml create mode 100644 .github/workflows/nuget.yml delete mode 100644 .github/workflows/publish.nuget.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..15efdef --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,17 @@ +name: Auto Merge Dependabot Requests + +on: pull_request_target + +permissions: + pull-requests: write + contents: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Merge non major updates + uses: trakx/github-actions/dependabot-automerge@v10.0.14 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/delete.packages.yml b/.github/workflows/delete.packages.yml new file mode 100644 index 0000000..65b08dd --- /dev/null +++ b/.github/workflows/delete.packages.yml @@ -0,0 +1,28 @@ +name: Delete old packages + +on: + workflow_dispatch: + inputs: + name: + description: 'The name of the package to be deleted.' + required: true + num-old-versions-to-delete: + description: 'The number of old versions to delete starting from the oldest version.' + default: '1' + required: false + +jobs: + + delete: + + runs-on: ubuntu-latest + + steps: + - name: Delete packages + id: deletePackages + uses: actions/delete-package-versions@v4 + with: + package-name: '${{github.event.inputs.name}}' + package-type: 'nuget' + num-old-versions-to-delete: ${{github.event.inputs.num-old-versions-to-delete}} + diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml deleted file mode 100644 index bc2f09c..0000000 --- a/.github/workflows/dotnet-core.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: .NET Core - -on: - push: - branches: [ dev, master ] - pull_request: - branches: [ dev, master ] - -env: - SOLUTION_PATH: "src/Trakx.CryptoCompare.ApiClient.sln" - CryptoCompareApiConfiguration__ApiKey: ${{secrets.CRYPTOCOMPARE_API_KEY}} - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup .NET Core - uses: actions/setup-dotnet@v2 - with: - dotnet-version: 7.0.x - - - name: Add github nuget source - run: dotnet nuget add source "https://nuget.pkg.github.com/trakx/index.json" --name "github" --username "trakx-bot" --password ${{secrets.TRAKX_BOT_READONLY_PAT}} --store-password-in-clear-text - - - name: Install dependencies - run: dotnet restore ${{env.SOLUTION_PATH}} - - - name: Remove github source - run: dotnet nuget remove source "github" - - - name: Build - run: | - dotnet build ${{env.SOLUTION_PATH}} --configuration Debug --no-restore - - name: Test & Coverage - run: | - for f in ./src/*.Tests/*.Tests.csproj; do echo "testing project $f" && \ - dotnet test $f --configuration Debug --no-restore --logger GitHubActions --verbosity normal\ - /p:CollectCoverage=true \ - /p:CoverletOutputFormat=opencover \ - /p:UserSourceLink=true \ - /p:ExcludeByAttribute=\"Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute\" \ - /p:Include=\"[Trakx.*]*\"; \ - done - - - name: Publish Coverage - env: - CODACY_PROJECT_TOKEN: ${{secrets.CODACY_TOKEN}} - run: | - for f in ./src/*.Tests/coverage.opencover.xml; do echo "sending coverage report $f" && \ - bash <(curl -Ls https://coverage.codacy.com/get.sh) report -l csharp -r $f --partial --commit-uuid ${{github.sha}}; \ - done - bash <(curl -Ls https://coverage.codacy.com/get.sh) final --commit-uuid ${{github.sha}} diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml new file mode 100644 index 0000000..bbb7b3b --- /dev/null +++ b/.github/workflows/nuget.yml @@ -0,0 +1,65 @@ +name: Publish Nuget Packages + +on: + + # the workflow run allows to not run the test suite twice for non release + # branches, and should be used for all branches, but the reference branch + # in workflow run is the default branch and we don't get the correct release + # tags when it is triggered from release branches like stage and dev. + workflow_run: + workflows: ["Build and Test"] + types: [completed] + branches-ignore: [ stage, master ] + + # so we treat these releases branches specially, using the push trigger directly. + push: + branches: [ stage, master ] + + workflow_dispatch: + inputs: + semverIncrementLevel: + description: "Level of the semver (major.minor.patch) to be increased to get the new package version." + required: false + default: "patch" + debuggable: + description: "Set to false if you want a non debuggable (Release) package." + required: false + default: "true" + +jobs: + + test: + uses: ./.github/workflows/test.yml + if: ${{ github.event_name != 'workflow_run' }} + secrets: inherit + + publish: + + needs: [test] + runs-on: ubuntu-latest + if: | + always() && + (needs.test.result == 'success' || (needs.test.result == 'skipped' + && github.event_name == 'workflow_run' + && github.event.workflow_run.conclusion == 'success' )) + + steps: + - name: Default input values + id: default-inputs + run: | + debuggable="${{github.event.inputs.debuggable}}" + debuggable=${debuggable:-true} + echo "debuggable=$debuggable" >> $GITHUB_OUTPUT + + semverIncrementLevel="${{github.event.inputs.semverIncrementLevel}}" + semverIncrementLevel=${semverIncrementLevel:-true} + echo "semverIncrementLevel=$semverIncrementLevel" >> $GITHUB_OUTPUT + + - name: Build and publish nuget packages + id: publish + uses: trakx/github-actions/publish-nuget@v10.0.14 + with: + packageReadonlyPat: ${{secrets.TRAKX_BOT_READONLY_PAT}} + githubToken: ${{secrets.GITHUB_TOKEN}} + debuggable: ${{steps.default-inputs.outputs.debuggable}} + semverIncrementLevel: ${{steps.default-inputs.outputs.semverIncrementLevel}} \ No newline at end of file diff --git a/.github/workflows/publish.nuget.yml b/.github/workflows/publish.nuget.yml deleted file mode 100644 index e2e9c51..0000000 --- a/.github/workflows/publish.nuget.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Publish nuget package - -on: - workflow_dispatch: - inputs: - semverIncrementLevel: - description: 'Level of the semver (major.minor.patch) to be increased to get the new package version.' - required: true - default: 'patch' - push: - branches: - - master - -env: - SOLUTION_PATH: "src/Trakx.CryptoCompare.ApiClient.sln" - PROJECT_PATH: "src/Trakx.CryptoCompare.ApiClient/Trakx.CryptoCompare.ApiClient.csproj" - PROJECT_WEBSOCKET_PATH: "src/Trakx.CryptoCompare.ApiClient.Websocket/Trakx.CryptoCompare.ApiClient.Websocket.csproj" - CryptoCompareApiConfiguration__ApiKey: ${{secrets.CRYPTOCOMPARE_API_KEY}} - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - - name: Set compilation mode - id: comp-mode - run: | - DEBUGGABLE="${{github.event.inputs.debuggable}}" - DEBUGGABLE=${DEBUGGABLE:-true} - if [ [DEBUGGABLE == f*] -o [DEBUGGABLE == F*] ]; then - COMP_MODE=Release - else - COMP_MODE=Debug - fi - echo '::set-output name=compilationMode::'$COMP_MODE - echo "compilation mode set to ${{steps.comp-mode.outputs.compilationMode}}" - - - name: checkout - uses: actions/checkout@v3 - - - name: Bump version - id: bumpVersion - uses: trakx/bump-version-action/get-tag@v9.1.1 - with: - semverIncrementLevel: ${{github.event.inputs.semverIncrementLevel}} - - - name: Setup .NET Core - uses: actions/setup-dotnet@v2 - with: - dotnet-version: 7.0.x - - - name: Add github nuget source - run: dotnet nuget add source "https://nuget.pkg.github.com/trakx/index.json" --name "github" --username "trakx-bot" --password ${{secrets.TRAKX_BOT_READONLY_PAT}} --store-password-in-clear-text - - - name: Install dependencies - run: dotnet restore ${{env.SOLUTION_PATH}} - - - name: Remove github source - run: dotnet nuget remove source "github" - - - name: Build - run: | - dotnet build ${{env.SOLUTION_PATH}} \ - --configuration ${{steps.comp-mode.outputs.compilationMode}} \ - -p:Version=${{steps.bumpVersion.outputs.assemblyVersion}} \ - -p:SourceRevisionId=${{steps.bumpVersion.outputs.productVersion}} \ - --no-restore - - - name: Test - run: dotnet test ${{env.SOLUTION_PATH}} --no-restore --verbosity normal - - - name: Package - run: | - dotnet pack ${{env.PROJECT_PATH}} --no-build --configuration ${{ steps.comp-mode.outputs.compilationMode }} \ - --output ./nuget/ -p:PackageVersion=${{ steps.bumpVersion.outputs.fullVersion }} --include-symbols --include-source - dotnet pack ${{env.PROJECT_WEBSOCKET_PATH}} --no-build --configuration ${{ steps.comp-mode.outputs.compilationMode }} \ - --output ./nuget/ -p:PackageVersion=${{ steps.bumpVersion.outputs.fullVersion }} --include-symbols --include-source - - - name: Publish - # https://github.com/NuGet/Home/issues/8580 - run: | - ls ./nuget/*.nupkg - for f in ./nuget/*.symbols.nupkg; do echo "pushing $f file.." && dotnet nuget push $f --api-key ${{secrets.GITHUB_TOKEN}} \ - --source "https://nuget.pkg.github.com/trakx/index.json"; done - - - name: Push version tag - id: pushTag - uses: trakx/bump-version-action/push-tag@v9.1.1 - with: - tag: v${{steps.bumpVersion.outputs.fullVersion}} - githubToken: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6117f96 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Build and Test + +on: + push: + branches: [ dev ] + pull_request: + branches: [ dev, stage, master ] + workflow_call: + +jobs: + + test: + runs-on: ubuntu-latest + steps: + - name: Test and cover solutions + id: test + uses: trakx/github-actions/test-dotnet@v10.0.14 + with: + packageReadonlyPat: ${{secrets.TRAKX_BOT_READONLY_PAT}} + codacyToken: ${{secrets.CODACY_TOKEN}} + awsAccessKeyId: ${{secrets.AWS_ACCESS_KEY_ID}} + awsAccessKeySecret: ${{secrets.AWS_ACCESS_KEY_SECRET}} diff --git a/README.md b/README.md index c06e58e..2ac8fbf 100644 --- a/README.md +++ b/README.md @@ -79,5 +79,10 @@ Console.WriteLine(eth.Data.General.Name); ## AWS Parameters In order to be able to run some integration tests, you should ensure that you have access to the AWS parameters starting in `/CiCd`. In order for the applications in this solution to run correctly on AWS, please ensure that variables starting in `/[environment]` are defined for all 3 environments \( _Production_, _Staging_, _Development_ \) : ```awsParams +# REPOSITORY SECRETS /[environment]/Trakx/CryptoCompare/ApiClient/CryptoCompareApiConfiguration/ApiKey + +# GLOBAL SECRETS +# Instead of creating a specific repository secret, can use the global one with the same [Key] +/[environment]/Global/CryptoCompareApiConfiguration/ApiKey ``` \ No newline at end of file diff --git a/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Clients/CryptoCompareClientTestsBase.cs b/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Clients/CryptoCompareClientTestsBase.cs index a4cd66f..54e5767 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Clients/CryptoCompareClientTestsBase.cs +++ b/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Clients/CryptoCompareClientTestsBase.cs @@ -29,13 +29,20 @@ public class CryptoCompareApiFixture : IDisposable public ICryptoCompareClient CryptoCompareClient { get; } public CryptoCompareApiFixture() { - var configuration = EnvConfigurationHelper.GetConfigurationFromEnv(); + CryptoCompareApiConfiguration configuration = LoadConfiguration(); + var services = new ServiceCollection(); services.AddCryptoCompareClient(configuration); var provider = services.BuildServiceProvider(); CryptoCompareClient = provider.GetRequiredService(); } + public static CryptoCompareApiConfiguration LoadConfiguration() + { + return AwsConfigurationHelper.GetConfigurationFromAws() + ?? throw new InvalidOperationException("Unable to load configuration from AWS"); + } + protected virtual void Dispose(bool disposing) { if (!disposing) return; diff --git a/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Core/ThrottledHttpClientHandlerTests.cs b/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Core/ThrottledHttpClientHandlerTests.cs index 17336aa..adc8aee 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Core/ThrottledHttpClientHandlerTests.cs +++ b/src/Trakx.CryptoCompare.ApiClient.Tests/Integration/Rest/Core/ThrottledHttpClientHandlerTests.cs @@ -1,8 +1,8 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Trakx.Common.Testing.Configuration; using Trakx.CryptoCompare.ApiClient.Rest; +using Trakx.CryptoCompare.ApiClient.Tests.Integration.Rest.Clients; using Xunit; namespace Trakx.CryptoCompare.ApiClient.Tests.Integration.Rest.Core @@ -15,10 +15,12 @@ public async Task WaitsBetweenQueries() var throttleDelayMs = 200; var queriesCount = 5; - var configuration = EnvConfigurationHelper.GetConfigurationFromEnv() - with { + var configuration = CryptoCompareApiFixture.LoadConfiguration() + with + { ThrottleDelayMs = throttleDelayMs }; + var client = new CryptoCompareClient(configuration); var stopWatch = new Stopwatch(); diff --git a/src/Trakx.CryptoCompare.ApiClient.Tests/Trakx.CryptoCompare.ApiClient.Tests.csproj b/src/Trakx.CryptoCompare.ApiClient.Tests/Trakx.CryptoCompare.ApiClient.Tests.csproj index df69253..4e092f7 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Tests/Trakx.CryptoCompare.ApiClient.Tests.csproj +++ b/src/Trakx.CryptoCompare.ApiClient.Tests/Trakx.CryptoCompare.ApiClient.Tests.csproj @@ -8,23 +8,23 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all @@ -33,8 +33,7 @@ - + \ No newline at end of file diff --git a/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Integration/CryptoCompareWebsocketHandlerTests.cs b/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Integration/CryptoCompareWebsocketHandlerTests.cs index b08a544..8dcfd10 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Integration/CryptoCompareWebsocketHandlerTests.cs +++ b/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Integration/CryptoCompareWebsocketHandlerTests.cs @@ -1,12 +1,12 @@ -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.Globalization; using System.Linq; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Threading.Tasks; -using Trakx.Common.Testing.Configuration; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Trakx.CryptoCompare.ApiClient.Tests.Integration.Rest.Clients; using Trakx.CryptoCompare.ApiClient.Websocket.Extensions; using Trakx.CryptoCompare.ApiClient.Websocket.Model; using Trakx.Websocket.Model; @@ -25,7 +25,7 @@ public CryptoCompareWebsocketHandlerTests() _cryptoCompareWebsocketHandler = _serviceScope.ServiceProvider.GetRequiredService(); } - public IServiceProvider CreateServiceProvider() + public static IServiceProvider CreateServiceProvider() { var serviceCollection = new ServiceCollection(); var websocketConfiguration = new WebsocketConfiguration @@ -33,8 +33,8 @@ public IServiceProvider CreateServiceProvider() BufferSize = 4096, MaxSubscriptionsPerScope = 100 }; - var configuration = EnvConfigurationHelper.GetConfigurationFromEnv() - with { WebSocketBaseUrl = "wss://streamer.cryptocompare.com/v2?api_key=", }; + + var configuration = CryptoCompareApiFixture.LoadConfiguration(); serviceCollection.AddCryptoCompareWebsockets(configuration, websocketConfiguration); return serviceCollection.BuildServiceProvider(); @@ -46,14 +46,15 @@ public void Dispose() } - private async Task GetResult(string subStr) where T : InboundMessageBase + private async Task GetResult(string subStr, int bufferSeconds = 10) + where T : InboundMessageBase { var topicSub = CryptoCompareSubscriptionFactory.GetTopicSubscription (SubscribeActions.SubAdd, subStr); await _cryptoCompareWebsocketHandler.AddAsync(topicSub); var res = await _cryptoCompareWebsocketHandler.GetTopicMessageStream(topicSub.Topic) - .Buffer(TimeSpan.FromSeconds(10), 1) + .Buffer(TimeSpan.FromSeconds(bufferSeconds), 1) .Select(t => t.FirstOrDefault()) .FirstOrDefaultAsync() .ToTask(); @@ -64,8 +65,8 @@ private async Task GetResult(string subStr) where T : InboundMessageBase [Fact] public async Task Should_be_able_to_get_full_top_tier_volume_subscriptions() { - var result = await GetResult(CryptoCompareSubscriptionFactory.GetFullTopTierVolumeSubscriptionStr("btc")) - .ConfigureAwait(false); + var subscriptionString = CryptoCompareSubscriptionFactory.GetFullTopTierVolumeSubscriptionStr("btc"); + var result = await GetResult(subscriptionString).ConfigureAwait(false); result!.Symbol.Should().Be("BTC"); decimal.TryParse(result.Volume, CultureInfo.InvariantCulture, out decimal volume); volume.Should().BeGreaterThan(0); @@ -74,16 +75,19 @@ public async Task Should_be_able_to_get_full_top_tier_volume_subscriptions() [Fact] public async Task Should_be_able_to_get_oc_book() { - var result = await GetResult(CryptoCompareSubscriptionFactory.GetTopOfOrderBookSubscriptionStr("Binance", "btc", "usdt")) - .ConfigureAwait(false); + var subscriptionString = CryptoCompareSubscriptionFactory.GetTopOfOrderBookSubscriptionStr("Binance", "btc", "usdt"); + + var result = await GetResult(subscriptionString, bufferSeconds: 30); + + result.Should().NotBeNull(); result!.Type.Should().Be("30"); } [Fact] public async Task Should_be_able_to_get_ohlcc_candles() { - var result = await GetResult(CryptoCompareSubscriptionFactory.GetOHLCCandlesSubscriptionStr("Binance", "btc", "usdt", "m")) - .ConfigureAwait(false); + var subscriptionString = CryptoCompareSubscriptionFactory.GetOHLCCandlesSubscriptionStr("Binance", "btc", "usdt", "m"); + var result = await GetResult(subscriptionString).ConfigureAwait(false); result!.Open.Should().BeGreaterThan(0); result!.LastTimeStamp.Should().BeGreaterThan(0); result!.Market.Should().NotBeNull(); diff --git a/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Trakx.CryptoCompare.ApiClient.Websocket.Tests.csproj b/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Trakx.CryptoCompare.ApiClient.Websocket.Tests.csproj index affbc88..3dcda7f 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Trakx.CryptoCompare.ApiClient.Websocket.Tests.csproj +++ b/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Trakx.CryptoCompare.ApiClient.Websocket.Tests.csproj @@ -10,15 +10,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -28,8 +28,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Unit/CryptoCompareWebsocketHandlerTests.cs b/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Unit/CryptoCompareWebsocketHandlerTests.cs index 3745171..04e5f87 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Unit/CryptoCompareWebsocketHandlerTests.cs +++ b/src/Trakx.CryptoCompare.ApiClient.Websocket.Tests/Unit/CryptoCompareWebsocketHandlerTests.cs @@ -1,12 +1,11 @@ -using FluentAssertions; -using Microsoft.Extensions.Options; -using NSubstitute; using System; using System.Linq; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Text.Json; using System.Threading.Tasks; +using FluentAssertions; +using NSubstitute; using Trakx.CryptoCompare.ApiClient.Websocket.Model; using Trakx.Websocket.Interfaces; using Trakx.Websocket.Model; @@ -18,12 +17,19 @@ namespace Trakx.CryptoCompare.ApiClient.Websocket.Tests.Unit; public class CryptoCompareWebsocketHandlerTests { + private const string TestSymbol = "btc"; + private readonly TestWebsocketClient _testClient; private readonly CryptoCompareWebsocketHandler _websocketHandler; + private readonly TopicSubscription _topicSub; + private readonly TopicSubscription _removeSub; + + private int SubscriptionCount => _websocketHandler.CurrentSubscriptions.Count; public CryptoCompareWebsocketHandlerTests() { _testClient = Substitute.ForPartsOf(); + var fakeConfiguration = new WebsocketConfiguration { BufferSize = 4092, @@ -33,40 +39,75 @@ public CryptoCompareWebsocketHandlerTests() var fakeWebsocketFactory = Substitute.For(); fakeWebsocketFactory.CreateNewWebSocket(Arg.Any(), Arg.Any>()) .Returns(_testClient); + var fakeFinexConfig = new CryptoCompareApiConfiguration() { WebSocketBaseUrl = "https://www.google.com" }; _websocketHandler = new CryptoCompareWebsocketHandler(fakeConfiguration, fakeFinexConfig, fakeWebsocketFactory); + + _topicSub = SetupTopicSubscription(SubscribeActions.SubAdd); + _removeSub = SetupTopicSubscription(SubscribeActions.SubRemove); + } + + [Fact] + public async Task AddAsync_sends_message_to_client() + { + await _websocketHandler.AddAsync(_topicSub); + _testClient.Received(1).Send(_topicSub.Topic); + } + + [Fact] + public async Task RemoveAsync_sends_message_to_client() + { + // this call is needed to "open" the channel + await _websocketHandler.AddAsync(_topicSub); + + await _websocketHandler.RemoveAsync(_removeSub); + + _testClient.Received(1).Send(_removeSub.Topic); } [Fact] - public async Task AddAsync_should_remove_subscription_on_sub_remove() + public async Task AddAsync_removes_subscription_on_sub_remove() { - var topicSub = CryptoCompareSubscriptionFactory.GetTopicSubscription(SubscribeActions.SubAdd, - CryptoCompareSubscriptionFactory.GetFullTopTierVolumeSubscriptionStr("test")); - var removeSub = CryptoCompareSubscriptionFactory.GetTopicSubscription(SubscribeActions.SubRemove, - CryptoCompareSubscriptionFactory.GetFullTopTierVolumeSubscriptionStr("test")); - await _websocketHandler.AddAsync(topicSub); - _websocketHandler.CurrentSubscriptions.Count.Should().Be(1); - await _websocketHandler.AddAsync(removeSub); - _websocketHandler.CurrentSubscriptions.Count.Should().Be(0); + await _websocketHandler.AddAsync(_topicSub); + SubscriptionCount.Should().Be(1); + + await _websocketHandler.AddAsync(_removeSub); + SubscriptionCount.Should().Be(0); + } + + [Fact] + public async Task RemoveAsync_only_removes_subscription_with_valid_topic() + { + await _websocketHandler.AddAsync(_topicSub); + SubscriptionCount.Should().Be(1); + + // no changes because it doesn't have the SubRemove action + await _websocketHandler.RemoveAsync(_topicSub); + SubscriptionCount.Should().Be(1); + + await _websocketHandler.RemoveAsync(_removeSub); + SubscriptionCount.Should().Be(0); + + // only 2 message should have been sent, the first Add and the valid Remove + _testClient.Received(1).Send(_topicSub.Topic); + _testClient.Received(1).Send(_removeSub.Topic); } [Fact] public async Task IncomingMessage_should_map_to_correct_observer() { - var topicSub = CryptoCompareSubscriptionFactory.GetTopicSubscription(SubscribeActions.SubAdd, - CryptoCompareSubscriptionFactory.GetFullTopTierVolumeSubscriptionStr("test")); - await _websocketHandler.AddAsync(topicSub); + await _websocketHandler.AddAsync(_topicSub); - var topicMessageTask = _websocketHandler.GetTopicMessageStream(topicSub.Topic) + var topicMessageTask = _websocketHandler.GetTopicMessageStream(_topicSub.Topic) .Buffer(TimeSpan.FromSeconds(5), 1) .Select(t => t.FirstOrDefault()) .FirstOrDefaultAsync() .ToTask(); - var fullVolumeMsg = new FullVolume { Type = "11", Volume = 1, Symbol = "BTC" }; + var fullVolumeMsg = new FullVolume { Type = "11", Volume = 1, Symbol = TestSymbol }; var fakeMsg = JsonSerializer.Serialize(fullVolumeMsg); _testClient.StreamFakeMessage(ResponseMessage.TextMessage(fakeMsg)); @@ -75,4 +116,10 @@ public async Task IncomingMessage_should_map_to_correct_observer() topicMessage!.Symbol.Should().Be(fullVolumeMsg.Symbol); topicMessage!.Volume.Should().Be(fullVolumeMsg.Volume); } + + private static TopicSubscription SetupTopicSubscription(SubscribeActions action) + { + var subscriptionString = CryptoCompareSubscriptionFactory.GetFullTopTierVolumeSubscriptionStr(TestSymbol); + return CryptoCompareSubscriptionFactory.GetTopicSubscription(action, subscriptionString); + } } diff --git a/src/Trakx.CryptoCompare.ApiClient.Websocket/CryptoCompareWebsocketHandler.cs b/src/Trakx.CryptoCompare.ApiClient.Websocket/CryptoCompareWebsocketHandler.cs index f7d8937..d0c5645 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Websocket/CryptoCompareWebsocketHandler.cs +++ b/src/Trakx.CryptoCompare.ApiClient.Websocket/CryptoCompareWebsocketHandler.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Options; -using Serilog; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Serilog; +using Trakx.Common.Extensions; using Trakx.CryptoCompare.ApiClient.Websocket.Model; using Trakx.Websocket; using Trakx.Websocket.Interfaces; @@ -16,16 +16,18 @@ public class CryptoCompareWebsocketHandler : ClientWebsocketRedirectHandlerBase< { private readonly CryptoCompareApiConfiguration _config; - public CryptoCompareWebsocketHandler(WebsocketConfiguration websocketConfigurationOption, + public CryptoCompareWebsocketHandler( + WebsocketConfiguration websocketConfigurationOption, CryptoCompareApiConfiguration config, IClientWebsocketFactory clientWebsocketFactory, - TaskScheduler taskScheduler = null) : base( - websocketConfigurationOption - , clientWebsocketFactory, taskScheduler) + TaskScheduler? taskScheduler = null) + : base(websocketConfigurationOption, clientWebsocketFactory, taskScheduler) { _config = config; } + protected override Uri ClientSocketUri => _config.WebSocketEndpoint; + protected override IReadOnlyDictionary MessageTypesByTopics => new Dictionary { { "0", typeof(Trade) }, @@ -48,7 +50,7 @@ public CryptoCompareWebsocketHandler(WebsocketConfiguration websocketConfigurati protected override object? DeserializeClientSocketIncoming(string topic, IncomingMessage m) { - var typeStr = m.DeserializedMessage?.Type ?? null; + var typeStr = m.DeserializedMessage?.Type; if (typeStr != null && MessageTypesByTopics.ContainsKey(typeStr)) { var type = MessageTypesByTopics[typeStr]; @@ -61,30 +63,45 @@ public CryptoCompareWebsocketHandler(WebsocketConfiguration websocketConfigurati protected override async Task AddInternalAsync(TopicSubscription subscription) { - var jDoc = JsonDocument.Parse(subscription.Topic); - JsonProperty? unsubProperties = jDoc.RootElement.EnumerateObject().Any(t => t.Name.Equals("action", StringComparison.InvariantCultureIgnoreCase) - && t.Value.GetString().Equals("SubRemove", StringComparison.InvariantCultureIgnoreCase)) ? - jDoc.RootElement.EnumerateObject().FirstOrDefault(t => t.Name == "subs") - : null; - if (unsubProperties != null) + if (HasRemoveAction(subscription)) { - var unsubArrayLength = unsubProperties.Value.Value.GetArrayLength(); - for (int i = 0; i < unsubArrayLength; i++) + return await RemoveAsync(subscription); + } + return await base.AddInternalAsync(subscription); + } + + public async Task RemoveAsync(TopicSubscription subscription) + { + var payload = JsonSerializer.Deserialize(subscription.Topic); + + if (payload == null) return false; + if (payload.Action != SubscribeActions.SubRemove) return false; + + foreach (var payloadSub in payload.Subs) + { + var toRemoveSubs = Subscriptions.Where(t => t.Topic.ContainsIgnoreCase(payloadSub)).ToList(); + foreach (var unwanted in toRemoveSubs) { - var unsubElement = unsubProperties.Value.Value[i].GetString(); - var toRemoveSubs = Subscriptions.Where(t => t.Topic.Contains(unsubElement, StringComparison.InvariantCultureIgnoreCase)) - .ToList(); - if (toRemoveSubs.Any()) - { - await this.RemoveAsync(toRemoveSubs); - } + Subscriptions.Remove(unwanted); } - SendClient(subscription.Topic); - return true; } - return await base.AddInternalAsync(subscription); + SendClient(subscription.Topic); + + await Task.CompletedTask; + return true; } - protected override Uri ClientSocketUri => _config.WebSocketEndpoint; + /// + /// The for + /// has a payload of type . + /// This method checks if the in the payload is . + /// It's a fast string-based check, using the string representation of the enum. + /// The slower alternative would be parsing the JSON payload. + /// + private static bool HasRemoveAction(TopicSubscription subscription) + { + var subRemoveAsString = SubscribeActions.SubRemove.ToString(); + return subscription.Topic.ContainsIgnoreCase(subRemoveAsString); + } } diff --git a/src/Trakx.CryptoCompare.ApiClient.Websocket/ICryptoCompareWebsocketHandler.cs b/src/Trakx.CryptoCompare.ApiClient.Websocket/ICryptoCompareWebsocketHandler.cs index fa84710..c839f18 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Websocket/ICryptoCompareWebsocketHandler.cs +++ b/src/Trakx.CryptoCompare.ApiClient.Websocket/ICryptoCompareWebsocketHandler.cs @@ -1,8 +1,13 @@ -using Trakx.CryptoCompare.ApiClient.Websocket.Model; +using System.Threading.Tasks; +using Trakx.CryptoCompare.ApiClient.Websocket.Model; using Trakx.Websocket.Interfaces; +using Trakx.Websocket.Model; namespace Trakx.CryptoCompare.ApiClient.Websocket; public interface ICryptoCompareWebsocketHandler : IClientWebsocketRedirectHandler { + /// Removes the specified session subscription. + /// The subscription. + Task RemoveAsync(TopicSubscription subscription); } diff --git a/src/Trakx.CryptoCompare.ApiClient.Websocket/Trakx.CryptoCompare.ApiClient.Websocket.csproj b/src/Trakx.CryptoCompare.ApiClient.Websocket/Trakx.CryptoCompare.ApiClient.Websocket.csproj index 181f1ba..edfae1b 100644 --- a/src/Trakx.CryptoCompare.ApiClient.Websocket/Trakx.CryptoCompare.ApiClient.Websocket.csproj +++ b/src/Trakx.CryptoCompare.ApiClient.Websocket/Trakx.CryptoCompare.ApiClient.Websocket.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Trakx.CryptoCompare.ApiClient/CryptoCompareApiConfiguration.cs b/src/Trakx.CryptoCompare.ApiClient/CryptoCompareApiConfiguration.cs index daf9ce4..ca1d33d 100644 --- a/src/Trakx.CryptoCompare.ApiClient/CryptoCompareApiConfiguration.cs +++ b/src/Trakx.CryptoCompare.ApiClient/CryptoCompareApiConfiguration.cs @@ -2,21 +2,17 @@ using System.Text.Json.Serialization; using Trakx.Common.Attributes; -namespace Trakx.CryptoCompare.ApiClient +namespace Trakx.CryptoCompare.ApiClient; + +public record CryptoCompareApiConfiguration { - public record CryptoCompareApiConfiguration - { -#nullable disable - public string WebSocketBaseUrl { get; init; } = "wss://streamer.cryptocompare.com/"; + public string WebSocketBaseUrl { get; init; } = "wss://streamer.cryptocompare.com/"; - [AwsParameter] - [SecretEnvironmentVariable] - public string ApiKey { get; set; } + [AwsParameter(AllowGlobal = true)] + public string? ApiKey { get; set; } - [JsonIgnore] - public Uri WebSocketEndpoint => new Uri(new Uri(WebSocketBaseUrl), $"v2?api_key={ApiKey}"); + [JsonIgnore] + public Uri WebSocketEndpoint => new(new Uri(WebSocketBaseUrl), $"v2?api_key={ApiKey}"); - public int ThrottleDelayMs { get; init; } = 0; -#nullable restore - } + public int ThrottleDelayMs { get; init; } = 0; } diff --git a/src/Trakx.CryptoCompare.ApiClient/Trakx.CryptoCompare.ApiClient.csproj b/src/Trakx.CryptoCompare.ApiClient/Trakx.CryptoCompare.ApiClient.csproj index 867983d..905ca71 100644 --- a/src/Trakx.CryptoCompare.ApiClient/Trakx.CryptoCompare.ApiClient.csproj +++ b/src/Trakx.CryptoCompare.ApiClient/Trakx.CryptoCompare.ApiClient.csproj @@ -9,9 +9,9 @@ - + - +