#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use turnframe_test::projection::ProjectionFingerprint;
use turnframe_test::workflows::{claim, traveler, trip};
#[test]
fn the_trip_projector_emits_what_its_version_promised() {
let workflow = trip::TripWorkflow::default();
let fingerprint = ProjectionFingerprint::of(&workflow)
.at("absent", None)
.at("incomplete_case", Some(trip::incomplete_case()))
.at("complete_case", Some(trip::complete_case()))
.at(
"awaiting_rebooking_confirmation",
Some(trip::awaiting_rebooking_confirmation()),
)
.build();
insta::assert_yaml_snapshot!(fingerprint.snapshot_name(), fingerprint);
}
#[test]
fn the_traveler_projector_emits_what_its_version_promised() {
for workflow in [
traveler::TravelerWorkflow::default(),
traveler::TravelerWorkflow::new().with_cards(),
] {
pin_traveler(&workflow);
}
}
fn pin_traveler(workflow: &traveler::TravelerWorkflow) {
let fingerprint = ProjectionFingerprint::of(workflow)
.at("absent", None)
.at("incomplete_draft", Some(traveler::incomplete_draft()))
.at(
"declined_because_it_does_not_apply",
Some(traveler::declined_loyalty_number(
traveler::DeclineReason::NotApplicable,
)),
)
.at(
"declined_because_it_is_not_known",
Some(traveler::declined_loyalty_number(
traveler::DeclineReason::Unknown,
)),
)
.build();
insta::assert_yaml_snapshot!(fingerprint.snapshot_name(), fingerprint);
}
#[test]
fn the_claim_projector_emits_what_its_version_promised() {
let workflow = claim::ClaimWorkflow::default();
let fingerprint = ProjectionFingerprint::of(&workflow)
.at("absent", None)
.at("awaiting_extraction", Some(claim::awaiting_extraction()))
.at(
"under_review",
Some(claim::under_review(claim::complete_proposal())),
)
.at(
"under_review_incomplete",
Some(claim::under_review(claim::partial_proposal())),
)
.at("abandoned", Some(claim::abandoned()))
.build();
insta::assert_yaml_snapshot!(fingerprint.snapshot_name(), fingerprint);
}
mod the_pin_itself {
use super::ProjectionFingerprint;
use turnframe_core::case::CaseRef;
use turnframe_core::flow::{PhaseOwnership, WorkflowDefinition, WorkflowNotice, WorkflowView};
use turnframe_core::ids::{WorkflowKey, WorkflowVersion};
use turnframe_core::locale::LocalizedText;
use turnframe_core::response::NoticeSeverity;
use turnframe_test::workflows::{traveler, trip};
#[derive(Debug)]
struct Switchable {
extra_obligation: bool,
reworded: bool,
version: &'static str,
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
struct State;
#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Phase {
Collecting,
}
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
enum Obligation {
First,
Second,
}
#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Outcome {}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
enum Command {}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
enum Event {}
impl WorkflowDefinition for Switchable {
type State = State;
type Phase = Phase;
type Obligation = Obligation;
type Command = Command;
type Event = Event;
type Outcome = Outcome;
fn key(&self) -> WorkflowKey {
WorkflowKey::from("switchable")
}
fn version(&self) -> WorkflowVersion {
WorkflowVersion::from(self.version)
}
fn phase_ownership(&self, _phase: &Self::Phase) -> PhaseOwnership {
PhaseOwnership::System
}
fn project(
&self,
case_ref: CaseRef,
_state: Option<&Self::State>,
) -> WorkflowView<Self::Phase, Self::Obligation, Self::Outcome> {
let mut obligations = vec![Obligation::First];
if self.extra_obligation {
obligations.push(Obligation::Second);
}
let text = if self.reworded {
"Rephrased, meaning unchanged."
} else {
"Original wording."
};
WorkflowView {
case_ref,
workflow_version: self.version(),
phase: Phase::Collecting,
obligations,
blocking_interaction: None,
notices: vec![WorkflowNotice {
code: String::from("switchable.note"),
severity: NoticeSeverity::Info,
text: LocalizedText::new(text),
}],
outcome: None,
}
}
fn operations(
&self,
_view: &WorkflowView<Self::Phase, Self::Obligation, Self::Outcome>,
) -> Vec<turnframe_core::operation::OperationSpec> {
Vec::new()
}
fn compile_act(
&self,
_state: Option<&Self::State>,
_view: &WorkflowView<Self::Phase, Self::Obligation, Self::Outcome>,
_act: &turnframe_core::target::ResolvedAct,
) -> Result<Vec<Self::Command>, turnframe_core::error::DomainRejection> {
Ok(Vec::new())
}
fn command_policy(
&self,
_state: Option<&Self::State>,
command: &Self::Command,
) -> turnframe_core::command::CommandPolicy {
match *command {}
}
fn validate_command(
&self,
_state: Option<&Self::State>,
command: &Self::Command,
) -> Result<(), turnframe_core::error::DomainRejection> {
match *command {}
}
fn receipts(
&self,
_events: &[turnframe_core::event::ReceiptEvent<Self::Event>],
_locale: &turnframe_core::locale::Locale,
) -> Vec<turnframe_core::event::OperationalReceipt> {
Vec::new()
}
}
fn fingerprint(definition: &Switchable) -> ProjectionFingerprint {
ProjectionFingerprint::of(definition)
.at("only", Some(State))
.build()
}
#[test]
fn a_behaviour_change_under_a_fixed_version_moves_the_pin() {
let before = fingerprint(&Switchable {
extra_obligation: false,
reworded: false,
version: "1",
});
let after = fingerprint(&Switchable {
extra_obligation: true,
reworded: false,
version: "1",
});
assert_eq!(
before.snapshot_name(),
after.snapshot_name(),
"the version did not move, so both would be compared against one snapshot"
);
assert_ne!(
before, after,
"an obligation appeared: the recorded snapshot must fail"
);
}
#[test]
fn a_rewording_does_not_move_the_pin() {
let before = fingerprint(&Switchable {
extra_obligation: false,
reworded: false,
version: "1",
});
let after = fingerprint(&Switchable {
extra_obligation: false,
reworded: true,
version: "1",
});
assert_eq!(
before, after,
"copy is not contract: a pin that failed on wording would be silenced"
);
}
#[test]
fn bumping_the_version_writes_a_new_snapshot_rather_than_overwriting() {
let before = fingerprint(&Switchable {
extra_obligation: false,
reworded: false,
version: "1",
});
let after = fingerprint(&Switchable {
extra_obligation: true,
reworded: false,
version: "2",
});
assert_ne!(
before.snapshot_name(),
after.snapshot_name(),
"a bumped version names a different snapshot file, so the old shape stays on record"
);
}
#[test]
fn every_sample_projector_pins_under_its_own_name() {
let trip_workflow = trip::TripWorkflow::default();
let traveler_workflow = traveler::TravelerWorkflow::default();
let trip = ProjectionFingerprint::of(&trip_workflow)
.at("absent", None)
.build();
let traveler = ProjectionFingerprint::of(&traveler_workflow)
.at("absent", None)
.build();
assert_eq!(trip.snapshot_name(), "trip@1");
assert_eq!(traveler.snapshot_name(), "traveler@1");
assert_ne!(
trip.snapshot_name(),
traveler.snapshot_name(),
"two projectors must never share a snapshot"
);
}
}