From 0c464804284ebc4a0ef15bada0e6ec20e9be667e Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Mon, 27 Jul 2026 10:53:57 +1200 Subject: [PATCH 1/2] chore(deps): clear all package advisories and take .NET 10 servicing patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A full `dotnet list package --vulnerable --include-transitive` showed five advisories, not the one visible in build warnings — the rest were being lost in warning noise. All are now clear across the solution and the build project. Security: - OpenTelemetry 1.11.x -> 1.17.0 (Prometheus exporter 1.10.0-beta.1 -> 1.17.0-beta.1 to stay in step). GHSA-g94r-2vxg-569j + GHSA-4625-4j76-fww9. Highest-impact of the set: ServiceDefaults is referenced by every host, so this shipped everywhere. - SharpCompress 0.38.0 -> 0.50.1. GHSA-6c8g-7p36-r338. Interim fix — its only consumer is the dead MediathekView parser that #49 removes outright. - Aspire.Hosting* 13.1.2 -> 13.4.6, which drops the StreamJsonRpc that carried MessagePack 2.5.192. GHSA-hv8m-jj95-wg3x. - Fallout.Common/Components 10.3.49 -> 10.4.0-rc.4, picking up Microsoft.Build 18.0.2 and clearing the five System.Security.Cryptography.Xml advisories on the Nuke build project. Servicing patches, same major (no API surface change): - EF Core + Design + SqlServer 10.0.4 -> 10.0.10 - Microsoft.Extensions.{Hosting.Abstractions,Configuration.Binder,Http} and Microsoft.AspNetCore.OpenApi 10.0.0 -> 10.0.10 - Riok.Mapperly 4.1.1 -> 4.3.1 The Aspire bump needed a second change to work. `Aspire.AppHost.Sdk` in Krautwatch.AppHost.csproj separately pins Aspire.Hosting.AppHost, the DCP orchestrator and the dashboard; leaving it at 13.1.2 while Aspire.Hosting moved to 13.4.6 made DCP refuse to start outright ("Newer version of the Aspire.Hosting.AppHost package is required"). Bumped to 13.4.6 with a comment so the lockstep requirement is not rediscovered the hard way. Deliberately NOT bumped — majors wanting their own PR and real testing: WolverineFx 5.18 -> 6.22 (the messaging/outbox backbone), NSubstitute 5 -> 6, Microsoft.NET.Test.Sdk 17 -> 18, xunit.runner.visualstudio 2 -> 3, and Microsoft.Extensions.{ServiceDiscovery,Http.Resilience} 9.x -> 10.8.0. Verified: `dotnet list package --vulnerable --include-transitive` clean on both the solution and build/_build.csproj. Build is 0 warnings / 0 errors. Tests green (Infra 67 / App 50 / Arch 8). `dotnet run --project AppHost` brings the fleet up — DCP created the Postgres container and started all five long-running hosts, with the run-to-completion migrator finishing first. Note: Aspire 13.4.6 defaults the dev Postgres container to postgres:18.3 (was 17.x). Dev data is ephemeral so this is harmless, but our Testcontainers fixture pins postgres:17-alpine and could be aligned. Co-Authored-By: Claude Opus 5 (1M context) --- .fallout/build.schema.json | 7 ++++ .github/workflows/build.yml | 12 +++++-- Directory.Packages.props | 34 +++++++++---------- build/_build.csproj | 4 +-- .../AppHost/Krautwatch.AppHost.csproj | 5 ++- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json index dcfe64d..472ce39 100644 --- a/.fallout/build.schema.json +++ b/.fallout/build.schema.json @@ -93,6 +93,13 @@ "Verbosity": { "description": "Logging verbosity during build execution. Default is 'Normal'", "$ref": "#/definitions/Verbosity" + }, + "BuildProjectFile": { + "type": [ + "null", + "string" + ], + "description": "Path to the build project (.csproj) relative to the repository root. Defaults to 'build/_build.csproj' when unset. Read by the Fallout global tool's in-tool runner." } } } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b88069..2c007bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,15 +29,21 @@ jobs: name: ubuntu-latest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Setup: .NET SDK' + uses: actions/setup-dotnet@v6 + with: + global-json-file: global.json + - name: 'Restore: dotnet tools' + run: dotnet tool restore - name: 'Run: Test' - run: ./build.cmd Test + run: dotnet fallout Test diff --git a/Directory.Packages.props b/Directory.Packages.props index 2bdeaa3..42f6421 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,17 +9,17 @@ - - + + - + - + @@ -28,31 +28,31 @@ - - - - - - + + + + + + - + - - - + + + - + - - + + diff --git a/build/_build.csproj b/build/_build.csproj index 2bd51d3..ce2b63d 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/src/Presentation/AppHost/Krautwatch.AppHost.csproj b/src/Presentation/AppHost/Krautwatch.AppHost.csproj index 986f200..1701692 100644 --- a/src/Presentation/AppHost/Krautwatch.AppHost.csproj +++ b/src/Presentation/AppHost/Krautwatch.AppHost.csproj @@ -1,6 +1,9 @@ - + + Exe From a742c71bacb0078e8e011333eb1866490007a33c Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Mon, 27 Jul 2026 11:32:35 +1200 Subject: [PATCH 2/2] chore(deps): upgrade test tooling to current majors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Takes the three dev-tooling majors held back from the previous commit. These are build/test-time only — nothing here ships in a host: - Microsoft.NET.Test.Sdk 17.14.1 -> 18.8.1 - xunit.runner.visualstudio 2.8.2 -> 3.1.5 (the v3 runner supports the xunit 2.9.3 core we use) - NSubstitute 5.3.0 -> 6.0.0 Shouldly (4.3.0), xunit (2.9.3) and ArchUnitNET (0.13.3) were already current. NSubstitute 6 changed the nullability annotation on Arg.Is's predicate — the lambda parameter is now T? — which surfaced four new CS8602/CS8604 warnings in Application.Tests. Fixed with explicit null guards rather than the null-forgiving operator, so a null argument fails the Received() assertion with NSubstitute's own message instead of throwing an NRE inside the matcher. Verified the test-runner major did not break trait filtering, which is what separates the two build targets: - ./build.sh Test -> 125 tests, Live correctly EXCLUDED (9s) - ./build.sh TestLive -> 16/16 Live tests, correctly INCLUDED, real ARD/ZDF network downloads green (6m6s) Build is back to 0 warnings / 0 errors. Also corrected a prerequisite I got wrong in the README rewrite: it told readers to run `dotnet workload install aspire`. That is legacy — Aspire 13 arrives via the Aspire.AppHost.Sdk reference plus NuGet packages. Confirmed empirically: this machine has NO workloads installed and the fleet starts fine. global.json is deliberately left at 10.0.100 — with rollForward latestMinor that is a floor, not a pin (it resolves to 10.0.302 here). Raising it would only exclude contributors on an earlier 10.0.x feature band for no gain. Co-Authored-By: Claude Opus 5 (1M context) --- Directory.Packages.props | 6 +++--- README.md | 7 +++++-- tests/Application.Tests/CrawlShowHandlerTests.cs | 2 +- tests/Application.Tests/RefreshProxyListHandlerTests.cs | 2 +- tests/Application.Tests/SabnzbdDownloadTests.cs | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 42f6421..2eb42a0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -58,12 +58,12 @@ - + - + - + diff --git a/README.md b/README.md index 168ffc5..c088004 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,13 @@ Built on .NET 10, Postgres, Wolverine and .NET Aspire. ## Prerequisites - **.NET 10 SDK** (`10.0.100+`, see `global.json`) -- **.NET Aspire workload** — `dotnet workload install aspire` -- **Docker** — Aspire provisions the Postgres container +- **Docker** — Aspire provisions the Postgres container, and the repository tests use Testcontainers - **ffmpeg** on PATH — `brew install ffmpeg` (used to remux HLS streams; the Downloader image bundles it) +> No Aspire *workload* install is needed. Aspire 13 comes in via the `Aspire.AppHost.Sdk` reference in +> `Krautwatch.AppHost.csproj` plus NuGet packages — the old `dotnet workload install aspire` step is +> legacy and does not apply. + --- ## Running the fleet locally diff --git a/tests/Application.Tests/CrawlShowHandlerTests.cs b/tests/Application.Tests/CrawlShowHandlerTests.cs index 90bddd5..003789d 100644 --- a/tests/Application.Tests/CrawlShowHandlerTests.cs +++ b/tests/Application.Tests/CrawlShowHandlerTests.cs @@ -46,7 +46,7 @@ public async Task Handle_selects_crawler_by_provider_and_upserts_the_crawled_epi zdf.LastQuery.ShouldBe("heute-show"); otherProvider.LastQuery.ShouldBeNull(); // the ARD crawler must not be invoked await repo.Received(1).UpsertManyAsync( - Arg.Is>(e => e.Count() == 2), Arg.Any()); + Arg.Is>(e => e != null && e.Count() == 2), Arg.Any()); } [Fact] diff --git a/tests/Application.Tests/RefreshProxyListHandlerTests.cs b/tests/Application.Tests/RefreshProxyListHandlerTests.cs index 80e4370..5c2c3d7 100644 --- a/tests/Application.Tests/RefreshProxyListHandlerTests.cs +++ b/tests/Application.Tests/RefreshProxyListHandlerTests.cs @@ -25,7 +25,7 @@ public async Task Upserts_the_fetched_candidates() await new RefreshProxyListHandler(source, repo, NullLogger.Instance).HandleAsync(); await repo.Received(1).UpsertBatchAsync( - Arg.Is>(ps => ps.Count() == 2), Arg.Any()); + Arg.Is>(ps => ps != null && ps.Count() == 2), Arg.Any()); } [Fact] diff --git a/tests/Application.Tests/SabnzbdDownloadTests.cs b/tests/Application.Tests/SabnzbdDownloadTests.cs index 6de8fcd..a33611e 100644 --- a/tests/Application.Tests/SabnzbdDownloadTests.cs +++ b/tests/Application.Tests/SabnzbdDownloadTests.cs @@ -67,7 +67,7 @@ public async Task Creates_and_enqueues_a_job_for_a_known_token() var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("zdf:1"); jobId.ShouldNotBeNull(); - await jobs.Received(1).AddAsync(Arg.Is(j => j.EpisodeId == "zdf:1" && j.StreamUrl == "https://cdn/x.mp4"), Arg.Any()); + await jobs.Received(1).AddAsync(Arg.Is(j => j != null && j.EpisodeId == "zdf:1" && j.StreamUrl == "https://cdn/x.mp4"), Arg.Any()); await queue.Received(1).EnqueueAsync(jobId!.Value, "https://cdn/x.mp4", Arg.Any()); } @@ -82,7 +82,7 @@ public async Task Snapshots_the_episodes_geo_restriction_onto_the_job() await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("kika:1"); - await jobs.Received(1).AddAsync(Arg.Is(j => j.GeoRestricted), Arg.Any()); + await jobs.Received(1).AddAsync(Arg.Is(j => j != null && j.GeoRestricted), Arg.Any()); } [Fact]