turnframe_test/lib.rs
1//! `turnframe-test` — the test kit for Turnframe.
2//!
3//! The promise that business effects stay deterministic while the conversation
4//! stays natural is worth the tests behind it, and those are hard to write: a
5//! plan whose spans must quote the user exactly, a workflow whose invariants
6//! must hold in *every* reachable state, a model double that must refuse to
7//! improvise, a receipt that may not claim more than the ledger says. This crate
8//! writes that machinery once.
9//!
10//! | Module | What it gives you |
11//! |---|---|
12//! | [`executors`] | the conformance suite for a [`WorkflowExecutor`](turnframe_core::flow::WorkflowExecutor) you wrote, sharpest about the batch that half-committed |
13//! | [`explore`] | bounded breadth-first exploration of a workflow's reachable states, with the shortest command path to anything that breaks |
14//! | [`strategies`] | `proptest` strategies over the core types, including understandings *grounded* in a text so every range is words of it |
15//! | [`workflows`] | three complete sample domains, a travel-disruption desk (trip, traveler, expense claim), each with a pure transition function, an in-memory executor and an exploration model |
16//! | [`providers`] | a scripted provider that fails loudly on an unscripted call, an [`UnderstandingBuilder`](providers::UnderstandingBuilder) that turns quotes into word ranges, a scripted understanding, and the provider conformance harness |
17//! | [`stores`] | the in-memory persistence layer with failure injection at named crash boundaries, call counting and a frozen clock |
18//! | [`replay`] | two executions compared artefact by artefact, and a replay record asked to account for its own turn |
19//! | [`assertions`] | the checks that keep coming up: no high-risk command without a trusted origin, no receipt without events, one phase per projection |
20//!
21//! Doubles for the runtime itself arrive with `turnframe-runtime`: this crate
22//! never depends on it, so a runtime test can use it without a cycle.
23//!
24//! # Example
25//!
26//! Explore the trip sample and assert that nothing in its reachable state
27//! space breaks a projection invariant.
28//!
29//! ```
30//! use turnframe_test::explore::{ExplorationLimits, explore};
31//! use turnframe_test::workflows::trip::{TripModel, TripOutcome, TripWorkflow};
32//!
33//! let report = explore(
34//! &TripWorkflow::default(),
35//! &TripModel::default(),
36//! ExplorationLimits::standard(),
37//! );
38//!
39//! assert!(report.is_clean(), "{}", report.describe());
40//! assert!(report.reached_outcome(&TripOutcome::Notified));
41//! ```
42
43#![forbid(unsafe_code)]
44#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
45
46/// The crate README, compiled as a doc-test so its examples cannot rot.
47#[cfg(doctest)]
48#[doc = include_str!("../README.md")]
49mod readme {}
50
51pub mod assertions;
52pub mod executors;
53pub mod explore;
54pub mod projection;
55pub mod providers;
56pub mod replay;
57pub mod stores;
58pub mod strategies;
59pub mod workflows;
60
61/// The items a test file usually wants: `use turnframe_test::prelude::*;`.
62///
63/// It re-exports [`turnframe_core::prelude`] as well, so a test rarely needs
64/// more than this one import.
65pub mod prelude {
66 pub use turnframe_core::prelude::*;
67
68 pub use crate::assertions::{
69 AssertionFailure, identical_blocks, no_high_risk_without_trusted_origin,
70 origin_satisfies_policy, receipts_backed_by_events, same_phase_in, single_phase,
71 };
72 pub use crate::executors::{
73 self, CHECK_COUNT as EXECUTOR_CHECK_COUNT, ExecutorFactory, InMemoryCase, SeededCase,
74 };
75 pub use crate::explore::{
76 ExplorationLimits, ExplorationReport, ExplorationViolation, ExplorationViolationKind,
77 SimulatedTransition, WorkflowModel, explore, reachable_states,
78 };
79 pub use crate::providers::{
80 RecordedCall, ScriptViolation, ScriptedProvider, ScriptedReply, ScriptedUnderstanding,
81 UnderstandingBuilder,
82 };
83 pub use crate::replay::{
84 ReplayDivergence, ReplayEvidence, ReplayGap, ReplayGaps, TurnExecution, same_turn,
85 };
86 pub use crate::stores::{FailurePoint, FakeStores, Stores, crash_boundary};
87 pub use crate::strategies;
88 pub use crate::workflows::claim::{
89 ClaimCommand, ClaimEvent, ClaimExecutor, ClaimField, ClaimModel, ClaimObligation,
90 ClaimOutcome, ClaimPhase, ClaimState, ClaimStatus, ClaimWorkflow, Proposal, ProposedField,
91 RecordedField,
92 };
93 pub use crate::workflows::traveler::{
94 DeclineReason, FieldState, TravelerCommand, TravelerEvent, TravelerExecutor, TravelerModel,
95 TravelerObligation, TravelerOutcome, TravelerPhase, TravelerState, TravelerStatus,
96 TravelerWorkflow,
97 };
98 pub use crate::workflows::trip::{
99 TripCommand, TripEvent, TripExecutor, TripModel, TripObligation, TripOutcome, TripPhase,
100 TripState, TripStatus, TripWorkflow,
101 };
102 pub use crate::workflows::{Applied, InMemoryExecutor, PureWorkflow, simulate};
103}