kittynode_core/application/
get_operational_state.rs

1use crate::application::is_docker_running;
2use crate::domain::operational_state::{OperationalMode, OperationalState};
3use crate::infra::config::ConfigStore;
4use eyre::Result;
5
6/// Returns the operational state describing runtime capabilities for the current mode.
7pub 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}