1pub(crate) mod control;
4pub(crate) mod restart;
5pub(crate) mod run;
6pub(crate) mod start;
7pub(crate) mod status;
8pub(crate) mod stop;
9
10use anyhow::Result;
11use clap::{Parser, Subcommand};
12
13#[derive(Parser)]
16pub struct DaemonCommand {
17 #[command(subcommand)]
19 pub command: DaemonSubcommands,
20}
21
22#[derive(Subcommand)]
24pub enum DaemonSubcommands {
25 Run(run::RunCommand),
27 Start(start::StartCommand),
29 Stop(stop::StopCommand),
31 Restart(restart::RestartCommand),
33 Status(status::StatusCommand),
35}
36
37impl DaemonCommand {
38 pub async fn execute(self) -> Result<()> {
40 match self.command {
41 DaemonSubcommands::Run(cmd) => cmd.execute().await,
42 DaemonSubcommands::Start(cmd) => cmd.execute().await,
43 DaemonSubcommands::Stop(cmd) => cmd.execute().await,
44 DaemonSubcommands::Restart(cmd) => cmd.execute().await,
45 DaemonSubcommands::Status(cmd) => cmd.execute().await,
46 }
47 }
48}
49
50#[cfg(test)]
51#[allow(clippy::unwrap_used, clippy::expect_used)]
52mod tests {
53 use super::*;
54 use std::path::Path;
55
56 #[derive(Parser)]
58 struct Wrapper {
59 #[command(subcommand)]
60 cmd: DaemonSubcommands,
61 }
62
63 fn parse(args: &[&str]) -> DaemonSubcommands {
64 let mut full = vec!["omni-dev"];
65 full.extend_from_slice(args);
66 Wrapper::try_parse_from(full).unwrap().cmd
67 }
68
69 #[test]
70 fn parses_all_subcommands() {
71 assert!(matches!(parse(&["run"]), DaemonSubcommands::Run(_)));
72 assert!(matches!(parse(&["start"]), DaemonSubcommands::Start(_)));
73 assert!(matches!(parse(&["stop"]), DaemonSubcommands::Stop(_)));
74 assert!(matches!(parse(&["restart"]), DaemonSubcommands::Restart(_)));
75 assert!(matches!(parse(&["status"]), DaemonSubcommands::Status(_)));
76 }
77
78 #[test]
79 fn socket_override_parses() {
80 let DaemonSubcommands::Run(cmd) = parse(&["run", "--socket", "/tmp/x.sock"]) else {
81 panic!("expected run");
82 };
83 assert_eq!(cmd.socket.as_deref(), Some(Path::new("/tmp/x.sock")));
84 }
85
86 #[test]
87 fn status_json_flag_parses() {
88 let DaemonSubcommands::Status(cmd) = parse(&["status", "--json"]) else {
89 panic!("expected status");
90 };
91 assert!(cmd.json);
92 }
93
94 #[test]
95 fn socket_defaults_to_none() {
96 let DaemonSubcommands::Status(cmd) = parse(&["status"]) else {
97 panic!("expected status");
98 };
99 assert!(cmd.socket.is_none());
100 assert!(!cmd.json);
101 }
102
103 #[tokio::test]
106 async fn status_dispatch_reports_not_running() {
107 let dir = tempfile::tempdir().unwrap();
108 let socket = dir.path().join("absent.sock");
109 for json in [false, true] {
110 let cmd = DaemonCommand {
111 command: DaemonSubcommands::Status(status::StatusCommand {
112 socket: Some(socket.clone()),
113 json,
114 }),
115 };
116 cmd.execute().await.unwrap();
117 }
118 }
119}