crates/flotilla-core/src/providers/discovery/detectors/codex.rs
Line | Count | Source |
1 | | //! Codex auth file host detector. |
2 | | //! |
3 | | //! Checks whether the Codex auth file (`auth.json`) exists under `$CODEX_HOME` |
4 | | //! (or `~/.codex` by default), indicating that the user has authenticated with |
5 | | //! the Codex CLI. |
6 | | |
7 | | use std::path::PathBuf; |
8 | | |
9 | | use async_trait::async_trait; |
10 | | |
11 | | use crate::providers::{ |
12 | | discovery::{EnvVars, EnvironmentAssertion, HostDetector}, |
13 | | CommandRunner, |
14 | | }; |
15 | | |
16 | | /// Returns the Codex home directory: `$CODEX_HOME` or `$HOME/.codex`. |
17 | | /// Returns `None` when neither env var is available. |
18 | 5 | fn codex_home(env: &dyn EnvVars) -> Option<PathBuf> { |
19 | 5 | if let Some(val2 ) = env.get("CODEX_HOME") { |
20 | 2 | Some(PathBuf::from(val)) |
21 | | } else { |
22 | 3 | env.get("HOME").map(|h| PathBuf::from(h)1 .join1 (".codex")) |
23 | | } |
24 | 5 | } |
25 | | |
26 | | /// Detects whether a Codex auth file exists. |
27 | | pub struct CodexAuthDetector; |
28 | | |
29 | | #[async_trait] |
30 | | impl HostDetector for CodexAuthDetector { |
31 | 5 | async fn detect(&self, runner: &dyn CommandRunner, env: &dyn EnvVars) -> Vec<EnvironmentAssertion> { |
32 | | let Some(home) = codex_home(env) else { |
33 | | return vec![]; |
34 | | }; |
35 | | let auth_path = home.join("auth.json"); |
36 | | // Check existence via runner (test -f) rather than local filesystem |
37 | | let Some(path_str) = auth_path.to_str() else { |
38 | | return vec![]; // non-UTF-8 path — can't pass to runner |
39 | | }; |
40 | | if runner.exists("test", &["-f", path_str]).await { |
41 | | vec![EnvironmentAssertion::auth_file("codex", auth_path)] |
42 | | } else { |
43 | | vec![] |
44 | | } |
45 | 5 | } |
46 | | } |
47 | | |
48 | | #[cfg(test)] |
49 | | mod tests { |
50 | | use std::path::Path; |
51 | | |
52 | | use super::*; |
53 | | use crate::providers::discovery::test_support::{DiscoveryMockRunner, TestEnvVars}; |
54 | | |
55 | | #[tokio::test] |
56 | 1 | async fn codex_auth_detector_found_via_codex_home() { |
57 | 1 | let runner = DiscoveryMockRunner::builder().tool_exists("test", true).build(); |
58 | 1 | let env = TestEnvVars::new([("CODEX_HOME", "/mock/codex")]); |
59 | 1 | let assertions = CodexAuthDetector.detect(&runner, &env).await; |
60 | | |
61 | 1 | assert_eq!(assertions.len(), 1); |
62 | 1 | match &assertions[0] { |
63 | 1 | EnvironmentAssertion::AuthFileExists { provider, path } => { |
64 | 1 | assert_eq!(provider, "codex"); |
65 | 1 | assert_eq!(path.as_path(), Path::new("/mock/codex/auth.json")); |
66 | 1 | } |
67 | 1 | other0 => panic!0 ("expected AuthFileExists, got {other:?}"), |
68 | 1 | } |
69 | 1 | } |
70 | | |
71 | | #[tokio::test] |
72 | 1 | async fn codex_auth_detector_found_via_home() { |
73 | 1 | let runner = DiscoveryMockRunner::builder().tool_exists("test", true).build(); |
74 | 1 | let env = TestEnvVars::new([("HOME", "/mock/home")]); |
75 | 1 | let assertions = CodexAuthDetector.detect(&runner, &env).await; |
76 | | |
77 | 1 | assert_eq!(assertions.len(), 1); |
78 | 1 | match &assertions[0] { |
79 | 1 | EnvironmentAssertion::AuthFileExists { provider, path } => { |
80 | 1 | assert_eq!(provider, "codex"); |
81 | 1 | assert_eq!(path.as_path(), Path::new("/mock/home/.codex/auth.json")); |
82 | 1 | } |
83 | 1 | other0 => panic!0 ("expected AuthFileExists, got {other:?}"), |
84 | 1 | } |
85 | 1 | } |
86 | | |
87 | | #[tokio::test] |
88 | 1 | async fn codex_auth_detector_not_found() { |
89 | | // test -f returns false |
90 | 1 | let runner = DiscoveryMockRunner::builder().tool_exists("test", false).build(); |
91 | 1 | let env = TestEnvVars::new([("CODEX_HOME", "/mock/codex")]); |
92 | 1 | let assertions = CodexAuthDetector.detect(&runner, &env).await; |
93 | | |
94 | 1 | assert!(assertions.is_empty()); |
95 | 1 | } |
96 | | |
97 | | #[tokio::test] |
98 | 1 | async fn codex_auth_detector_no_home() { |
99 | | // Neither CODEX_HOME nor HOME set |
100 | 1 | let runner = DiscoveryMockRunner::builder().build(); |
101 | 1 | let assertions = CodexAuthDetector.detect(&runner, &TestEnvVars::default()).await; |
102 | | |
103 | 1 | assert!(assertions.is_empty()); |
104 | 1 | } |
105 | | } |