difflore_cli/runtime/
context.rs1use std::path::PathBuf;
2
3use difflore_core::SqlitePool;
4use difflore_core::cloud::client::CloudClient;
5use tokio::sync::OnceCell;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum OutputMode {
9 Json,
10 Text,
11}
12
13impl OutputMode {
14 pub const fn from_json_flag(json: bool) -> Self {
15 if json { Self::Json } else { Self::Text }
16 }
17}
18
19pub struct CommandContext {
20 pub db: SqlitePool,
21 pub project: PathBuf,
22 pub mode: OutputMode,
23 cloud_cell: OnceCell<CloudClient>,
24}
25
26impl CommandContext {
27 pub async fn new(mode: OutputMode) -> Self {
28 let db = crate::commands::util::init_db().await;
29 let project = difflore_core::paths::current_project_root();
30 Self {
31 db,
32 project,
33 mode,
34 cloud_cell: OnceCell::new(),
35 }
36 }
37
38 pub async fn cloud(&self) -> &CloudClient {
42 self.cloud_cell
43 .get_or_init(|| async { CloudClient::create().await })
44 .await
45 }
46
47 pub fn json(&self) -> bool {
48 self.mode == OutputMode::Json
49 }
50}
51
52#[cfg(test)]
53#[allow(unsafe_code)] mod tests {
55 use super::*;
56 use std::sync::OnceLock;
57 use tempfile::TempDir;
58
59 fn ensure_test_home() {
63 static HOME: OnceLock<TempDir> = OnceLock::new();
64 HOME.get_or_init(|| {
65 let dir = TempDir::new().expect("create runtime test home tempdir");
66 unsafe {
69 std::env::set_var("DIFFLORE_HOME", dir.path());
70 }
71 dir
72 });
73 }
74
75 #[tokio::test]
76 async fn new_context_initialises_db_and_resolves_project_root() {
77 ensure_test_home();
78 let ctx = CommandContext::new(OutputMode::Text).await;
79 let row: (i64,) = sqlx::query_as("SELECT 1")
80 .fetch_one(&ctx.db)
81 .await
82 .expect("trivial select must succeed");
83 assert_eq!(row.0, 1);
84 let expected = difflore_core::paths::current_project_root();
85 assert_eq!(ctx.project, expected);
86 assert!(!ctx.json());
87 }
88
89 #[tokio::test]
90 async fn json_mode_reports_json() {
91 ensure_test_home();
92 let ctx = CommandContext::new(OutputMode::Json).await;
93 assert!(ctx.json());
94 }
95
96 #[tokio::test]
97 async fn cloud_is_lazy_and_memoised() {
98 ensure_test_home();
99 let ctx = CommandContext::new(OutputMode::Text).await;
100 let first = std::ptr::from_ref(ctx.cloud().await);
101 let second = std::ptr::from_ref(ctx.cloud().await);
102 assert_eq!(first, second, "second cloud() must hit the OnceCell");
103 }
104
105 #[test]
106 fn output_mode_from_json_flag() {
107 assert_eq!(OutputMode::from_json_flag(true), OutputMode::Json);
108 assert_eq!(OutputMode::from_json_flag(false), OutputMode::Text);
109 }
110}