ForgeTrust.AppSurface.Durable.Testing 0.2.0-preview.13

This is a prerelease version of ForgeTrust.AppSurface.Durable.Testing.
dotnet add package ForgeTrust.AppSurface.Durable.Testing --version 0.2.0-preview.13
                    
NuGet\Install-Package ForgeTrust.AppSurface.Durable.Testing -Version 0.2.0-preview.13
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ForgeTrust.AppSurface.Durable.Testing" Version="0.2.0-preview.13" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ForgeTrust.AppSurface.Durable.Testing" Version="0.2.0-preview.13" />
                    
Directory.Packages.props
<PackageReference Include="ForgeTrust.AppSurface.Durable.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ForgeTrust.AppSurface.Durable.Testing --version 0.2.0-preview.13
                    
#r "nuget: ForgeTrust.AppSurface.Durable.Testing, 0.2.0-preview.13"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ForgeTrust.AppSurface.Durable.Testing@0.2.0-preview.13
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ForgeTrust.AppSurface.Durable.Testing&version=0.2.0-preview.13&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ForgeTrust.AppSurface.Durable.Testing&version=0.2.0-preview.13&prerelease
                    
Install as a Cake Tool

ForgeTrust.AppSurface.Durable.Testing

ForgeTrust.AppSurface.Durable.Testing is a .NET 10 test-support package for consumers of ForgeTrust.AppSurface.Durable and ForgeTrust.AppSurface.Durable.Provider. It provides deterministic, production-backed fixtures for health, pump admission, drain control, and typed Work contracts. Its builders call the production constructors and preserve their validation; it does not emulate a database or provider policy.

Use this package in module and host tests where the question is about request construction, health interpretation, call ordering, or how a host handles provider admission outcomes. Use the PostgreSQL conformance suite when the claim concerns persisted state, leases, crash recovery, transactions, or an ambiguous external effect. The operational-assessment guide defines the production meanings of health and admission.

Quick start

The package has no test-framework or assertion-library dependency. Start with an xUnit project (or use an existing test project), add Testing, and save the following test as DurableHostTests.cs:

dotnet new xunit -f net10.0 -n DurableHost.Tests
cd DurableHost.Tests
dotnet package add ForgeTrust.AppSurface.Durable.Testing

Keep ForgeTrust.AppSurface.Durable.Testing, ForgeTrust.AppSurface.Durable, and ForgeTrust.AppSurface.Durable.Provider on the same coordinated preview version. When pinning previews, use the identical version on each direct package reference.

using ForgeTrust.AppSurface.Durable.Provider;
using ForgeTrust.AppSurface.Durable.Testing;
using Xunit;

public sealed class DurableHostTests
{
    [Fact]
    public async Task Host_observes_health_then_uses_authoritative_admission()
    {
        var health = new FakeDurableRuntimeHealth(
            new DurableHealthSnapshotBuilder().ForState(DurableRuntimeHealthState.Healthy).Build());
        var pump = new RecordingDurableRuntimePump();
        var scenario = new DurableHostScenario(
            health,
            pump,
            new DurableRuntimePumpRequestBuilder().Build());

        var assessment = await scenario.AssessHealthAsync();
        var attempt = await scenario.RunDirectPumpOnceAsync();

        Assert.True(assessment.Snapshot.IsReady);
        Assert.Equal(DurableRuntimePumpAttemptKind.Completed, attempt.Kind);
        Assert.Single(pump.History);
        Assert.Same(assessment, scenario.PumpInvocations[0].Assessment);
    }
}

Run dotnet test from the test-project directory. For a repository-local package feed, use the packed consumer verifier as the working example of restore, graph inspection, and four-kind admission assertions against a freshly packed artifact.

The default recording pump returns Completed with an empty result. That means this invocation completed and reported zero counts; it does not prove that a real store has no eligible work. Configure RecordingDurableRuntimePump.Admission to return any production DurableRuntimePumpAttempt, including Refused, Unavailable, or Incompatible, when testing host handling of those outcomes. Assert the exact Kind; never translate refusal or provider unavailability into an empty successful pass.

Reference: builders and fakes

API Default and behavior Use it for
DurableHealthSnapshotBuilder Starts at Healthy; named state supplies representative defaults. Field overrides are applied before the production snapshot constructor validates them. Health consumers and boundary validation.
BuildContradictoryForTest() Explicitly opts into otherwise rejected inconsistent state/field combinations for defensive consumer tests. Testing behavior when upstream evidence is contradictory.
DurableRuntimePumpRequestBuilder 32 maximum items, all surfaces, production request defaults for time budget. Bounded pump requests.
DurableRuntimePumpResultBuilder Zero counts, no more-work flag, zero elapsed time, no next due time. Exact result assertions.
DurableRuntimePumpAttemptBuilder Completed with a zero-count result. Production attempt constructor validates kind/result/problem-code combinations. Exhaustive four-kind outcome handling.
DurableWorkRequestBuilder<TWork,TResult> Requires a definition and an explicitly supplied payload; default test scope/command values and idempotency key test-key. Calls the definition's CreateRequest. Typed request parity and production codec validation.
DurableWorkerEnvelopeBuilder<TPayload> Uses native envelope creation and requires correlation and execution/fence identity. Worker projection assertions that preserve native identity.
FakeDurableRuntimeHealth Returns the configured snapshot unchanged; observes cancellation before returning. Deterministic health reads.
RecordingDurableRuntimePump Implements admission-aware and bounded pump interfaces. Captures invocation order, exact request reference, selected surfaces, limits, and completion. Defaults to an empty completed pass. Call ordering, cancellation, overlap, and outcome handling.
FakeDurableRuntimeDrainControl Applies begin/resume requests and retains immutable, ordered transition history. Drain-control consumer behavior.

Use builder overrides for a single field at a time where possible. Production constructors remain the validation authority, so malformed identifiers, bounds, surfaces, counts, codec metadata, or identity still fail during Build(). The contradictory path is intentionally conspicuous; ordinary Build() does not silently create impossible fixtures. DurableRuntimePumpAttemptBuilder.WithResult preserves an explicit result, including null, regardless of whether WithKind is called before or after it. Without an explicit result, Completed supplies an empty pass and other kinds supply no result; Build() passes contradictory combinations to the production constructor for validation.

Six health-state defaults

The builder's named states create valid representative snapshots. The four predicate columns are computed by the production DurableRuntimeHealthSnapshot, not by the builder. Observed means WasStoreObserved.

State Schema / epoch defaults Started / heartbeat defaults Draining Observed Can enable activation Can attempt pump Ready
Healthy compatible / compatible present / present no yes yes yes yes
NotStarted compatible / compatible absent / absent no yes yes yes no
Stale compatible / compatible present / present (representative timestamps) no yes yes yes no
Draining compatible / compatible present / present yes yes yes no no
Incompatible incompatible / incompatible present / present no yes no no no
Unavailable incompatible / incompatible absent / absent; active epoch absent no no no no no

Stale represents the state, not an elapsed-time calculation performed by this test builder. Override timestamps or compatibility fields to exercise a particular consumer case; the production constructor validates the resulting combination. An unavailable assessment means compatibility was not observed, not that incompatibility was proven.

Reference: host scenario and deadlines

DurableHostScenario takes explicit health and admission interfaces, one bounded request, and optional TimeProvider, observation timeout, and overall timeout. Defaults are TimeProvider.System, 30 seconds per observation, and two minutes overall. Budgets use monotonic timestamps. The overall budget begins with the first health assessment, is shared by later assessments and pump calls, and does not reset; construct a new scenario for a new budget. Custom providers must supply monotonic timestamps.

Call AssessHealthAsync before RunDirectPumpOnceAsync. Missing assessment throws InvalidOperationException. A successful assessment is published when its method reaches the terminal decision under the scenario lock, even if it started before another successful read. Provider task completion alone does not publish it. A later pump atomically captures the latest published assessment. Failed, canceled, or timed-out reads do not replace it. A published assessment may be reused for multiple pump calls. The snapshot is advisory: every in-budget pump call invokes authoritative admission exactly once even when CanAttemptPump is false. The exact provider attempt is returned unchanged.

DurableScenarioTimeoutException names the Phase (Health or Pump) and Reason (Observation or Overall). A timeout before admission has no invocation. If the pump call has started, Invocation carries its sequence, exact request, captured assessment, and repeat-awaitable Completion task. InvocationStarted and ExecutionStatusUnknown are true in that case. The timeout ends only the scenario's wait; it does not cancel provider work or convert it to refusal, unavailability, or an empty pass. Await Invocation.Completion to observe the eventual attempt, cancellation, or original exception. A fault after a permit may still require provider recovery to establish whether an external effect occurred. Only the caller's cancellation token is passed to provider methods.

The terminal decision gives an already-completed result priority, then observed caller cancellation, then overall deadline, then observation deadline. A caller cancellation observed before completion/timeout propagates as OperationCanceledException; provider exceptions propagate without being synthesized into attempts. Never silently retry a timed-out admission call.

Problems, causes, and remedies

API signal Likely cause Next step
DurableHealthSnapshotBuilder.Build() throws ArgumentException The selected named health state conflicts with an override, such as Healthy without a heartbeat. Use a matching state or field combination; reserve BuildContradictoryForTest() for a defensive test that deliberately supplies inconsistent evidence.
A request, result, typed Work, or native-envelope builder throws during Build() A production contract rejected an identifier, bound, surface, payload, codec, or fence identity. Correct the fixture input; use the typed Work guide for definition and codec requirements.
RunDirectPumpOnceAsync() throws InvalidOperationException before admission No successful AssessHealthAsync() call has published an assessment. Assess health first. A failed, canceled, or timed-out assessment does not replace a prior successful one.
DurableScenarioTimeoutException has no Invocation The health or pump observation budget expired before admission started. Inspect Phase and Reason; create a new scenario if its shared overall budget has expired.
DurableScenarioTimeoutException has an Invocation The local wait expired after pump admission started, so execution status is still unknown. Await Invocation.Completion and use the provider's diagnostics and recovery guidance before deciding whether a new attempt is safe.
Admission returns Refused, Unavailable, or Incompatible The provider declined admission; a health snapshot is advisory and cannot override that decision. Handle the exact attempt kind. The operational assessment troubleshooting matrix maps problem codes to cause and repair.

The Testing package preserves provider exceptions and problem codes. Consult the Durable diagnostics catalog for code-specific explanations; do not classify a failure or retry from exception text.

Contract observations

DurableWorkDefinitionObservation.Capture snapshots definition identity, codec metadata, classifications, retention policy IDs, provider-safety declaration, and default retry policy. DurableWorkRegistryObservation.Capture performs the exact production registry lookup; missing-registration errors propagate. DurableWorkBindingObservation.Capture records the exact definition reference and identity without resolving services or running work. These are observation records, not assertion helpers: use your test framework to make the assertions appropriate to your consumer.

History, retention, and privacy

Pump History and scenario PumpInvocations return atomic immutable snapshots ordered by invocation start. Entries retain exact request and result references, so their metadata/order is frozen but payload-bearing references are shallow. Do not mutate payload objects while asserting captured requests. The pump retains history for the fake instance's lifetime until ClearHistory(); clearing advances a generation so an older in-flight call cannot repopulate the cleared history. The scenario retains invocation handles for its lifetime; ClearCompletedPumpInvocations() only removes terminal handles, leaving in-flight work reachable. There is no automatic truncation or disposal that hides in-flight calls. Clear completed history when retaining a long-lived fixture, and await timed-out handles before releasing provider/test resources.

Durable payload values are never formatted or serialized by these helpers. Keep assertions and test output focused on safe scalar facts such as state, attempt kind, sequence, count, and stable problem code. A test framework may display objects passed to a failing assertion, so avoid asserting whole payload-bearing records when that could disclose sensitive fixture data.

Package coordination and proof boundary

This package references only Durable and Durable.Provider as product packages. It intentionally adds no PostgreSQL, ASP.NET Core, Testcontainers, test framework, or assertion library. Install it only in test projects. Keep the three Durable package versions aligned during preview upgrades and review their coordinated notes in the release hub. Package installation does not establish database behavior: retain real PostgreSQL tests for acceptance, claim, completion, stale-worker recovery, and ambiguous post-permit outcomes. See the Durable package chooser for package selection and the typed Work migration guide for authoring contracts.

Release Guidance

AppSurface ships as a coordinated package family. Before installing this package from a prerelease feed, check the package chooser and release hub for current release risk, migration guidance, and readiness.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0-preview.13 56 10/4/2026
0.2.0-preview.11 189 9/30/2026