# TimeProvider lab

Companion to [Test Time-Dependent Code Without Waiting](https://milanjovanovic.tech/blog/test-time-dependent-code-without-waiting).
The [lab page](https://milanjovanovic.tech/labs/timeprovider) includes file previews and individual downloads.

## Run the tests

Install the .NET 10 SDK, download and extract `timeprovider.zip`, then open a terminal in the `timeprovider` folder inside it.
If you are using the repository, run from `samples/mnw_215` instead.
The first run restores the packages pinned in `mnw_215.csproj`:

```sh
dotnet test --logger "console;verbosity=detailed"
```

All five tests should pass. Compare the messages with `output.txt`; paths, test order, and elapsed times can differ.
The APIs in the article are available from .NET 8, but this project targets .NET 10.

## What to look for

- An invitation is accepted one tick before expiry, rejected exactly at expiry, and rejected one tick after it.
- A provider-backed five-minute delay completes after advancing fake time by five minutes.
- A system-clock delay remains pending after the same advance. The test cancels it explicitly to clean up.
- A provider-backed one-minute timeout cancels the pending work at its deadline.
- A system-clock timeout remains pending after advancing the fake clock.

Every test owns a fresh `FakeTimeProvider`.
The five-second `WaitAsync` calls use real time to fail a broken test instead of leaving it waiting indefinitely.
They do not control the invitation expiry or the operation's timeout.

## Change one thing at a time

1. In `InvitationPolicy.cs`, change `< expiresAt` to `<= expiresAt` and run the tests. The assertion exactly at expiry fails with `Expected: False, Actual: True`. Restore `<`.
2. In `Advance_completes_a_provider_backed_delay`, remove `clock` from `Task.Delay(TimeSpan.FromMinutes(5), clock)`. The delay now uses real time and the test fails with a `TimeoutException` after its five-second watchdog. Restore the argument.
3. In `Provider_backed_timeout_cancels_work_at_its_deadline`, remove `clock` from the `CancellationTokenSource` constructor. Advancing fake time no longer triggers the timeout, so the test fails through the watchdog. Restore the provider, then remove `timeout.Token` from the work's `Task.Delay` instead. Cancellation no longer reaches the work, and the same test fails. Restore the token.

After restoring each edit, all five tests should pass again.
To run just the timeout test:

```sh
dotnet test --filter FullyQualifiedName~Provider_backed_timeout_cancels_work_at_its_deadline
```

## What the fake clock does not control

Create each timer before calling `Advance`.
For an asynchronous retry loop, wait until the next delay has been scheduled before advancing again.
Direct reads of `DateTime.UtcNow`, ordinary delays, and clocks in other processes are unaffected.
Cancellation is cooperative; an HTTP client or database driver still has to observe its token.

References: [TimeProvider overview](https://learn.microsoft.com/en-us/dotnet/standard/datetime/timeprovider-overview), [FakeTimeProvider testing](https://learn.microsoft.com/en-us/dotnet/core/extensions/timeprovider-testing), [CancellationTokenSource constructors](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.-ctor?view=net-10.0).
