use std::path::PathBuf;
use crate::host::Source;
use crate::stamp::{Marker, source_from_marker};
fn marker(source_mode: &str, source_path: Option<&str>) -> Marker {
Marker {
binary_version: "0.1.0".into(),
plugin_version: "0.1.0".into(),
source_mode: source_mode.into(),
scope: "user".into(),
agent: "claude".into(),
project_path: None,
source_path: source_path.map(String::from),
statusline_original: None,
tree_hash: None,
reinstalling: false,
}
}
#[test]
fn marker_without_source_path_still_deserializes() {
let json = r#"{
"binary_version": "0.1.0",
"plugin_version": "0.1.0",
"source_mode": "embedded",
"scope": "user",
"agent": "claude"
}"#;
let marker: Marker = serde_json::from_str(json).expect("an old marker without source_path must still load");
assert_eq!(marker.source_path, None);
}
#[test]
fn marker_with_source_path_round_trips() {
let json = r#"{
"binary_version": "0.1.0",
"plugin_version": "0.1.0",
"source_mode": "path",
"scope": "user",
"agent": "claude",
"source_path": "/tmp/some/plugin"
}"#;
let marker: Marker = serde_json::from_str(json).expect("marker must parse");
assert_eq!(marker.source_path.as_deref(), Some("/tmp/some/plugin"));
let bytes = serde_json::to_vec(&marker).expect("marker must serialize");
let round_tripped: Marker = serde_json::from_slice(&bytes).expect("round-tripped marker must parse");
assert_eq!(round_tripped.source_path.as_deref(), Some("/tmp/some/plugin"));
}
#[test]
fn marker_without_tree_hash_still_deserializes_and_writes_no_key() {
let json = r#"{
"binary_version": "0.1.0",
"plugin_version": "0.1.0",
"source_mode": "embedded",
"scope": "user",
"agent": "claude"
}"#;
let marker: Marker = serde_json::from_str(json).expect("an old marker without tree_hash must still load");
assert_eq!(marker.tree_hash, None);
let rendered = serde_json::to_string(&marker).expect("marker must serialize");
assert!(!rendered.contains("tree_hash"), "an absent record must not emit the key: {rendered}");
}
#[test]
fn marker_without_reinstalling_loads_as_settled_and_writes_no_key() {
let json = r#"{
"binary_version": "0.1.0",
"plugin_version": "0.1.0",
"source_mode": "embedded",
"scope": "user",
"agent": "claude"
}"#;
let marker: Marker = serde_json::from_str(json).expect("an old marker without reinstalling must still load");
assert!(!marker.reinstalling);
let rendered = serde_json::to_string(&marker).expect("marker must serialize");
assert!(!rendered.contains("reinstalling"), "a settled marker must not emit the key: {rendered}");
let mut in_flight = marker.clone();
in_flight.reinstalling = true;
let rendered = serde_json::to_string(&in_flight).expect("marker must serialize");
assert!(rendered.contains("\"reinstalling\":true"), "an in-flight reinstall must survive to disk: {rendered}");
}
#[test]
fn marker_without_statusline_original_still_deserializes() {
let json = r#"{
"binary_version": "0.1.0",
"plugin_version": "0.1.0",
"source_mode": "embedded",
"scope": "user",
"agent": "claude"
}"#;
let marker: Marker = serde_json::from_str(json).expect("an old marker without statusline_original must still load");
assert_eq!(marker.statusline_original, None);
}
#[test]
fn a_legacy_statusline_original_round_trips_verbatim() {
let raw = serde_json::json!({"type": "command", "command": "their-bar --wide", "padding": 2, "theirKey": ["a", 1]});
let mut m = marker("embedded", None);
m.statusline_original = Some(raw.clone());
let bytes = serde_json::to_vec(&m).expect("marker must serialize");
let back: Marker = serde_json::from_slice(&bytes).expect("round-tripped marker must parse");
assert_eq!(back.statusline_original, Some(raw));
}
#[test]
fn an_absent_stash_writes_no_key() {
let rendered = serde_json::to_string(&marker("embedded", None)).expect("marker must serialize");
assert!(!rendered.contains("statusline_original"), "an empty stash must not emit the key: {rendered}");
}
#[test]
fn a_marker_written_by_a_pre_retirement_binary_still_deserializes() {
let json = r#"{
"binary_version": "0.1.0",
"plugin_version": "0.1.0",
"source_mode": "embedded",
"scope": "user",
"agent": "claude",
"statusline_original": {"type": "command", "command": "their-bar"},
"statusline_command": "mytool statusline --client claude"
}"#;
let marker: Marker = serde_json::from_str(json).expect("a pre-retirement marker must still load");
assert!(marker.statusline_original.is_some(), "the stash beside the record must still load");
}
#[test]
fn source_from_marker_prefers_a_path_mode_marker() {
let m = marker("path", Some("/a/plugin"));
assert_eq!(source_from_marker(Some(&m), Source::Embedded), Source::Path(PathBuf::from("/a/plugin")));
}
#[test]
fn source_from_marker_falls_back_to_default_when_marker_is_absent() {
assert_eq!(source_from_marker(None, Source::Embedded), Source::Embedded);
}
#[test]
fn source_from_marker_rehydrates_an_embedded_marker() {
let m = marker("embedded", None);
assert_eq!(source_from_marker(Some(&m), Source::GitHub { repo: "o/r", ref_: "v1" }), Source::Embedded);
}
#[test]
fn source_from_marker_falls_back_to_default_for_a_github_marker() {
let m = marker("github", None);
assert_eq!(source_from_marker(Some(&m), Source::GitHub { repo: "o/r", ref_: "v1" }), Source::GitHub { repo: "o/r", ref_: "v1" });
}
#[test]
fn source_from_marker_falls_back_when_path_mode_but_no_persisted_path() {
let m = marker("path", None);
assert_eq!(source_from_marker(Some(&m), Source::Embedded), Source::Embedded);
}
#[test]
fn source_from_marker_is_per_marker_not_broadcast() {
let path_marker = marker("path", Some("/a/plugin"));
let embedded_marker = marker("embedded", None);
assert_eq!(source_from_marker(Some(&path_marker), Source::Embedded), Source::Path(PathBuf::from("/a/plugin")));
assert_eq!(source_from_marker(Some(&embedded_marker), Source::Embedded), Source::Embedded);
}