resource_tracker/sentinel/
mod.rs1pub mod run;
8pub mod s3;
9pub mod upload;
10
11pub use run::{RunContext, close_run, start_run};
12pub use upload::{BatchUploader, samples_to_csv};
13
14use std::time::Duration;
15use ureq::config::Config as UreqConfig;
16
17const DEFAULT_API_BASE: &str = "https://api.sentinel.sparecores.net";
19
20const API_CONNECT_TIMEOUT_SECS: u64 = 10;
22const API_TIMEOUT_SECS: u64 = 30;
24
25const UPLOAD_CONNECT_TIMEOUT_SECS: u64 = 10;
27const UPLOAD_RECV_RESPONSE_TIMEOUT_SECS: u64 = 30;
28
29#[derive(Clone)]
38pub struct SentinelClient {
39 pub token: String,
40 pub api_base: String,
41 pub agent: ureq::Agent,
42}
43
44impl SentinelClient {
45 pub fn from_env() -> Option<Self> {
50 let token = std::env::var("SENTINEL_API_TOKEN").ok()?;
51 if token.is_empty() {
52 return None;
53 }
54 let api_base =
55 std::env::var("SENTINEL_API_URL").unwrap_or_else(|_| DEFAULT_API_BASE.to_string());
56
57 let agent = UreqConfig::builder()
58 .timeout_connect(Some(Duration::from_secs(API_CONNECT_TIMEOUT_SECS)))
59 .timeout_recv_response(Some(Duration::from_secs(API_TIMEOUT_SECS)))
60 .build()
61 .new_agent();
62
63 Some(Self {
64 token,
65 api_base,
66 agent,
67 })
68 }
69
70 pub fn new_upload_agent() -> ureq::Agent {
77 UreqConfig::builder()
78 .timeout_connect(Some(Duration::from_secs(UPLOAD_CONNECT_TIMEOUT_SECS)))
79 .timeout_recv_response(Some(Duration::from_secs(UPLOAD_RECV_RESPONSE_TIMEOUT_SECS)))
80 .build()
81 .new_agent()
82 }
83}
84
85#[cfg(test)]
90mod tests {
91 use super::*;
92 use std::sync::Mutex;
93
94 static ENV_TEST_LOCK: Mutex<()> = Mutex::new(());
96
97 #[test]
103 fn test_no_token_returns_none() {
104 let _lock = ENV_TEST_LOCK.lock().unwrap();
105 unsafe {
107 std::env::remove_var("SENTINEL_API_TOKEN");
108 }
109 assert!(
110 SentinelClient::from_env().is_none(),
111 "expected None when SENTINEL_API_TOKEN is unset"
112 );
113 }
114
115 #[test]
116 fn test_empty_token_returns_none() {
117 let _lock = ENV_TEST_LOCK.lock().unwrap();
118 unsafe {
120 std::env::set_var("SENTINEL_API_TOKEN", "");
121 }
122 let result = SentinelClient::from_env();
123 unsafe {
124 std::env::remove_var("SENTINEL_API_TOKEN");
125 }
126 assert!(
127 result.is_none(),
128 "expected None when SENTINEL_API_TOKEN is empty string"
129 );
130 }
131
132 #[test]
134 fn test_valid_token_returns_some_with_defaults() {
135 let _lock = ENV_TEST_LOCK.lock().unwrap();
136 unsafe {
138 std::env::set_var("SENTINEL_API_TOKEN", "my-test-token");
139 }
140 unsafe {
141 std::env::remove_var("SENTINEL_API_URL");
142 }
143 let result = SentinelClient::from_env();
144 unsafe {
145 std::env::remove_var("SENTINEL_API_TOKEN");
146 }
147 let client = result.expect("expected Some when SENTINEL_API_TOKEN is non-empty");
148 assert_eq!(client.token, "my-test-token");
149 assert_eq!(client.api_base, DEFAULT_API_BASE);
150 }
151
152 #[test]
154 fn test_api_url_env_override() {
155 let _lock = ENV_TEST_LOCK.lock().unwrap();
156 unsafe {
158 std::env::set_var("SENTINEL_API_TOKEN", "tok");
159 }
160 unsafe {
161 std::env::set_var("SENTINEL_API_URL", "http://localhost:9999");
162 }
163 let result = SentinelClient::from_env();
164 unsafe {
165 std::env::remove_var("SENTINEL_API_TOKEN");
166 }
167 unsafe {
168 std::env::remove_var("SENTINEL_API_URL");
169 }
170 let client = result.expect("expected Some when token is set");
171 assert_eq!(client.api_base, "http://localhost:9999");
172 }
173}