kittynode_core/application/
get_operational_state.rs1use crate::application::is_docker_running;
2use crate::domain::operational_state::{OperationalMode, OperationalState};
3use crate::infra::config::ConfigStore;
4use eyre::Result;
5
6pub async fn get_operational_state() -> Result<OperationalState> {
8 let config = ConfigStore::load()?;
9 let mode = determine_mode(&config.server_url);
10 let docker_running = match mode {
11 OperationalMode::Local => is_docker_running().await,
12 OperationalMode::Remote => true,
13 };
14
15 Ok(compose_operational_state(mode, docker_running))
16}
17
18fn determine_mode(server_url: &str) -> OperationalMode {
19 if server_url.trim().is_empty() {
20 OperationalMode::Local
21 } else {
22 OperationalMode::Remote
23 }
24}
25
26fn compose_operational_state(mode: OperationalMode, docker_running: bool) -> OperationalState {
27 match mode {
28 OperationalMode::Local => {
29 let mut diagnostics = Vec::new();
30 if !docker_running {
31 diagnostics.push("Docker is not running locally".to_string());
32 }
33
34 OperationalState {
35 mode,
36 docker_running,
37 can_install: docker_running,
38 can_manage: docker_running,
39 diagnostics,
40 }
41 }
42 OperationalMode::Remote => OperationalState {
43 mode,
44 docker_running: true,
45 can_install: true,
46 can_manage: true,
47 diagnostics: Vec::new(),
48 },
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn determine_mode_discriminates_on_trimmed_server_url() {
58 assert!(matches!(determine_mode(""), OperationalMode::Local));
59 assert!(matches!(determine_mode(" "), OperationalMode::Local));
60 assert!(matches!(
61 determine_mode("https://example.com"),
62 OperationalMode::Remote
63 ));
64 }
65
66 #[test]
67 fn compose_local_state_reflects_docker_availability() {
68 let running = compose_operational_state(OperationalMode::Local, true);
69 assert!(running.docker_running, "docker_running should passthrough");
70 assert!(
71 running.can_install,
72 "install should be allowed when running"
73 );
74 assert!(running.can_manage, "manage should be allowed when running");
75 assert!(
76 running.diagnostics.is_empty(),
77 "no diagnostics when running"
78 );
79
80 let stopped = compose_operational_state(OperationalMode::Local, false);
81 assert!(!stopped.docker_running, "docker_running should passthrough");
82 assert!(
83 !stopped.can_install,
84 "install should be blocked when stopped"
85 );
86 assert!(!stopped.can_manage, "manage should be blocked when stopped");
87 assert_eq!(
88 stopped.diagnostics,
89 vec!["Docker is not running locally".to_string()],
90 "diagnostics should explain the failure"
91 );
92 }
93
94 #[test]
95 fn compose_remote_state_always_enables_capabilities() {
96 let state = compose_operational_state(OperationalMode::Remote, false);
97 assert!(
98 state.docker_running,
99 "remote mode always reports docker running"
100 );
101 assert!(state.can_install, "remote mode can install");
102 assert!(state.can_manage, "remote mode can manage");
103 assert!(
104 state.diagnostics.is_empty(),
105 "remote mode should not emit diagnostics"
106 );
107 }
108}