Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions Paperless.TestSupport/ContainerFixtureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Paperless.TestSupport;
/// Template-method base for the integration-test container fixtures. Owns the
/// shared container lifecycle (RabbitMQ + MinIO + Elasticsearch, plus an optional
/// Postgres), MinIO bucket creation, Elasticsearch readiness polling, the
/// Elasticsearch document/search polling helpers, and guarded teardown.
/// Elasticsearch document/search polling helpers, and ordered teardown.
/// <para>
/// Derived fixtures supply the system-under-test by overriding
/// <see cref="ConfigureSutAsync" /> (which must assign <see cref="Services" />)
Expand Down Expand Up @@ -73,17 +73,18 @@ public async ValueTask InitializeAsync()

public async ValueTask DisposeAsync()
{
// Guard SUT teardown and swallow per-container Dispose failures so that an
// exception thrown during InitializeAsync (e.g. a wait-strategy timeout) is
// not masked by a secondary NRE/dispose error during xUnit fixture cleanup.
try { await DisposeSutAsync(); } catch { /* best-effort */ }

try { await _rabbit.DisposeAsync(); } catch { /* best-effort */ }
try { await _minio.DisposeAsync(); } catch { /* best-effort */ }
try { await _elastic.DisposeAsync(); } catch { /* best-effort */ }
// SUT first (it consumes the containers), then the infra. Plain awaits, no
// best-effort catch: a teardown failure is a real fault that must surface.
// DisposeSutAsync overrides null-guard their own SUT, so a failed
// InitializeAsync cannot NRE here, and Testcontainers' Ryuk reaper cleans up
// any container a mid-teardown throw leaves running.
await DisposeSutAsync();
await _rabbit.DisposeAsync();
await _minio.DisposeAsync();
await _elastic.DisposeAsync();
if (_postgres is not null)
{
try { await _postgres.DisposeAsync(); } catch { /* best-effort */ }
await _postgres.DisposeAsync();
}
}

Expand Down
11 changes: 5 additions & 6 deletions PaperlessServices.Tests/Integration/WorkerTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,13 @@ protected override async ValueTask ConfigureSutAsync()

protected override async ValueTask DisposeSutAsync()
{
// _host is assigned in ConfigureSutAsync. If init throws before that line
// (e.g. a container wait-strategy times out), _host is still null; the base
// guards this call so a naive _host.StopAsync() NRE cannot mask the real
// InitializeAsync exception in xUnit's collection-fixture cleanup report.
// Null-guarded because a failed InitializeAsync (e.g. a container wait-strategy
// timeout) returns before _host is assigned. When the host exists, stop it
// gracefully then dispose it — no best-effort catch: a shutdown fault is real
// and must surface, not hide behind the init exception.
if (_host is not null)
{
try { await _host.StopAsync(); }
catch { /* best-effort: don't mask the InitializeAsync exception */ }
await _host.StopAsync();
_host.Dispose();
}
}
Expand Down