auths_sdk/workflows/
status.rs1use crate::result::{
4 AgentStatus, DeviceReadiness, DeviceStatus, IdentityStatus, NextStep, StatusReport,
5};
6use chrono::{DateTime, Duration, Utc};
7use std::path::Path;
8
9pub struct StatusWorkflow;
20
21impl StatusWorkflow {
22 pub fn query(repo_path: &Path, _now: DateTime<Utc>) -> Result<StatusReport, String> {
33 let _ = repo_path; let identity = None; let devices = Vec::new(); let agent = AgentStatus {
43 running: false,
44 pid: None,
45 socket_path: None,
46 };
47
48 let next_steps = Self::compute_next_steps(&identity, &devices, &agent);
50
51 Ok(StatusReport {
52 identity,
53 devices,
54 agent,
55 next_steps,
56 })
57 }
58
59 fn compute_next_steps(
61 identity: &Option<IdentityStatus>,
62 devices: &[DeviceStatus],
63 agent: &AgentStatus,
64 ) -> Vec<NextStep> {
65 let mut steps = Vec::new();
66
67 if identity.is_none() {
69 steps.push(NextStep {
70 summary: "Initialize your identity".to_string(),
71 command: "auths init --profile developer".to_string(),
72 });
73 return steps;
74 }
75
76 if devices.is_empty() {
78 steps.push(NextStep {
79 summary: "Link this device to your identity".to_string(),
80 command: "auths pair".to_string(),
81 });
82 }
83
84 let expiring_soon = devices
86 .iter()
87 .filter(|d| d.readiness == DeviceReadiness::ExpiringSoon)
88 .count();
89 if expiring_soon > 0 {
90 steps.push(NextStep {
91 summary: format!("{} device(s) expiring soon", expiring_soon),
92 command: "auths device extend".to_string(),
93 });
94 }
95
96 if !agent.running {
98 steps.push(NextStep {
99 summary: "Start the authentication agent for signing".to_string(),
100 command: "auths agent start".to_string(),
101 });
102 }
103
104 if steps.is_empty() {
106 steps.push(NextStep {
107 summary: "Explore advanced features".to_string(),
108 command: "auths --help-all".to_string(),
109 });
110 }
111
112 steps
113 }
114
115 pub fn compute_readiness(
117 expires_at: Option<DateTime<Utc>>,
118 revoked_at: Option<DateTime<Utc>>,
119 now: DateTime<Utc>,
120 ) -> DeviceReadiness {
121 if revoked_at.is_some() {
122 return DeviceReadiness::Revoked;
123 }
124
125 match expires_at {
126 Some(exp) if exp < now => DeviceReadiness::Expired,
127 Some(exp) if exp - now < Duration::days(7) => DeviceReadiness::ExpiringSoon,
128 Some(_) => DeviceReadiness::Ok,
129 None => DeviceReadiness::Ok, }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 #[allow(clippy::disallowed_methods)]
140 fn test_compute_readiness_revoked() {
141 let now = Utc::now();
142 let readiness =
143 StatusWorkflow::compute_readiness(None, Some(now - Duration::hours(1)), now);
144 assert_eq!(readiness, DeviceReadiness::Revoked);
145 }
146
147 #[test]
148 #[allow(clippy::disallowed_methods)]
149 fn test_compute_readiness_expired() {
150 let now = Utc::now();
151 let exp = now - Duration::days(1);
152 let readiness = StatusWorkflow::compute_readiness(Some(exp), None, now);
153 assert_eq!(readiness, DeviceReadiness::Expired);
154 }
155
156 #[test]
157 #[allow(clippy::disallowed_methods)]
158 fn test_compute_readiness_expiring_soon() {
159 let now = Utc::now();
160 let exp = now + Duration::days(3);
161 let readiness = StatusWorkflow::compute_readiness(Some(exp), None, now);
162 assert_eq!(readiness, DeviceReadiness::ExpiringSoon);
163 }
164
165 #[test]
166 #[allow(clippy::disallowed_methods)]
167 fn test_compute_readiness_ok() {
168 let now = Utc::now();
169 let exp = now + Duration::days(30);
170 let readiness = StatusWorkflow::compute_readiness(Some(exp), None, now);
171 assert_eq!(readiness, DeviceReadiness::Ok);
172 }
173
174 #[test]
175 fn test_next_steps_no_identity() {
176 let steps = StatusWorkflow::compute_next_steps(
177 &None,
178 &[],
179 &AgentStatus {
180 running: false,
181 pid: None,
182 socket_path: None,
183 },
184 );
185 assert!(!steps.is_empty());
186 assert!(steps[0].command.contains("init"));
187 }
188}