Osy#betaRun it on Osyrin: osyrin.com

Reference / Testing

Workflow.Settle and Background.Settle — let due work run, in a test

Workflow.Settle(<entity>); · Background.Settle();

Test-only. Workflow.Settle(row) runs that row's workflow until it has nothing left to do now: due timers, reminders, an expired state or deadline. Background.Settle() does the same for everything due in the app, including schedule occurrences, which belong to no row you could name. Neither moves the clock; TestClock.Advance does.

stable1 example compiled by CItestingworkflowtime

Summary#

In production, work that falls due later — a state's Expire, a Deadline, a reminder, a schedule's next occurrence — is run by the platform's dispatcher on its own cadence. A test does not wait for it. Workflow.Settle(row) runs that row's workflow until nothing is due now; Background.Settle() does it for the whole app. Move the clock first with TestClock.Advance when the work is due later than now.

Signature#

Workflow.Settle(<row>);     // that row's run: due timers fire, routes run, until nothing is due now
Background.Settle();        // everything due in the app, including schedule occurrences

Description#

When do I need it?#

Only for work that waits for a clock. A row a commit creates has already entered its workflow when UnitOfWork.Commit() returns — an autostarted enter needs no settle (Tracks and Initial (the field a workflow drives)). What a settle adds is the sweep: every timer due at the test's current time fires once, its route runs, and the run moves on.

Which one?#

you wantwrite
a named row's timer, reminder, Expire or Deadline to fireWorkflow.Settle(row);
a schedule's occurrence — the row it makes does not exist yetBackground.Settle();
several rows' runs at onceBackground.Settle(); — one sweep, not one settle per row

Workflow.Settle reads only its row's run: it is cheap, and it cannot fire something that belongs to another row.

What does it not do?#

  • It does not move the clock. Work due tomorrow stays due tomorrow; TestClock.Advance(TimeSpan.FromDays(1)) first, then settle.
  • One settle fires a recurring reminder once, the way a periodic sweep does — settle again to see the next.
  • It is test-only. App code has no clock to drain; the dispatcher does it.
using Osysharp.Workflow;

app Holds {
  model "**/*.osy";
  use Osysharp.Workflow;
}

enum HoldState { Held, Lapsed }

entity Hold {
  [MaxLength(80)] string Title;
  HoldState Status;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

workflow HoldLife {
  Tracks    = Hold.Status;
  Autostart = true;
  Initial   = Held;

  state Held {
    Expire { Within = TimeSpan.FromDays(1); Accrues = false; }
    on Expire { goto Lapsed; }
  }

  terminal success Lapsed { }
}

[Test]
void A_hold_lapses_once_its_day_has_passed() {
  var h = new Hold { Title = "Blue kayak" };
  UnitOfWork.Commit();

  Workflow.Settle(h);                            // nothing is due yet
  Assert.Equal(HoldState.Held, Hold.Single(x => x.Id == h.Id).Status);

  TestClock.Advance(TimeSpan.FromHours(25));
  Workflow.Settle(h);                            // now the Expire is due, and its route runs
  Assert.Equal(HoldState.Lapsed, Hold.Single(x => x.Id == h.Id).Status);
}

See also#

Related

TestClock.Advance and TestClock.Set

In a test, moves the run's clock FORWARD by a duration, then lets the next Workflow.Settle fire every workflow timer…

Remind (milestone reminders)

A reminder scheduled off a milestone. Its SCHEDULE is config in the header parens — `After` is the first fire (once, at…

Tracks and Initial (the field a workflow drives)

Names the enum field a workflow owns and the state a run starts in. No application code may write that field, and when…

Schedule (recurring work)

A schedule produces work on a cadence. Each occurrence creates a row of the entity its Template names — carrying…

Fixtures: built once, cloned for every test, and built on each other

A [TestFixture] is built once per run, and every test that names it runs on its own copy of what it left — rows, files…

[Test] / [TestFixture]

A test is an ordinary function marked [Test]. It runs against a throwaway clone of the app, so it may create rows and…