use std::sync::atomic::{AtomicUsize, Ordering};
use serial_test::serial;
use super::*;
use crate::credentials::env_guard::EnvVarGuard;
struct NeverReturns {
calls: Arc<AtomicUsize>,
}
impl KeyStore for NeverReturns {
fn get(&self, _provider: &str) -> Option<String> {
self.calls.fetch_add(1, Ordering::SeqCst);
loop {
std::thread::sleep(Duration::from_secs(3600));
}
}
fn set(&self, _provider: &str, _value: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn unset(&self, _provider: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn list(&self) -> Vec<String> {
Vec::new()
}
}
struct AlwaysFails;
impl KeyStore for AlwaysFails {
fn get(&self, _provider: &str) -> Option<String> {
None
}
fn try_get(&self, _provider: &str) -> Result<Option<String>, KeyStoreError> {
Err(KeyStoreError::Keyring("locked".to_string()))
}
fn set(&self, _provider: &str, _value: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn unset(&self, _provider: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn list(&self) -> Vec<String> {
Vec::new()
}
}
struct CountingAbsent {
calls: Arc<AtomicUsize>,
}
impl KeyStore for CountingAbsent {
fn get(&self, _provider: &str) -> Option<String> {
self.calls.fetch_add(1, Ordering::SeqCst);
None
}
fn set(&self, _provider: &str, _value: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn unset(&self, _provider: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn list(&self) -> Vec<String> {
Vec::new()
}
}
#[test]
fn a_store_that_never_returns_times_out_within_the_bound() {
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(NeverReturns {
calls: Arc::clone(&calls),
});
let bound = Duration::from_millis(200);
let started = Instant::now();
let err = store_get_bounded(store, "test-never-returns-a", bound).expect_err("must time out");
let elapsed = started.elapsed();
assert_eq!(err.kind, StoreErrorKind::Timeout);
assert!(!err.cached, "a fresh read must not report itself cached");
assert!(
elapsed < bound * 5,
"the caller waited {elapsed:?}, well past the {bound:?} bound"
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn concurrent_resolves_issue_exactly_one_store_read() {
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(NeverReturns {
calls: Arc::clone(&calls),
});
let provider = "test-single-flight-b";
let handles: Vec<_> = (0..8)
.map(|_| {
let store = Arc::clone(&store);
std::thread::spawn(move || {
store_get_bounded(store, provider, Duration::from_millis(150))
})
})
.collect();
for handle in handles {
let got = handle.join().expect("thread");
assert_eq!(got, Err(StoreFailure::fresh(StoreErrorKind::Timeout)));
}
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"8 concurrent callers issued more than one store read"
);
}
#[test]
fn a_cached_error_is_returned_without_a_second_read() {
let provider = "test-negative-cache-c";
record_error(provider, StoreErrorKind::Keyring);
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(CountingAbsent {
calls: Arc::clone(&calls),
});
let err = store_get_bounded(store, provider, Duration::from_secs(1)).expect_err("cached");
assert_eq!(err.kind, StoreErrorKind::Keyring);
assert!(err.cached, "the cache answered but did not say so");
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"the cache did not suppress"
);
}
#[test]
fn the_error_cache_expires_and_the_next_read_is_issued() {
let provider = "test-negative-cache-expiry-d";
map(&ERROR_CACHE)
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(
provider.to_string(),
(
Instant::now() - STORE_ERROR_CACHE_TTL - Duration::from_secs(1),
StoreErrorKind::Timeout,
),
);
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(CountingAbsent {
calls: Arc::clone(&calls),
});
let got = store_get_bounded(store, provider, Duration::from_secs(2));
assert_eq!(got, Ok(None), "a stale cache entry blocked a fresh read");
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn a_failing_store_reports_its_kind() {
let store: Arc<dyn KeyStore> = Arc::new(AlwaysFails);
let err = store_get_bounded(store, "test-failing-e", Duration::from_secs(1))
.expect_err("backend failure");
assert_eq!(err.kind, StoreErrorKind::Keyring);
assert!(!err.cached);
}
#[test]
#[serial(dotenv_credential_env)]
fn an_absent_value_is_absent_not_an_error() {
let _env = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _guard = EnvVarGuard::remove("LINEAR_API_KEY");
let store: Arc<dyn KeyStore> = Arc::new(CountingAbsent {
calls: Arc::new(AtomicUsize::new(0)),
});
let err = resolve_provider_bounded_with("linear", store, Duration::from_secs(1))
.expect_err("nothing configured");
assert!(matches!(err, SecretResolveError::Absent { .. }), "{err:?}");
assert_eq!(err.kind(), "absent");
assert_eq!(err.var(), "LINEAR_API_KEY");
}
#[test]
#[serial(dotenv_credential_env)]
fn the_env_tier_answers_without_touching_the_store() {
let _env = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _guard = EnvVarGuard::set("BRAVE_API_KEY", "synthetic-env-value");
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(CountingAbsent {
calls: Arc::clone(&calls),
});
let got = resolve_provider_bounded_with("brave", store, Duration::from_secs(1));
assert_eq!(got.as_deref(), Ok("synthetic-env-value"));
assert_eq!(calls.load(Ordering::SeqCst), 0);
}
#[test]
fn an_unregistered_variable_is_not_resolvable() {
let err = resolve_env_var_bounded("AWS_SECRET_ACCESS_KEY").expect_err("unregistered");
assert!(
matches!(err, SecretResolveError::Unregistered { .. }),
"{err:?}"
);
assert_eq!(err.kind(), "unregistered");
}
#[test]
fn error_kinds_render_without_any_value() {
let value = "sk-synthetic-never-a-real-key";
for err in [
SecretResolveError::Absent {
var: "OPENROUTER_API_KEY".to_string(),
},
SecretResolveError::Timeout {
var: "OPENROUTER_API_KEY".to_string(),
waited_ms: 3000,
cached: false,
},
SecretResolveError::Store {
var: "OPENROUTER_API_KEY".to_string(),
kind: StoreErrorKind::Keyring,
cached: true,
},
] {
let rendered = format!("{err} {err:?}");
assert!(!rendered.contains(value), "a value reached the message");
assert!(rendered.contains("OPENROUTER_API_KEY"), "{rendered}");
}
assert_eq!(StoreErrorKind::Timeout.to_string(), "timeout");
}
struct SlowValue {
delay: Duration,
value: String,
calls: Arc<AtomicUsize>,
}
impl KeyStore for SlowValue {
fn get(&self, _provider: &str) -> Option<String> {
self.calls.fetch_add(1, Ordering::SeqCst);
std::thread::sleep(self.delay);
Some(self.value.clone())
}
fn set(&self, _provider: &str, _value: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn unset(&self, _provider: &str) -> Result<(), KeyStoreError> {
Ok(())
}
fn list(&self) -> Vec<String> {
Vec::new()
}
}
fn await_reader_done(provider: &str, bound: Duration) {
let deadline = Instant::now() + bound;
while Instant::now() < deadline {
let outstanding = map(&INFLIGHT)
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.contains_key(provider);
if !outstanding {
return;
}
std::thread::sleep(Duration::from_millis(10));
}
panic!("the detached reader never finished within {bound:?}");
}
#[test]
fn a_late_success_retires_the_cached_timeout() {
let provider = "test-late-approval-f";
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(SlowValue {
delay: Duration::from_millis(250),
value: "synthetic-late-approved-value".to_string(),
calls: Arc::clone(&calls),
});
let err = store_get_bounded(Arc::clone(&store), provider, Duration::from_millis(30))
.expect_err("the caller must give up first");
assert_eq!(err.kind, StoreErrorKind::Timeout);
await_reader_done(provider, Duration::from_secs(5));
let got = store_get_bounded(store, provider, Duration::from_secs(5));
assert_eq!(
got,
Ok(Some("synthetic-late-approved-value".to_string())),
"the stale Timeout entry outlived the successful read that cleared it"
);
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"the second read was suppressed"
);
}
#[test]
fn a_store_failure_is_fresh_first_and_cached_second() {
let provider = "test-cached-flag-g";
let store: Arc<dyn KeyStore> = Arc::new(AlwaysFails);
let fresh = resolve_provider_bounded_with(provider, Arc::clone(&store), Duration::from_secs(1))
.expect_err("the backend refuses");
let repeat = resolve_provider_bounded_with(provider, store, Duration::from_secs(1))
.expect_err("the negative cache answers");
assert!(
matches!(
fresh,
SecretResolveError::Store {
kind: StoreErrorKind::Keyring,
cached: false,
..
}
),
"a freshly-read failure claimed to be cached: {fresh:?}"
);
assert!(
matches!(cached_flag(&repeat), Some(true)),
"a cache-served failure did not report itself cached: {repeat:?}"
);
}
fn cached_flag(err: &SecretResolveError) -> Option<bool> {
match err {
SecretResolveError::Store { cached, .. } | SecretResolveError::Timeout { cached, .. } => {
Some(*cached)
}
_ => None,
}
}
struct Gate {
open: Mutex<bool>,
changed: Condvar,
}
impl Gate {
fn new() -> Arc<Self> {
Arc::new(Self {
open: Mutex::new(false),
changed: Condvar::new(),
})
}
fn open(&self) {
*self
.open
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = true;
self.changed.notify_all();
}
fn wait(&self, bound: Duration, what: &str) {
let deadline = Instant::now() + bound;
let mut open = self
.open
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while !*open {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
panic!("gate never opened within {bound:?}: {what}");
}
let (guard, timeout) = self
.changed
.wait_timeout(open, remaining)
.unwrap_or_else(std::sync::PoisonError::into_inner);
open = guard;
if timeout.timed_out() && !*open {
panic!("gate never opened within {bound:?}: {what}");
}
}
}
}
struct InstalledParkHook;
impl InstalledParkHook {
fn install(provider: &'static str, parked: Arc<Gate>, release: Arc<Gate>) -> Self {
let hook: ParkHook = Arc::new(move |p: &str| {
if p != provider {
return;
}
parked.open();
release.wait(
Duration::from_secs(5),
"the test never opened release after forcing the caller's timeout to elapse",
);
});
*PARK_BEFORE_PUBLISH
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(hook);
Self
}
}
impl Drop for InstalledParkHook {
fn drop(&mut self) {
*PARK_BEFORE_PUBLISH
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
}
#[test]
fn a_timed_out_caller_cannot_cache_behind_a_publishing_reader() {
let provider = "test-publish-race-h";
let value = "synthetic-race-approved-value";
clear_error(provider);
let parked = Gate::new();
let release = Gate::new();
let _hook = InstalledParkHook::install(provider, Arc::clone(&parked), Arc::clone(&release));
let calls = Arc::new(AtomicUsize::new(0));
let store: Arc<dyn KeyStore> = Arc::new(SlowValue {
delay: Duration::ZERO,
value: value.to_string(),
calls: Arc::clone(&calls),
});
let bound = Duration::from_millis(120);
let caller = std::thread::spawn(move || store_get_bounded(store, provider, bound));
parked.wait(
Duration::from_secs(5),
"the reader never reached park_before_publish inside finish",
);
std::thread::sleep(bound * 2);
release.open();
await_reader_done(provider, Duration::from_secs(5));
let got = caller.join().expect("caller thread");
assert_eq!(
cached_error(provider),
None,
"the timed-out caller cached a Timeout behind the reader's clear_error, \
suppressing reads for the full TTL although the value was published"
);
assert_eq!(
got,
Ok(Some(value.to_string())),
"the caller must observe the outcome the reader published under the \
same lock, not its own Timeout"
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
}