use std::fs;
use std::path::PathBuf;
use syntax_workout_core::{ExecutionMode, NodePayload, Workout, validate, StreamMetric, StreamData};
fn workspace_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
fn load_and_validate(path: &str) -> Workout {
let full_path = workspace_root().join(path);
let content = fs::read_to_string(&full_path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", full_path.display()));
let workout: Workout = serde_json::from_str(&content)
.unwrap_or_else(|e| panic!("failed to parse {}: {e}", full_path.display()));
let errors = validate(&workout.root);
assert!(
errors.is_empty(),
"validation errors in {path}: {errors:?}"
);
let json = serde_json::to_string_pretty(&workout).unwrap();
let back: Workout = serde_json::from_str(&json)
.unwrap_or_else(|e| panic!("round-trip failed for {path}: {e}"));
assert_eq!(workout, back, "round-trip mismatch for {path}");
workout
}
#[test]
fn straight_sets() {
let w = load_and_validate("examples/strength/straight-sets.json");
assert_eq!(w.sport, Some("strength".into()));
assert_eq!(w.root.name, Some("Push Day A".into()));
}
#[test]
fn supersets() {
let w = load_and_validate("examples/strength/supersets.json");
let block = &w.root.children[0];
if let NodePayload::Block { execution_mode, .. } = &block.payload {
assert_eq!(*execution_mode, ExecutionMode::Parallel);
} else {
panic!("expected Block payload");
}
}
#[test]
fn circuit() {
let w = load_and_validate("examples/strength/circuit.json");
let block = &w.root.children[0];
if let NodePayload::Block { execution_mode, .. } = &block.payload {
assert_eq!(*execution_mode, ExecutionMode::Circuit { rounds: 3 });
} else {
panic!("expected Block payload");
}
}
#[test]
fn bodyweight() {
let w = load_and_validate("examples/strength/bodyweight.json");
assert_eq!(w.root.name, Some("Bodyweight Session".into()));
}
#[test]
fn dropsets() {
let w = load_and_validate("examples/strength/dropsets.json");
let exercise = &w.root.children[0].children[0];
assert_eq!(exercise.children.len(), 3);
}
#[test]
fn easy_run() {
let w = load_and_validate("examples/endurance/easy-run.json");
assert_eq!(w.sport, Some("running".into()));
}
#[test]
fn interval_run() {
let w = load_and_validate("examples/endurance/interval-run.json");
let block = &w.root.children[0];
if let NodePayload::Block { execution_mode, .. } = &block.payload {
assert_eq!(*execution_mode, ExecutionMode::Circuit { rounds: 4 });
} else {
panic!("expected Block payload");
}
}
#[test]
fn cycling_ride() {
let w = load_and_validate("examples/endurance/cycling-ride.json");
assert_eq!(w.sport, Some("cycling".into()));
}
#[test]
fn warmup_then_strength() {
let w = load_and_validate("examples/mixed/warmup-then-strength.json");
assert_eq!(w.root.children.len(), 2, "expected 2 blocks (warmup + superset)");
}
#[test]
fn ppl_week() {
let w = load_and_validate("examples/mixed/ppl-week.json");
let week = &w.root.children[0];
assert_eq!(week.children.len(), 3, "expected 3 days in the week");
}
#[test]
fn easy_run_with_streams() {
let w = load_and_validate("examples/endurance/easy-run-with-streams.json");
assert_eq!(w.sport, Some("running".into()));
assert_eq!(w.version, "1.1.0");
let streams = w.streams.as_ref().expect("expected streams");
assert_eq!(streams.timestamps.len(), 6);
assert_eq!(streams.channels.len(), 4);
streams.validate().expect("streams should be valid");
assert!(matches!(streams.channels[0].metric, StreamMetric::HeartRate));
assert!(matches!(streams.channels[0].data, StreamData::Scalar(_)));
assert!(matches!(streams.channels[3].metric, StreamMetric::Position));
assert!(matches!(streams.channels[3].data, StreamData::Position(_)));
assert_eq!(w.laps.len(), 2);
assert_eq!(w.laps[0].name, Some("km 1".into()));
assert_eq!(w.laps[0].start_index, Some(0));
assert_eq!(w.laps[1].name, Some("km 2".into()));
}