use assert_cmd::Command;
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 record_start_then_bare_returns_active() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
oxiline_cmd(&db)
.args(["activity", "add", "코딩", "--json"])
.assert()
.success();
let out = oxiline_cmd(&db)
.args(["record", "start", "코딩", "--json"])
.output()
.unwrap();
assert!(
out.status.success(),
"record start must succeed; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let state: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
let active_name = state
.get("active")
.and_then(|a| a.get("activity"))
.and_then(|a| a.get("name"))
.and_then(|v| v.as_str());
assert_eq!(
active_name,
Some("코딩"),
"expected active activity '코딩', got: {}",
state
);
let bare_out = oxiline_cmd(&db)
.args(["record", "--json"])
.output()
.unwrap();
assert!(
bare_out.status.success(),
"record (bare) must succeed; stderr: {}",
String::from_utf8_lossy(&bare_out.stderr)
);
let bare: serde_json::Value = serde_json::from_slice(&bare_out.stdout).unwrap();
assert_eq!(
bare.get("active")
.and_then(|a| a.get("activity"))
.and_then(|a| a.get("name"))
.and_then(|v| v.as_str()),
Some("코딩")
);
let _keep_alive: TempDir = tmp;
}
#[test]
fn record_at_backdates_switch() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
oxiline_cmd(&db)
.args(["activity", "add", "코딩", "--json"])
.assert()
.success();
oxiline_cmd(&db)
.args(["activity", "add", "독서", "--json"])
.assert()
.success();
let first_at = "2026-08-01T09:00:00Z";
let switch_at = "2026-08-01T09:01:30Z";
oxiline_cmd(&db)
.args(["record", "start", "코딩", "--at", first_at, "--json"])
.assert()
.success();
let out = oxiline_cmd(&db)
.args(["record", "start", "독서", "--at", switch_at, "--json"])
.output()
.unwrap();
assert!(
out.status.success(),
"record start --at must succeed; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let log_out = oxiline_cmd(&db)
.args([
"record",
"log",
"--range",
"2026-08-01:2026-08-01",
"--json",
])
.output()
.unwrap();
assert!(log_out.status.success());
let records: serde_json::Value = serde_json::from_slice(&log_out.stdout).unwrap();
let arr = records.as_array().expect("record log must be a JSON array");
assert_eq!(arr.len(), 2, "expected 2 records, got: {}", records);
let first = &arr[0];
assert_eq!(
first.get("ended_at").and_then(|v| v.as_str()),
Some(switch_at),
"first record's ended_at must match --at; got: {}",
first
);
let second = &arr[1];
assert_eq!(
second.get("started_at").and_then(|v| v.as_str()),
Some(switch_at),
"second record's started_at must match --at; got: {}",
second
);
let _keep_alive: TempDir = tmp;
}
#[test]
fn record_log_with_date_range() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
oxiline_cmd(&db)
.args(["activity", "add", "코딩", "--json"])
.assert()
.success();
oxiline_cmd(&db)
.args(["record", "start", "코딩", "--json"])
.assert()
.success();
let today = chrono::Local::now().format("%Y-%m-%d").to_string();
let out = oxiline_cmd(&db)
.args(["record", "log", "--date", &today, "--json"])
.output()
.unwrap();
assert!(
out.status.success(),
"record log --date must succeed; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let records: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
let arr = records.as_array().expect("record log must be a JSON array");
assert_eq!(
arr.len(),
1,
"expected exactly the record we just started, got: {}",
records
);
assert!(
arr[0].get("activity_id").and_then(|v| v.as_str()).is_some(),
"activity_id must be present and non-null"
);
let _keep_alive: TempDir = tmp;
}
#[test]
fn record_stop_closes_open() {
let tmp = tempdir().unwrap();
let db = tmp.path().join("test.db");
oxiline_cmd(&db)
.args(["activity", "add", "코딩", "--json"])
.assert()
.success();
oxiline_cmd(&db)
.args(["record", "start", "코딩", "--json"])
.assert()
.success();
let out = oxiline_cmd(&db)
.args(["record", "stop", "--json"])
.output()
.unwrap();
assert!(
out.status.success(),
"record stop must succeed; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let bare_out = oxiline_cmd(&db)
.args(["record", "--json"])
.output()
.unwrap();
assert!(bare_out.status.success());
let bare: serde_json::Value = serde_json::from_slice(&bare_out.stdout).unwrap();
assert!(
bare.get("active").is_none() || bare.get("active") == Some(&serde_json::Value::Null),
"after `record stop`, active must be null; got: {}",
bare
);
let _keep_alive: TempDir = tmp;
}