#![forbid(unsafe_code)]
#[cfg(test)]
mod tests {
use ftui_runtime::program::{ProgramConfig, RolloutPolicy, RuntimeLane};
#[test]
fn runbook_step1_baseline_defaults() {
let config = ProgramConfig::default();
assert_eq!(
config.runtime_lane,
RuntimeLane::Structured,
"Default lane must be Structured (current migration state)"
);
assert_eq!(
config.rollout_policy,
RolloutPolicy::Off,
"Default rollout policy must be Off (no shadow comparison)"
);
}
#[test]
fn runbook_step2_enable_shadow_mode() {
let config = ProgramConfig::default().with_rollout_policy(RolloutPolicy::Shadow);
assert_eq!(config.rollout_policy, RolloutPolicy::Shadow);
assert!(config.rollout_policy.is_shadow());
assert_eq!(config.runtime_lane, RuntimeLane::Structured);
}
#[test]
fn runbook_step3_evaluate_scorecard() {
use crate::rollout_scorecard::{RolloutScorecard, RolloutScorecardConfig, RolloutVerdict};
use crate::shadow_run::{ShadowRun, ShadowRunConfig};
use ftui_core::event::Event;
use ftui_render::frame::Frame;
use ftui_runtime::program::{Cmd, Model};
use ftui_widgets::Widget;
use ftui_widgets::paragraph::Paragraph;
struct RunbookModel {
count: u32,
}
#[derive(Debug, Clone)]
enum Msg {
Tick,
}
impl From<Event> for Msg {
fn from(_: Event) -> Self {
Msg::Tick
}
}
impl Model for RunbookModel {
type Message = Msg;
fn update(&mut self, _msg: Msg) -> Cmd<Msg> {
self.count += 1;
Cmd::none()
}
fn view(&self, frame: &mut Frame) {
let text = format!("{}", self.count);
let area = ftui_core::geometry::Rect::new(0, 0, frame.width(), 1);
Paragraph::new(text).render(area, frame);
}
}
let shadow_config = ShadowRunConfig::new("runbook", "step3", 42).viewport(40, 10);
let result = ShadowRun::compare(
shadow_config,
|| RunbookModel { count: 0 },
|session: &mut crate::lab_integration::LabSession<RunbookModel>| {
session.init();
session.tick();
session.capture_frame();
},
);
let mut scorecard =
RolloutScorecard::new(RolloutScorecardConfig::default().min_shadow_scenarios(1));
scorecard.add_shadow_result(result);
let verdict = scorecard.evaluate();
assert_eq!(
verdict,
RolloutVerdict::Go,
"Identical models must produce Go verdict"
);
}
#[test]
fn runbook_step4_promote_to_enabled() {
let config = ProgramConfig::default()
.with_lane(RuntimeLane::Asupersync)
.with_rollout_policy(RolloutPolicy::Enabled);
assert_eq!(config.rollout_policy, RolloutPolicy::Enabled);
assert_eq!(config.runtime_lane, RuntimeLane::Asupersync);
let resolved = config.runtime_lane.resolve();
assert_eq!(
resolved,
RuntimeLane::Structured,
"Asupersync must fall back to Structured until fully implemented"
);
}
#[test]
fn runbook_step5_monitor_queue_telemetry() {
let snap = ftui_runtime::effect_system::queue_telemetry();
assert_eq!(
snap.in_flight,
snap.enqueued
.saturating_sub(snap.processed)
.saturating_sub(snap.dropped),
"in_flight must equal enqueued - processed - dropped"
);
}
#[test]
fn runbook_step6_rollback() {
let config = ProgramConfig::default()
.with_lane(RuntimeLane::Asupersync)
.with_rollout_policy(RolloutPolicy::Enabled);
let config = config
.with_lane(RuntimeLane::Structured)
.with_rollout_policy(RolloutPolicy::Off);
assert_eq!(config.runtime_lane, RuntimeLane::Structured);
assert_eq!(config.rollout_policy, RolloutPolicy::Off);
}
#[test]
fn runbook_step7_evidence_bundle() {
use crate::rollout_scorecard::{
RolloutEvidenceBundle, RolloutScorecard, RolloutScorecardConfig,
};
let scorecard =
RolloutScorecard::new(RolloutScorecardConfig::default().min_shadow_scenarios(0));
let summary = scorecard.summary();
let bundle = RolloutEvidenceBundle {
scorecard: summary,
queue_telemetry: Some(ftui_runtime::effect_system::queue_telemetry()),
requested_lane: RuntimeLane::Structured.label().to_string(),
resolved_lane: RuntimeLane::Structured.label().to_string(),
rollout_policy: RolloutPolicy::Off.label().to_string(),
};
let json = bundle.to_json();
assert!(json.starts_with('{'), "evidence must be valid JSON object");
assert!(json.ends_with('}'), "evidence must be valid JSON object");
assert!(
json.contains("\"schema_version\""),
"evidence must include schema version"
);
assert!(
json.contains("\"scorecard\""),
"evidence must include scorecard"
);
assert!(
json.contains("\"runtime\""),
"evidence must include runtime info"
);
}
#[test]
fn runbook_env_var_parsing() {
assert_eq!(RuntimeLane::parse("legacy"), Some(RuntimeLane::Legacy));
assert_eq!(
RuntimeLane::parse("structured"),
Some(RuntimeLane::Structured)
);
assert_eq!(
RuntimeLane::parse("asupersync"),
Some(RuntimeLane::Asupersync)
);
assert_eq!(RolloutPolicy::parse("off"), Some(RolloutPolicy::Off));
assert_eq!(RolloutPolicy::parse("shadow"), Some(RolloutPolicy::Shadow));
assert_eq!(
RolloutPolicy::parse("enabled"),
Some(RolloutPolicy::Enabled)
);
}
}