# TestClock.Advance

> In a test, moves the run's clock FORWARD by a duration, then lets the next Workflow.Settle fire every workflow timer the jump made due — reminders and instance/state deadlines.

<!-- id: testing-clock-advance · area: testing · stability: stable · html: https://osysharp.com/reference/testing/clock-advance/ -->

## Summary        {#summary}
`TestClock.Advance(delta)` moves the test's ambient clock forward by a `TimeSpan`, and it moves **every clock the app
reads**. An app reads time in three places, and a test that has said what time it is is believed by all three:

| where the time is read | does `Advance` move it? |
|---|---|
| the server, in memory — a function body | **yes** |
| the server, **inside a query** — `Item.Where(i => DateTime.UtcNow < i.Expires)` | **yes** — the platform's instant is bound into the SQL |
| the **browser** — a client expression or a `live var` computed over a list the client holds | **yes** |

So a row expiring in an hour is expired after `Advance(2h)` — in a function body, through a `Where(…)`, and on the
page — because they are three renderings of one question and a test may only get one answer to it.

⚑ **This matters most at a BOUNDARY, which is the only place it can bite.** "Is it due yet" is wrong for rows either
side of one instant and right for everything else, so a comparison off by an hour looks perfect until the hour that
decides it. Write the two tests that pin both sides of that instant — compute the distance to the boundary and step
one minute short of it, then one minute past — rather than one that advances a day and checks. Whether the second
crosses midnight otherwise depends on the hour the suite happens to run.

⚠ **In PRODUCTION a query's clock is still the database's `now()`.** Nothing here changes a deployed request, which
has no pinned clock; the binding happens only when a test has pinned one.

⚠ It is **TEST-ONLY, enforced by the compiler**: app code that moved its own clock would have no deadlines left, and
a milestone that breaches because the code said so is not an SLA. Workflow SLA clocks (reminders, the
per-state expire, the whole-instance deadline) are wall-time, so advancing the clock past a timer's due moment and
then calling <span class="planned" title="this page is planned and not written yet">Workflow.Settle</span> fires that timer deterministically — no real waiting.

## Signature      {#signature}
```osy syntax
TestClock.Advance(<TimeSpan>);   // e.g. TestClock.Advance(TimeSpan.FromHours(2))
```

## Description    {#description}
Time in a workflow SLA advances as wall-clock while a run waits. In a test that is not real time — you drive it.
`TestClock.Advance` adds `delta` to the run's clock (starting from the current instant, or now if the clock was never
pinned). The move alone changes nothing; the following `Workflow.Settle(entity)` runs the timer sweep, which fires
every clock now due:

- a **reminder** whose `After` (first fire) or `ThenEvery` (repeat) delay has elapsed runs its `Remind <Name>(…) { }` body and
  records a `Reminded` row on the [audit timeline](https://osysharp.com/reference/workflow/audit/) — a reminder never transitions the run;
- the instance **`Deadline`** or a state **`Expire`** whose budget has elapsed fires its `on Deadline` / `on Expire`
  route (which may `goto` a new state).

One `Settle` fires each due timer once; a recurring reminder fires once per `Settle`, matching how a periodic worker
sweeps. Because a reminder has no state change to observe, assert on the audit timeline (see [For(entity).Audit](https://osysharp.com/reference/workflow/audit/)).

The delta must be a `TimeSpan` — durations are always `TimeSpan.From…`, never a bare literal.

## Examples       {#examples}

⭐ **The commonest use, and the one this verb exists for: a state DERIVED FROM ELAPSED TIME.** Watering, renewals,
trials, overdue — anything where "has enough time passed?" is the app's whole question. Without `Advance` a test can
only back-date the seed, which never exercises the transition the user actually cares about.

```osy title="a lighthouse lamp falls due once its service interval passes" test app=testing-clock-due
entity Lamp {
  [MaxLength(80)] string Tower; DateTime LastServiced; int EveryDays = 7;
  security { allow read, create, update, delete when IsAnonymous || IsAuthenticated; }
}

[Page("/")]
[AllowAnonymous]
[Render(CSR)]
component Home() {
  live var lamps = Lamp.ToList();
  live var due = lamps.Where(l => (DateTime.UtcNow - l.LastServiced).Days >= l.EveryDays).ToList();
  render { Stack(gap: 2) { Text("Due now"); foreach (var l in due) { Text(l.Tower); } } }
}

[Test]
void a_lamp_falls_due_after_its_interval() {
  var l = new Lamp { Tower = "Skerryvore", LastServiced = DateTime.UtcNow, EveryDays = 7 };
  UnitOfWork.Commit();

  Ui.Visit("/");
  Assert.Hidden("Skerryvore");                 // just serviced — not due

  TestClock.Advance(TimeSpan.FromDays(8));     // the jump the keeper waits a week for
  Assert.Visible("Skerryvore");                // …and the CLIENT's live var sees it
}
```

⚑ That `due` is a **client** `live var` over rows the browser already holds — the third row of the table above —
and `Advance` moves it. This example is compiled and run by the docs gate, so it cannot go stale.

The workflow case, where the jump makes a TIMER due and the next `Settle` fires it:

```osy title="the workflow half — a jump makes a timer due, and the next Settle fires it" syntax
// A FRAGMENT, deliberately: it needs a workflow, its slot and its SLA clocks to stand around it. The compiled
// version of exactly this — advance, settle, assert on the timeline — is on [Remind (milestone reminders)](https://osysharp.com/reference/workflow/remind/).
var po = new PurchaseOrder { Title = "Laptops", Total = 20000 };
Workflow.Settle(po);                     // autostart → arms the slot's SLA clocks
PoApproval.RaiseSubmit(po);

TestClock.Advance(TimeSpan.FromHours(2));     // past the slot's Remind Nudge(After = 1h)
Workflow.Settle(po);                      // sweep fires the reminder

var audit = PoApproval.For(po).Audit;     // assert on the timeline, not on a state change
Assert.Equal(1, audit.Where(a => a.Kind == AuditKind.Reminded && a.Slot == "Legal").Count());
Assert.Equal(PoStatus.Review, po.Status); // a reminder does NOT transition — still waiting
```

## See also       {#see-also}
- [For(entity).Audit](https://osysharp.com/reference/workflow/audit/) — the timeline a fired reminder writes to
- [[Test] / [TestFixture]](https://osysharp.com/reference/testing/test/) — the `[Test]` a clock-driven assertion lives in
