diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6ef6dc0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,289 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + +env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + - name: Restore dependencies + run: dotnet restore + + - name: Cache NuGet packages + uses: actions/cache@v5 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/global.json') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: Build + run: dotnet build --no-restore --configuration Release + + test: + name: Test + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Cache NuGet packages + uses: actions/cache@v5 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/global.json') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: Build + run: dotnet build --configuration Release --no-restore + + - name: Expose GitHub Actions Runtime + uses: actions/github-script@v9 + with: + script: | + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']); + core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']); + + - name: Run unit tests with coverage + run: dotnet test --configuration Release --no-build --results-directory ./TestResults -- --coverage --report-trx --treenode-filter "/*/*/*/*[Category=UnitTest]" --coverage-output-format cobertura --ignore-exit-code 8 + + - name: Upload TRX artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: trx-unit + path: ./TestResults/**/*.trx + + - name: Upload coverage artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: coverage-unit + path: ./TestResults/**/*.cobertura.xml + + component-test: + name: Component test + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Cache NuGet packages + uses: actions/cache@v5 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/global.json') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: Build + run: dotnet build --configuration Release --no-restore + + - name: Expose GitHub Actions Runtime + uses: actions/github-script@v9 + with: + script: | + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']); + core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']); + + - name: Run Blazor component tests with coverage + run: dotnet test --configuration Release --no-build --results-directory ./TestResults -- --coverage --report-trx --treenode-filter "/*/*/*/*[Category=Components]" --coverage-output-format cobertura --ignore-exit-code 8 + + - name: Upload TRX artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: trx-component + path: ./TestResults/**/*.trx + + - name: Upload coverage artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: coverage-component + path: ./TestResults/**/*.cobertura.xml + + integration-test: + name: Integration Test + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Cache NuGet packages + uses: actions/cache@v5 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/global.json') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: Build + run: dotnet build --configuration Release --no-restore + + - name: Expose GitHub Actions Runtime + uses: actions/github-script@v9 + with: + script: | + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']); + core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']); + + - name: Run integration tests with coverage + run: dotnet test --configuration Release --no-build --results-directory ./TestResults -- --coverage --report-trx --treenode-filter "/*/*/*/*[Category=Integration]" --coverage-output-format cobertura --ignore-exit-code 8 + + - name: Upload TRX artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: trx-integration + path: ./TestResults/**/*.trx + + - name: Upload coverage artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: coverage-integration + path: ./TestResults/**/*.cobertura.xml + + aspire-test: + name: Aspire Test + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ vars.DOTNET_VERSION }} + + - name: Restore dependencies + run: dotnet restore + + - name: Cache NuGet packages + uses: actions/cache@v5 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/global.json') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: Build + run: dotnet build --configuration Release --no-restore + + - name: Expose GitHub Actions Runtime + uses: actions/github-script@v9 + with: + script: | + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']); + core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']); + + - name: Trust ASP.NET Core dev certificate + run: | + mkdir -p "$HOME/.aspnet/https" + dotnet dev-certs https -ep "$HOME/.aspnet/https/devcert.crt" --format PEM --no-password + sudo cp "$HOME/.aspnet/https/devcert.crt" /usr/local/share/ca-certificates/dotnet-devcert.crt + sudo update-ca-certificates + + - name: Run Aspire tests with coverage + run: dotnet test --configuration Release --no-build --results-directory ./TestResults -- --coverage --report-trx --treenode-filter "/*/*/*/*[Category=AppHost]" --coverage-output-format cobertura --ignore-exit-code 8 + env: + ASPIRE_ALLOW_UNSECURED_TRANSPORT: "true" + + - name: Upload TRX artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: trx-aspire + path: ./TestResults/**/*.trx + + - name: Upload coverage artifact + uses: actions/upload-artifact@v7 + if: always() + with: + name: coverage-aspire + path: ./TestResults/**/*.cobertura.xml + + report: + name: Report + runs-on: ubuntu-latest + needs: [ test, component-test, integration-test, aspire-test ] + if: always() + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Download all TRX artifacts + uses: actions/download-artifact@v8 + with: + pattern: trx-* + path: TestResults + merge-multiple: true + + - name: Download all coverage artifacts + uses: actions/download-artifact@v8 + with: + pattern: coverage-* + path: TestResults + + - name: List downloaded artifacts + if: always() + run: find TestResults -type f | sort 2>/dev/null || echo "No files found in TestResults" + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: ./TestResults/**/*.cobertura.xml + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/codecov-action@v5 + with: + files: ./TestResults/**/*.trx + token: ${{ secrets.CODECOV_TOKEN }} + report_type: test_results \ No newline at end of file diff --git a/.github/workflows/code-inspection.yml b/.github/workflows/code-inspection.yml new file mode 100644 index 0000000..cf10d14 --- /dev/null +++ b/.github/workflows/code-inspection.yml @@ -0,0 +1,50 @@ +name: Code inspection +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - main + - 'releases/*' # The release branches +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ vars.DOTNET_VERSION }} + + - name: Cache NuGet packages + uses: actions/cache@v5 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/global.json') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --configuration Debug --no-restore + +# - name: Install .NET SDK for Qodana +# run: curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 --install-dir "$GITHUB_WORKSPACE/.dotnet" + + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.3 + with: + pr-mode: false +# args: --solution TUnit.Sample.slnx --property=dotnet.msbuild.dotnet-cli=/usr/share/dotnet/dotnet + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_1311845410 }} \ No newline at end of file diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 7fb344b..0000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: # Specify your branches here - - main # The 'main' branch - - 'releases/*' # The release branches - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit - fetch-depth: 0 # a full history is required for pull request analysis - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.3 - with: - pr-mode: false - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_1311845410 }} - QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..3b8d4af --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: + default: + target: 80% + threshold: 3% + patch: + default: + target: 80% + threshold: 3% + +comment: + layout: "condensed_header, diff, flags, components" + require_changes: true \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..31c6981 --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "10.0.201", + "rollForward": "latestPatch" + }, + "test": { + "runner": "Microsoft.Testing.Platform" + } +} diff --git a/qodana.yaml b/qodana.yaml index b0a303c..24d3c5c 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -3,7 +3,8 @@ #################################################################################################################### version: "1.0" -linter: jetbrains/qodana-cdnet:2025.3 +dotnet: + solution: TUnit.Sample.slnx profile: name: qodana.recommended include: diff --git a/test/TUnit.Sample.ApiService.IntegrationTests/Utility/CoreIntegrationTestBase.cs b/test/TUnit.Sample.ApiService.IntegrationTests/Utility/CoreIntegrationTestBase.cs index ebd6ad7..6b81863 100644 --- a/test/TUnit.Sample.ApiService.IntegrationTests/Utility/CoreIntegrationTestBase.cs +++ b/test/TUnit.Sample.ApiService.IntegrationTests/Utility/CoreIntegrationTestBase.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Npgsql; @@ -40,7 +41,8 @@ CREATE SCHEMA IF NOT EXISTS "{SchemaName}" .Options; await using var dbContext = new CoreDbContext(options) { SchemaName = SchemaName }; - await dbContext.Database.EnsureCreatedAsync(); + var dbCreator = (RelationalDatabaseCreator)dbContext.Database.GetService(); + await dbCreator.CreateTablesAsync(); } protected override void ConfigureTestConfiguration(IConfigurationBuilder config) diff --git a/test/TUnit.Sample.AppHost.IntegrationTests/AppFixture.cs b/test/TUnit.Sample.AppHost.IntegrationTests/AppFixture.cs index 91ca51f..85899e6 100644 --- a/test/TUnit.Sample.AppHost.IntegrationTests/AppFixture.cs +++ b/test/TUnit.Sample.AppHost.IntegrationTests/AppFixture.cs @@ -5,8 +5,9 @@ namespace TUnit.Sample.AppHost.IntegrationTests; public class AppFixture : AspireFixture { + protected override TimeSpan ResourceTimeout => TimeSpan.FromMinutes(4); protected override ResourceWaitBehavior WaitBehavior => ResourceWaitBehavior.Named; - protected override IEnumerable ResourcesToWaitFor() => [ResourceConstants.WebApi, ResourceConstants.WebFrontend]; + protected override IEnumerable ResourcesToWaitFor() => [ResourceConstants.WebApi]; protected override void ConfigureBuilder(IDistributedApplicationTestingBuilder builder) { diff --git a/test/TUnit.Sample.AppHost.IntegrationTests/GlobalSetup.cs b/test/TUnit.Sample.AppHost.IntegrationTests/GlobalSetup.cs index 2c94b0c..e7c1615 100644 --- a/test/TUnit.Sample.AppHost.IntegrationTests/GlobalSetup.cs +++ b/test/TUnit.Sample.AppHost.IntegrationTests/GlobalSetup.cs @@ -1 +1 @@ -[assembly: Category("Integration")] \ No newline at end of file +[assembly: Category("AppHost")] \ No newline at end of file