use assert_cmd::Command;
use serde_json::Value;
use tempfile::{TempDir, tempdir};
const OXILINE_BIN: &str = env!("CARGO_BIN_EXE_oxiline");
fn oxiline_cmd(db_path: &std::path::Path) -> Command {
let mut c = Command::new(OXILINE_BIN);
c.env("OXILINE_DB_PATH", db_path)
.env("LC_ALL", "C")
.env("LANG", "C");
c
}
#[test]
fn plan_add_materializes_slot_with_options() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
for name in ["코딩", "독서"] {
oxiline_cmd(&db)
.args(["activity", "add", name, "--json"])
.assert()
.success();
}
let add = oxiline_cmd(&db)
.args([
"plan",
"add",
"--at",
"11:00",
"--duration",
"120",
"--days",
"mon",
"--options",
"코딩,독서",
"--json",
])
.output()
.unwrap();
assert!(
add.status.success(),
"plan add failed: {}",
String::from_utf8_lossy(&add.stderr)
);
let plan: Value = serde_json::from_slice(&add.stdout).unwrap();
let plan_id = plan["id"].as_str().unwrap().to_string();
assert_eq!(plan["start_minute"].as_u64(), Some(660));
assert_eq!(plan["duration_minute"].as_u64(), Some(120));
assert_eq!(plan["weekday_mask"].as_u64(), Some(1));
let list = oxiline_cmd(&db)
.args(["plan", "list", "--date", "2026-08-03", "--json"])
.output()
.unwrap();
assert!(list.status.success());
let slots = serde_json::from_slice::<Value>(&list.stdout)
.unwrap()
.as_array()
.cloned()
.unwrap();
let ours = slots
.iter()
.find(|s| s["plan_id"].as_str() == Some(&plan_id))
.expect("added plan must materialize for its weekday");
let opts = ours["options"].as_array().unwrap();
assert_eq!(opts.len(), 2, "slot must carry both OR options");
let _keep_alive: TempDir = tmp;
}
#[test]
fn plan_edit_preserves_time_duration_days() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
oxiline_cmd(&db)
.args(["activity", "add", "코딩", "--json"])
.assert()
.success();
let add = oxiline_cmd(&db)
.args([
"plan",
"add",
"--at",
"9:00",
"--duration",
"90",
"--days",
"wed",
"--options",
"코딩",
"--json",
])
.output()
.unwrap();
assert!(
add.status.success(),
"{}",
String::from_utf8_lossy(&add.stderr)
);
let plan: Value = serde_json::from_slice(&add.stdout).unwrap();
let id = plan["id"].as_str().unwrap().to_string();
assert_eq!(plan["weekday_mask"].as_u64(), Some(4));
let edit = oxiline_cmd(&db)
.args(["plan", "edit", &id, "--title", "새 제목", "--json"])
.output()
.unwrap();
assert!(
edit.status.success(),
"{}",
String::from_utf8_lossy(&edit.stderr)
);
let updated: Value = serde_json::from_slice(&edit.stdout).unwrap();
assert_eq!(
updated["start_minute"].as_u64(),
Some(540),
"start_minute must survive a title-only edit"
);
assert_eq!(
updated["duration_minute"].as_u64(),
Some(90),
"duration must survive a title-only edit"
);
assert_eq!(
updated["weekday_mask"].as_u64(),
Some(4),
"weekday_mask must survive a title-only edit"
);
assert_eq!(updated["title"].as_str(), Some("새 제목"));
let _keep_alive: TempDir = tmp;
}
#[test]
fn plan_rm_removes_plan() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
oxiline_cmd(&db)
.args(["activity", "add", "코딩", "--json"])
.assert()
.success();
let add = oxiline_cmd(&db)
.args([
"plan",
"add",
"--days",
"daily",
"--options",
"코딩",
"--json",
])
.output()
.unwrap();
assert!(add.status.success());
let plan: Value = serde_json::from_slice(&add.stdout).unwrap();
let id = plan["id"].as_str().unwrap().to_string();
oxiline_cmd(&db)
.args(["plan", "rm", &id, "--json"])
.assert()
.success();
let list = oxiline_cmd(&db)
.args(["plan", "list", "--json"])
.output()
.unwrap();
let plans = serde_json::from_slice::<Value>(&list.stdout)
.unwrap()
.as_array()
.cloned()
.unwrap();
assert!(plans.is_empty(), "plan list must be empty after rm");
let _keep_alive: TempDir = tmp;
}