From 9d8da41e6c827ffdbb309a4ca32c4411b6ab4edb Mon Sep 17 00:00:00 2001 From: ancplua Date: Sat, 16 May 2026 04:14:59 +0200 Subject: [PATCH 1/5] fix(tests): drop orphan ListenerExecuteAsyncTests + uppercase XmlContent const Unblocks Compile target which was failing with: - AL0115 at ListenerExecuteAsyncTests.cs:254 (empty catch in ListenerStreams helper) - IDE1006 at ReportProcessorTests.cs:554 (const must be PascalCase) The whole ListenerExecuteAsyncTests.cs file is orphaned by ListenerLifecycleTests.cs which fully supersedes it (leftover from PR #16 rebase). ListenerStreams helper is only referenced inside that same file, so deletion is self-contained. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Unit/ListenerExecuteAsyncTests.cs | 259 ------------------ .../Unit/ReportProcessorTests.cs | 4 +- 2 files changed, 2 insertions(+), 261 deletions(-) delete mode 100644 PaperlessREST.Tests/Unit/ListenerExecuteAsyncTests.cs diff --git a/PaperlessREST.Tests/Unit/ListenerExecuteAsyncTests.cs b/PaperlessREST.Tests/Unit/ListenerExecuteAsyncTests.cs deleted file mode 100644 index 3ec2f2e..0000000 --- a/PaperlessREST.Tests/Unit/ListenerExecuteAsyncTests.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using RabbitMQ.Client.Exceptions; - -namespace PaperlessREST.Tests.Unit; - -/// -/// Lifecycle tests for the BackgroundService and -/// overrides. Cover the consumer factory wiring, -/// the consume-loop, and the generic-exception catch branch on the GenAI listener. -/// -/// Completion is signalled via set from a mock callback -/// (per CLAUDE.md): never poll a log snapshot. -/// -/// -/// The OperationInterruptedException "no queue" branch is intentionally not unit-tested: -/// constructing that exception requires RabbitMQ-internal ShutdownEventArgs types that aren't -/// in the test project's surface, and the branch is a one-off shutdown helper — covered -/// operationally on a missing-queue restart, not via fake-broker reproduction here. -/// -/// -public sealed class GenAiResultListenerExecuteAsyncTests : IDisposable -{ - private readonly Mock> _consumer; - private readonly Mock _consumerFactory; - private readonly Mock _documentService; - private readonly FakeLogCollector _logCollector = new(); - private readonly FakeLogger _logger; - private readonly MockRepository _mocks = new(MockBehavior.Strict) { DefaultValue = DefaultValue.Empty }; - private readonly Mock _scope; - private readonly Mock _scopeFactory; - private readonly Mock _serviceProvider; - private readonly Mock> _sseStream; - - public GenAiResultListenerExecuteAsyncTests() - { - _scopeFactory = _mocks.Create(); - _scope = _mocks.Create(); - _serviceProvider = _mocks.Create(); - _documentService = _mocks.Create(); - _consumerFactory = _mocks.Create(); - _consumer = _mocks.Create>(); - _sseStream = _mocks.Create>(); - _logger = new FakeLogger(_logCollector); - - _scope.As().Setup(d => d.DisposeAsync()).Returns(ValueTask.CompletedTask); - _scopeFactory.Setup(f => f.CreateScope()).Returns(_scope.Object); - _scope.Setup(s => s.ServiceProvider).Returns(_serviceProvider.Object); - _serviceProvider.Setup(p => p.GetService(typeof(IDocumentService))).Returns(_documentService.Object); - _consumer.As().Setup(d => d.DisposeAsync()).Returns(ValueTask.CompletedTask); - } - - public void Dispose() - { - TestContext.Current.SendDiagnosticMessage("Full logs:\n{0}", _logCollector.GetFullLoggerText()); - } - - private GenAiResultListener CreateSut() => - new(_consumerFactory.Object, _scopeFactory.Object, _sseStream.Object, _logger); - - [Fact] - public async Task ExecuteAsync_HappyPath_LogsStartedConsumesAndStopped() - { - GenAIEvent evt = new(Guid.CreateVersion7(), "summary", TimeProvider.System.GetUtcNow(), null); - TaskCompletionSource processed = new(TaskCreationOptions.RunContinuationsAsynchronously); - - _consumerFactory.Setup(f => f.CreateConsumerAsync()) - .ReturnsAsync(_consumer.Object); - _consumer.Setup(c => c.ConsumeAsync(It.IsAny())) - .Returns((CancellationToken ct) => ListenerStreams.SingleThenWait(evt, ct)); - _documentService.Setup(s => s.UpdateDocumentSummaryAsync( - evt.DocumentId, evt.Summary!, evt.GeneratedAt, It.IsAny())) - .ReturnsAsync(Result.Updated); - _sseStream.Setup(s => s.Publish(evt)); - _consumer.Setup(c => c.AckAsync()) - .Returns(() => - { - processed.TrySetResult(true); - return Task.CompletedTask; - }); - - using GenAiResultListener sut = CreateSut(); - using CancellationTokenSource cts = new(); - - await sut.StartAsync(cts.Token); - await processed.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); - await cts.CancelAsync(); - await sut.ExecuteTask!; - - _logCollector.GetSnapshot().Should().Contain(l => - l.Level == LogLevel.Information && l.Message.Contains("GenAI Result Listener started")); - _logCollector.GetSnapshot().Should().Contain(l => - l.Level == LogLevel.Information && l.Message.Contains("GenAI Result Listener stopped")); - } - - [Fact] - public async Task ExecuteAsync_NoQueueOperationInterrupted_LogsWarningAndAwaitsCancellation() - { - // Constructing OperationInterruptedException through its real ctor requires RabbitMQ- - // internal ShutdownEventArgs types not in the test surface. To exercise the listener's - // "no queue" catch arm, we sidestep the ctor with GetUninitializedObject and inject the - // Message field via reflection — the catch's `when (ex.Message.Contains("no queue"))` - // matches purely on text, so a bare exception with the right message is sufficient. - OperationInterruptedException ex = - (OperationInterruptedException)RuntimeHelpers.GetUninitializedObject(typeof(OperationInterruptedException)); - FieldInfo? messageField = typeof(Exception).GetField("_message", BindingFlags.Instance | BindingFlags.NonPublic); - messageField!.SetValue(ex, "NOT_FOUND - no queue 'GenAI'"); - - _consumerFactory.Setup(f => f.CreateConsumerAsync()).ThrowsAsync(ex); - - using GenAiResultListener sut = CreateSut(); - using CancellationTokenSource cts = new(); - - await sut.StartAsync(cts.Token); - - // The listener should now be sleeping in Task.Delay(Timeout.Infinite, stoppingToken) - // inside the catch arm. Wait for the warning, then cancel to unblock. - TimeSpan timeout = TimeSpan.FromSeconds(5); - using CancellationTokenSource waitCts = new(timeout); - while (!waitCts.IsCancellationRequested && - !_logCollector.GetSnapshot().Any(l => - l.Level == LogLevel.Warning && - l.Message.Contains("GenAI Result Listener disabled", StringComparison.OrdinalIgnoreCase))) - { - await Task.Delay(20, TestContext.Current.CancellationToken); - } - - await cts.CancelAsync(); - - Func awaitExecute = async () => await sut.ExecuteTask!; - await awaitExecute.Should().ThrowAsync(); - - _logCollector.GetSnapshot().Should().Contain(l => - l.Level == LogLevel.Warning && - l.Message.Contains("GenAI Result Listener disabled", StringComparison.OrdinalIgnoreCase)); - } - - [Fact] - public async Task ExecuteAsync_UnexpectedException_LogsErrorAndRethrows() - { - InvalidOperationException boom = new("broker down"); - _consumerFactory.Setup(f => f.CreateConsumerAsync()) - .ThrowsAsync(boom); - - using GenAiResultListener sut = CreateSut(); - using CancellationTokenSource cts = new(); - - Func startAndAwait = async () => - { - await sut.StartAsync(cts.Token); - await sut.ExecuteTask!; - }; - - await startAndAwait.Should().ThrowAsync().WithMessage("broker down"); - _logCollector.GetSnapshot().Should().Contain(l => - l.Level == LogLevel.Error && l.Message.Contains("Unexpected error", StringComparison.OrdinalIgnoreCase)); - } - -} - -public sealed class OcrResultListenerExecuteAsyncTests : IDisposable -{ - private readonly Mock> _consumer; - private readonly Mock _consumerFactory; - private readonly Mock _documentService; - private readonly FakeLogCollector _logCollector = new(); - private readonly FakeLogger _logger; - private readonly MockRepository _mocks = new(MockBehavior.Strict) { DefaultValue = DefaultValue.Empty }; - private readonly Mock _scope; - private readonly Mock _scopeFactory; - private readonly Mock _serviceProvider; - private readonly Mock> _sseStream; - - public OcrResultListenerExecuteAsyncTests() - { - _scopeFactory = _mocks.Create(); - _scope = _mocks.Create(); - _serviceProvider = _mocks.Create(); - _documentService = _mocks.Create(); - _consumerFactory = _mocks.Create(); - _consumer = _mocks.Create>(); - _sseStream = _mocks.Create>(); - _logger = new FakeLogger(_logCollector); - - _scope.As().Setup(d => d.DisposeAsync()).Returns(ValueTask.CompletedTask); - _scopeFactory.Setup(f => f.CreateScope()).Returns(_scope.Object); - _scope.Setup(s => s.ServiceProvider).Returns(_serviceProvider.Object); - _serviceProvider.Setup(p => p.GetService(typeof(IDocumentService))).Returns(_documentService.Object); - _consumer.As().Setup(d => d.DisposeAsync()).Returns(ValueTask.CompletedTask); - } - - public void Dispose() - { - TestContext.Current.SendDiagnosticMessage("Full logs:\n{0}", _logCollector.GetFullLoggerText()); - } - - private OcrResultListener CreateSut() => - new(_consumerFactory.Object, _scopeFactory.Object, _sseStream.Object, _logger); - - [Fact] - public async Task ExecuteAsync_HappyPath_LogsStartedConsumesAndStopped() - { - OcrEvent evt = new(Guid.CreateVersion7(), "Completed", "ocr text", TimeProvider.System.GetUtcNow()); - TaskCompletionSource processed = new(TaskCreationOptions.RunContinuationsAsynchronously); - - _consumerFactory.Setup(f => f.CreateConsumerAsync()) - .ReturnsAsync(_consumer.Object); - _consumer.Setup(c => c.ConsumeAsync(It.IsAny())) - .Returns((CancellationToken ct) => ListenerStreams.SingleThenWait(evt, ct)); - _documentService.Setup(s => s.ProcessOcrResultAsync( - evt.JobId, "Completed", "ocr text", It.IsAny())) - .ReturnsAsync(Result.Updated); - _sseStream.Setup(s => s.Publish(evt)); - _consumer.Setup(c => c.AckAsync()) - .Returns(() => - { - processed.TrySetResult(true); - return Task.CompletedTask; - }); - - using OcrResultListener sut = CreateSut(); - using CancellationTokenSource cts = new(); - - await sut.StartAsync(cts.Token); - await processed.Task.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken); - await cts.CancelAsync(); - await sut.ExecuteTask!; - - _logCollector.GetSnapshot().Should().Contain(l => - l.Level == LogLevel.Information && l.Message.Contains("OCR Result Listener started")); - _logCollector.GetSnapshot().Should().Contain(l => - l.Level == LogLevel.Information && l.Message.Contains("OCR Result Listener stopped")); - } - -} - -/// -/// Helpers for fabricating streams in listener lifecycle tests. -/// Each stream completes cleanly on token cancellation so the consume-loop in -/// exits and the "stopped" log gets emitted. -/// -internal static class ListenerStreams -{ - /// Yields one item, then waits indefinitely; completes cleanly on token cancellation. - public static async IAsyncEnumerable SingleThenWait( - T item, [EnumeratorCancellation] CancellationToken ct = default) - { - yield return item; - - try - { - await Task.Delay(Timeout.Infinite, ct).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - } - -} diff --git a/PaperlessREST.Tests/Unit/ReportProcessorTests.cs b/PaperlessREST.Tests/Unit/ReportProcessorTests.cs index e561e4b..8918579 100644 --- a/PaperlessREST.Tests/Unit/ReportProcessorTests.cs +++ b/PaperlessREST.Tests/Unit/ReportProcessorTests.cs @@ -551,13 +551,13 @@ public async Task ProcessAsync_DateWithTimezone_FailsDateOnlyTryParseExact() { // Arrange — "2024-01-15+02:00" satisfies xs:date (which permits timezone suffix) // but DateOnly.TryParseExact("yyyy-MM-dd", ...) rejects it. - const string xmlContent = """ + const string XmlContent = """ """; - string filePath = CreateTestFile("date-with-tz.xml", xmlContent); + string filePath = CreateTestFile("date-with-tz.xml", XmlContent); ReportProcessor sut = CreateSut(); // Act From a1cd53529319a8ce02edfd72471d2d5b235ab721 Mon Sep 17 00:00:00 2001 From: ancplua Date: Sat, 16 May 2026 04:57:32 +0200 Subject: [PATCH 2/5] build(deps): bump every pinned version to current stable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Centralized "update every single version" sweep across the four version files. NuGet (Version.props + Directory.Packages.props): - Microsoft.* runtime line: 10.0.0 → 10.0.8 (EF Core, AspNetCore.OpenApi, Extensions.Hosting/Logging/Options, AspNetCore.Mvc.Testing) - Microsoft.Extensions testing/telemetry (split out to own var): 10.0.0 → 10.6.0 - Npgsql 10.0.0 → 10.0.2; Npgsql.EFCore.PostgreSQL (split out): 10.0.0 → 10.0.1 - Asp.Versioning.* 8.1.x → 10.0.0 (aligns with .NET 10) - Testcontainers.* 4.9.0 → 4.11.0 - Elastic.Clients.Elasticsearch 9.2.2 → 9.4.0 - Testably.Abstractions.Testing 5.0.1 → 6.3.0 (major) - Scalar.AspNetCore 2.11.0 → 2.14.14 - ErrorOr 2.0.1 → 2.1.1 - xunit.v3.mtp-v2 3.2.1 → 3.2.2 - Microsoft.Testing.Extensions.CodeCoverage 18.1.0 → 18.6.2 - Microsoft.Testing.Extensions.TrxReport 2.0.2 → 2.2.3 - Testably.Abstractions 10.0.0 → 10.2.0 - CreatePdf.NET 3.0.3 → 3.0.4 - DotNetEnv 3.1.1 → 3.2.0 - Security pins: NuGet.Packaging 7.3.1 → 7.6.0, System.Security.Cryptography.Xml 10.0.7 → 10.0.8 Docker images (.env.test + WorkerTestBase.cs + SharedRestContainerFixture.cs): - elasticsearch: 9.1.3 → 9.4.1 (testing the ES-9-quirk theory for the MultipleDocuments_SearchCorrectly flake) - rabbitmq: 4.1.4-management → 4.3.0-management - minio: RELEASE.2025-07-23 → RELEASE.2025-09-07 - postgres: kept at floating 17-alpine Also synced SharedRestContainerFixture.cs C# defaults which had drifted (postgres:16, rabbitmq:3.13). Toolchain: - ANcpLua.NET.Sdk{,.Web,.Test}: 3.4.29 → 3.4.32 (global.json) - pnpm: 10.30.2 → 11.1.2 (package.json + CI corepack lines) - actions/checkout@v4 → v6 - actions/setup-dotnet@v4 → v5 - actions/cache@v4 → v5 - actions/setup-node@v4 → v6 - codecov/codecov-action@v5 → v6 Held: - actions/upload-artifact@v4 (v7 has breaking immutability changes; needs migration) - Mapster.DependencyInjection 1.0.1 (10.0.7 is a calendar-versioning shift; needs release-notes review) - Hangfire.PostgreSql 1.21.1 (current pin is ahead of nuget.org search result; preserved) Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.test | 6 +-- .github/workflows/ci.yml | 20 +++++----- Directory.Packages.props | 15 +++---- .../Integration/SharedRestContainerFixture.cs | 8 ++-- .../Integration/WorkerTestBase.cs | 6 +-- PaperlessUI.Angular/package.json | 2 +- Version.props | 40 ++++++++++--------- global.json | 6 +-- 8 files changed, 54 insertions(+), 49 deletions(-) diff --git a/.env.test b/.env.test index 16c2218..bc1b9b3 100644 --- a/.env.test +++ b/.env.test @@ -19,9 +19,9 @@ # gap with the production compose stack so integration tests exercise the same # migration / dialect surface the deployed REST + Services see. POSTGRES_IMAGE=postgres:17-alpine -RABBITMQ_IMAGE=rabbitmq:4.1.4-management -MINIO_IMAGE=minio/minio:RELEASE.2025-07-23T15-54-02Z -ELASTIC_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:9.1.3 +RABBITMQ_IMAGE=rabbitmq:4.3.0-management +MINIO_IMAGE=minio/minio:RELEASE.2025-09-07T16-13-09Z +ELASTIC_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:9.4.1 # Container credentials (placeholders; Testcontainers regenerates) POSTGRES_DB=paperless diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb34598..bee55d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,17 +32,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup .NET 10 - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: 10.0.x - name: Cache NuGet packages - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | .nuke/temp @@ -76,7 +76,7 @@ jobs: - name: Upload coverage to Codecov if: always() - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: files: ./Artifacts/coverage/PaperlessREST.Tests/coverage.cobertura.xml,./Artifacts/coverage/PaperlessServices.Tests/coverage.cobertura.xml flags: backend @@ -102,12 +102,12 @@ jobs: run: working-directory: PaperlessUI.Angular steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Activate pnpm via corepack run: | corepack enable - corepack prepare pnpm@10.30.2 --activate - - uses: actions/setup-node@v4 + corepack prepare pnpm@11.1.2 --activate + - uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm @@ -123,12 +123,12 @@ jobs: run: working-directory: PaperlessUI.React steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Activate pnpm via corepack run: | corepack enable - corepack prepare pnpm@10.30.2 --activate - - uses: actions/setup-node@v4 + corepack prepare pnpm@11.1.2 --activate + - uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm diff --git a/Directory.Packages.props b/Directory.Packages.props index 783c3bd..04d5265 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,19 +25,20 @@ - + - - + - - + + + + @@ -79,8 +80,8 @@ pulled by Nuke.Common) and GHSA-37gx-xxp4-5rgx / GHSA-w3x6-4m5h-cxqf (System.Security.Cryptography.Xml 9.0.0). Effective because the SDK turns on CentralPackageTransitivePinningEnabled. --> - - + + diff --git a/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs b/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs index 4748265..f3ddb71 100644 --- a/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs +++ b/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs @@ -20,12 +20,12 @@ static SharedRestContainerFixture() public SharedRestContainerFixture() { - string postgresImage = Environment.GetEnvironmentVariable("POSTGRES_IMAGE") ?? "postgres:16-alpine"; - string rabbitImage = Environment.GetEnvironmentVariable("RABBITMQ_IMAGE") ?? "rabbitmq:3.13-management-alpine"; + string postgresImage = Environment.GetEnvironmentVariable("POSTGRES_IMAGE") ?? "postgres:17-alpine"; + string rabbitImage = Environment.GetEnvironmentVariable("RABBITMQ_IMAGE") ?? "rabbitmq:4.3.0-management"; string minioImage = Environment.GetEnvironmentVariable("MINIO_IMAGE") ?? - "minio/minio:RELEASE.2025-07-23T15-54-02Z"; + "minio/minio:RELEASE.2025-09-07T16-13-09Z"; string elasticImage = Environment.GetEnvironmentVariable("ELASTIC_IMAGE") ?? - "docker.elastic.co/elasticsearch/elasticsearch:9.1.3"; + "docker.elastic.co/elasticsearch/elasticsearch:9.4.1"; _postgres = new PostgreSqlBuilder() .WithImage(postgresImage) diff --git a/PaperlessServices.Tests/Integration/WorkerTestBase.cs b/PaperlessServices.Tests/Integration/WorkerTestBase.cs index 4de2ea7..9dc5799 100644 --- a/PaperlessServices.Tests/Integration/WorkerTestBase.cs +++ b/PaperlessServices.Tests/Integration/WorkerTestBase.cs @@ -22,9 +22,9 @@ public class SharedContainerFixture : IAsyncLifetime private const int MinioPort = 9000; // Default image versions - override via environment variables for CI flexibility - private const string DefaultElasticsearchImage = "docker.elastic.co/elasticsearch/elasticsearch:9.1.3"; - private const string DefaultMinioImage = "minio/minio:RELEASE.2024-11-07T00-52-20Z"; - private const string DefaultRabbitmqImage = "rabbitmq:4.0-management-alpine"; + private const string DefaultElasticsearchImage = "docker.elastic.co/elasticsearch/elasticsearch:9.4.1"; + private const string DefaultMinioImage = "minio/minio:RELEASE.2025-09-07T16-13-09Z"; + private const string DefaultRabbitmqImage = "rabbitmq:4.3.0-management"; private readonly string _bucketName = $"test-{Guid.NewGuid():N}"; diff --git a/PaperlessUI.Angular/package.json b/PaperlessUI.Angular/package.json index f48ab23..b857bcf 100644 --- a/PaperlessUI.Angular/package.json +++ b/PaperlessUI.Angular/package.json @@ -13,7 +13,7 @@ "generate": "pnpm run generate:openapi && pnpm run generate:types" }, "private": true, - "packageManager": "pnpm@10.30.2", + "packageManager": "pnpm@11.1.2", "dependencies": { "@angular/common": "^21.2.0", "@angular/compiler": "^21.2.0", diff --git a/Version.props b/Version.props index 436ef9c..8902cfd 100644 --- a/Version.props +++ b/Version.props @@ -2,15 +2,18 @@ - 10.0.0 - 10.0.0 - 10.0.7 - 10.0.0 - 10.0.0 + 10.0.8 + 10.0.8 + 10.0.8 + 10.0.8 + + 10.6.0 + 10.0.2 + 10.0.1 - 8.1.0 - 8.1.1 + 10.0.0 + 10.0.0 9.0.0 @@ -20,32 +23,33 @@ 9.0.8 0.7.1 4.20.72 - 3.2.1 - 18.1.0 - 2.0.2 + 3.2.2 + 18.6.2 + 2.2.3 - 10.0.0 - 5.0.1 + 10.2.0 + 6.3.0 - 4.9.0 + 4.11.0 - 3.0.3 - 3.1.1 - 9.2.2 - 2.0.1 + 3.0.4 + 3.2.0 + 9.4.0 + 2.1.1 2.0.1 1.8.23 1.21.1 1.8.1.2 2025.2.4 + 1.0.1 3.1.0 7.0.0 13.0.4 - 2.11.0 + 2.14.14 2.3.1 diff --git a/global.json b/global.json index cdf0aab..e2e2c2c 100644 --- a/global.json +++ b/global.json @@ -4,9 +4,9 @@ "rollForward": "latestFeature" }, "msbuild-sdks": { - "ANcpLua.NET.Sdk": "3.4.29", - "ANcpLua.NET.Sdk.Web": "3.4.29", - "ANcpLua.NET.Sdk.Test": "3.4.29" + "ANcpLua.NET.Sdk": "3.4.32", + "ANcpLua.NET.Sdk.Web": "3.4.32", + "ANcpLua.NET.Sdk.Test": "3.4.32" }, "test": { "runner": "Microsoft.Testing.Platform" From 07594d814dcb93d763db4c1030e5ae0a2767aaba Mon Sep 17 00:00:00 2001 From: ancplua Date: Sat, 16 May 2026 05:01:13 +0200 Subject: [PATCH 3/5] fix(deps): resolve fallout from version bumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI on a1cd535 surfaced four issues from the en-masse bump: 1. EF Core MSB3277 conflict — Hangfire.PostgreSql 1.21.1 pulls EF Core Relational 10.0.4, conflicting with our 10.0.8 pin. Added direct PackageVersion for Microsoft.EntityFrameworkCore.Relational so CentralPackageTransitivePinningEnabled force-unifies to 10.0.8. 2. Testcontainers 4.11.0 deprecated parameterless builder constructors (CS0618 errors on ElasticsearchBuilder(), MinioBuilder(), RabbitMqBuilder(), PostgreSqlBuilder()). Switched to the image-parameter constructor form per the migration note at https://github.com/testcontainers/testcontainers-dotnet/discussions/1470 in both WorkerTestBase.cs and SharedRestContainerFixture.cs. 3. Microsoft.Testing.Platform version skew (CS1705) — TrxReport 2.2.3 needs Platform 2.2.3 but xunit.v3.mtp-v2 3.2.2 only ships Platform 2.1.0. Pinned Microsoft.Testing.Platform to match TrxReport via transitive pinning. 4. pnpm 11 strict mode errors on ignored build scripts (ERR_PNPM_IGNORED_BUILDS). Whitelisted the four Angular native build deps (@parcel/watcher, esbuild, lmdb, msgpackr-extract) via pnpm.onlyBuiltDependencies in PaperlessUI.Angular/package.json. Co-Authored-By: Claude Opus 4.7 (1M context) --- Directory.Packages.props | 4 ++++ .../Integration/SharedRestContainerFixture.cs | 12 ++++-------- .../Integration/WorkerTestBase.cs | 12 ++++++------ PaperlessUI.Angular/package.json | 8 ++++++++ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 04d5265..1c52b77 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,6 +24,8 @@ + + @@ -44,6 +46,8 @@ + + diff --git a/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs b/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs index f3ddb71..f3066b5 100644 --- a/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs +++ b/PaperlessREST.Tests/Integration/SharedRestContainerFixture.cs @@ -27,22 +27,18 @@ public SharedRestContainerFixture() string elasticImage = Environment.GetEnvironmentVariable("ELASTIC_IMAGE") ?? "docker.elastic.co/elasticsearch/elasticsearch:9.4.1"; - _postgres = new PostgreSqlBuilder() - .WithImage(postgresImage) + _postgres = new PostgreSqlBuilder(postgresImage) .WithWaitStrategy(Wait.ForUnixContainer() .UntilMessageIsLogged("database system is ready to accept connections")) .Build(); - _rabbit = new RabbitMqBuilder() - .WithImage(rabbitImage) + _rabbit = new RabbitMqBuilder(rabbitImage) .Build(); - _minio = new MinioBuilder() - .WithImage(minioImage) + _minio = new MinioBuilder(minioImage) .Build(); - _elastic = new ElasticsearchBuilder() - .WithImage(elasticImage) + _elastic = new ElasticsearchBuilder(elasticImage) .WithEnvironment("discovery.type", "single-node") .WithEnvironment("xpack.security.enabled", "false") .WithEnvironment("ES_JAVA_OPTS", "-Xms512m -Xmx512m") diff --git a/PaperlessServices.Tests/Integration/WorkerTestBase.cs b/PaperlessServices.Tests/Integration/WorkerTestBase.cs index 9dc5799..2efc966 100644 --- a/PaperlessServices.Tests/Integration/WorkerTestBase.cs +++ b/PaperlessServices.Tests/Integration/WorkerTestBase.cs @@ -28,8 +28,8 @@ public class SharedContainerFixture : IAsyncLifetime private readonly string _bucketName = $"test-{Guid.NewGuid():N}"; - private readonly ElasticsearchContainer _elastic = new ElasticsearchBuilder() - .WithImage(Environment.GetEnvironmentVariable("ELASTIC_IMAGE") ?? DefaultElasticsearchImage) + private readonly ElasticsearchContainer _elastic = new ElasticsearchBuilder( + Environment.GetEnvironmentVariable("ELASTIC_IMAGE") ?? DefaultElasticsearchImage) .WithEnvironment("discovery.type", "single-node") .WithEnvironment("xpack.security.enabled", "false") .WithEnvironment("ES_JAVA_OPTS", "-Xms512m -Xmx512m") @@ -37,12 +37,12 @@ public class SharedContainerFixture : IAsyncLifetime private readonly string _indexName = $"test_{Guid.NewGuid():N}"; - private readonly MinioContainer _minio = new MinioBuilder() - .WithImage(Environment.GetEnvironmentVariable("MINIO_IMAGE") ?? DefaultMinioImage) + private readonly MinioContainer _minio = new MinioBuilder( + Environment.GetEnvironmentVariable("MINIO_IMAGE") ?? DefaultMinioImage) .Build(); - private readonly RabbitMqContainer _rabbit = new RabbitMqBuilder() - .WithImage(Environment.GetEnvironmentVariable("RABBITMQ_IMAGE") ?? DefaultRabbitmqImage) + private readonly RabbitMqContainer _rabbit = new RabbitMqBuilder( + Environment.GetEnvironmentVariable("RABBITMQ_IMAGE") ?? DefaultRabbitmqImage) .Build(); private IHost _host = null!; diff --git a/PaperlessUI.Angular/package.json b/PaperlessUI.Angular/package.json index b857bcf..ba76b50 100644 --- a/PaperlessUI.Angular/package.json +++ b/PaperlessUI.Angular/package.json @@ -14,6 +14,14 @@ }, "private": true, "packageManager": "pnpm@11.1.2", + "pnpm": { + "onlyBuiltDependencies": [ + "@parcel/watcher", + "esbuild", + "lmdb", + "msgpackr-extract" + ] + }, "dependencies": { "@angular/common": "^21.2.0", "@angular/compiler": "^21.2.0", From 0bc241c1aa3edcbd71ff7d730d9dbc2afe8ab9d9 Mon Sep 17 00:00:00 2001 From: ancplua Date: Sat, 16 May 2026 05:03:38 +0200 Subject: [PATCH 4/5] revert(deps): hold TrxReport at 2.0.2 and pnpm at 10.30.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bumps were incompatible with the SDK / ecosystem state and got reverted: - Microsoft.Testing.Extensions.TrxReport 2.2.3 → 2.0.2. TrxReport 2.2.3 needs Microsoft.Testing.Platform 2.2.3, but the .NET 10 SDK (10.0.300) implicitly references Platform 2.1.0, and NU1009 blocks any attempt to add a PackageVersion override for an implicitly-referenced package under Central Package Management. Held until xunit.v3.mtp-v2 ships with Platform 2.2.3+ or the SDK band moves. - pnpm 10.30.2 → 11.1.2 reverted; the pnpm.onlyBuiltDependencies whitelist I added did not satisfy pnpm 11's now-fatal ERR_PNPM_IGNORED_BUILDS for @parcel/watcher / esbuild / lmdb / msgpackr-extract. Held until the Angular toolchain is reapproved end-to-end (post-install approve-builds flow + lockfile regen) on a quiet branch. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 4 ++-- Directory.Packages.props | 2 -- PaperlessUI.Angular/package.json | 10 +--------- Version.props | 3 ++- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bee55d6..32ee2d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,7 +106,7 @@ jobs: - name: Activate pnpm via corepack run: | corepack enable - corepack prepare pnpm@11.1.2 --activate + corepack prepare pnpm@10.30.2 --activate - uses: actions/setup-node@v6 with: node-version: 22 @@ -127,7 +127,7 @@ jobs: - name: Activate pnpm via corepack run: | corepack enable - corepack prepare pnpm@11.1.2 --activate + corepack prepare pnpm@10.30.2 --activate - uses: actions/setup-node@v6 with: node-version: 22 diff --git a/Directory.Packages.props b/Directory.Packages.props index 1c52b77..f29e850 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,8 +46,6 @@ - - diff --git a/PaperlessUI.Angular/package.json b/PaperlessUI.Angular/package.json index ba76b50..f48ab23 100644 --- a/PaperlessUI.Angular/package.json +++ b/PaperlessUI.Angular/package.json @@ -13,15 +13,7 @@ "generate": "pnpm run generate:openapi && pnpm run generate:types" }, "private": true, - "packageManager": "pnpm@11.1.2", - "pnpm": { - "onlyBuiltDependencies": [ - "@parcel/watcher", - "esbuild", - "lmdb", - "msgpackr-extract" - ] - }, + "packageManager": "pnpm@10.30.2", "dependencies": { "@angular/common": "^21.2.0", "@angular/compiler": "^21.2.0", diff --git a/Version.props b/Version.props index 8902cfd..7780573 100644 --- a/Version.props +++ b/Version.props @@ -25,7 +25,8 @@ 4.20.72 3.2.2 18.6.2 - 2.2.3 + + 2.0.2 10.2.0 From ed9f3b5b15819d96fa2cb82d5addcc990ffdd4cd Mon Sep 17 00:00:00 2001 From: ancplua Date: Sat, 16 May 2026 05:05:59 +0200 Subject: [PATCH 5/5] fix(tests): port DatabaseFixture to Testcontainers 4.11 image-ctor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed in the previous Testcontainers migration pass — DatabaseFixture still used the deprecated PostgreSqlBuilder() parameterless ctor (CS0618). Also sync the postgres default tag with .env.test (17-alpine instead of the stale 16-alpine). Co-Authored-By: Claude Opus 4.7 (1M context) --- PaperlessREST.Tests/Integration/DatabaseFixture.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PaperlessREST.Tests/Integration/DatabaseFixture.cs b/PaperlessREST.Tests/Integration/DatabaseFixture.cs index f1b69dd..eb022cf 100644 --- a/PaperlessREST.Tests/Integration/DatabaseFixture.cs +++ b/PaperlessREST.Tests/Integration/DatabaseFixture.cs @@ -21,10 +21,9 @@ static DatabaseFixture() public DatabaseFixture() { - string postgresImage = Environment.GetEnvironmentVariable("POSTGRES_IMAGE") ?? "postgres:16-alpine"; + string postgresImage = Environment.GetEnvironmentVariable("POSTGRES_IMAGE") ?? "postgres:17-alpine"; - _container = new PostgreSqlBuilder() - .WithImage(postgresImage) + _container = new PostgreSqlBuilder(postgresImage) .WithWaitStrategy(Wait.ForUnixContainer() .UntilMessageIsLogged("database system is ready to accept connections")) .Build();