use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
use serial_test::serial;
use tracing::field::{Field, Visit};
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::prelude::*;
use trusty_common::credentials::KeyStoreError;
use super::test_env::EnvVarGuard;
use super::*;
#[derive(Default)]
struct Sink {
lines: Mutex<Vec<String>>,
}
struct CaptureLayer {
sink: Arc<Sink>,
}
struct Fields {
out: String,
}
impl Visit for Fields {
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
use std::fmt::Write as _;
let _ = write!(self.out, " {}={value:?}", field.name());
}
fn record_str(&mut self, field: &Field, value: &str) {
use std::fmt::Write as _;
let _ = write!(self.out, " {}={value}", field.name());
}
}
impl<S: tracing::Subscriber> Layer<S> for CaptureLayer {
fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
let mut fields = Fields {
out: event.metadata().level().to_string(),
};
event.record(&mut fields);
self.sink
.lines
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(fields.out);
}
}
fn capture<T>(f: impl FnOnce() -> T) -> (T, Vec<String>) {
let sink = Arc::new(Sink::default());
let subscriber = tracing_subscriber::registry().with(CaptureLayer {
sink: Arc::clone(&sink),
});
let out = tracing::subscriber::with_default(subscriber, f);
let lines = sink
.lines
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
(out, lines)
}
fn assert_logged(lines: &[String], var: &str, kind: &str) {
let errors: Vec<&String> = lines.iter().filter(|l| l.starts_with("ERROR")).collect();
assert_eq!(errors.len(), 1, "expected one ERROR line, got {lines:?}");
assert!(errors[0].contains(var), "{}", errors[0]);
assert!(
errors[0].contains(&format!("kind={kind}")),
"expected kind={kind}: {}",
errors[0]
);
}
struct AbsentStore {
calls: Arc<AtomicUsize>,
}
impl KeyStore for AbsentStore {
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()
}
}
struct NeverReturns;
impl KeyStore for NeverReturns {
fn get(&self, _provider: &str) -> Option<String> {
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 RefusingStore;
impl KeyStore for RefusingStore {
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()
}
}
#[test]
#[serial]
fn an_absent_secret_is_none_and_logs_absent() {
let _guard = EnvVarGuard::unset("BITBUCKET_APP_PASSWORD");
let calls = Arc::new(AtomicUsize::new(0));
let store = Arc::new(AbsentStore {
calls: Arc::clone(&calls),
});
let (resolved, lines) = capture(|| {
resolve_secret_with("BITBUCKET_APP_PASSWORD", store, Duration::from_millis(500))
});
assert!(resolved.is_none(), "an absent credential must not resolve");
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"the store was consulted once"
);
assert_logged(&lines, "BITBUCKET_APP_PASSWORD", "absent");
}
#[test]
#[serial]
fn a_store_timeout_is_none_and_logs_timeout() {
let _guard = EnvVarGuard::unset("JIRA_API_TOKEN");
let bound = Duration::from_millis(200);
let started = std::time::Instant::now();
let (resolved, lines) =
capture(|| resolve_secret_with("JIRA_API_TOKEN", Arc::new(NeverReturns), bound));
let waited = started.elapsed();
assert!(resolved.is_none(), "a timed-out read must not resolve");
assert!(
waited < Duration::from_secs(2),
"the caller waited {waited:?}"
);
assert_logged(&lines, "JIRA_API_TOKEN", "timeout");
}
#[test]
#[serial]
fn a_store_error_is_none_and_logs_the_kind() {
let _guard = EnvVarGuard::unset("LINEAR_API_KEY");
let (resolved, lines) = capture(|| {
resolve_secret_with(
"LINEAR_API_KEY",
Arc::new(RefusingStore),
Duration::from_millis(500),
)
});
assert!(resolved.is_none(), "a refused store must not resolve");
assert_logged(&lines, "LINEAR_API_KEY", "keyring-backend");
}
#[test]
#[serial]
fn an_unregistered_variable_reads_only_the_process_environment() {
let calls = Arc::new(AtomicUsize::new(0));
let store = || {
Arc::new(AbsentStore {
calls: Arc::clone(&calls),
})
};
let present = EnvVarGuard::set("TM_TEST_UNREGISTERED_8236", "from-the-process-env");
let (resolved, lines) = capture(|| {
resolve_secret_with(
"TM_TEST_UNREGISTERED_8236",
store(),
Duration::from_millis(500),
)
});
assert_eq!(resolved.as_deref(), Some("from-the-process-env"));
assert!(lines.is_empty(), "a success must log nothing: {lines:?}");
drop(present);
let (resolved, lines) = capture(|| {
resolve_secret_with(
"TM_TEST_UNREGISTERED_8236",
store(),
Duration::from_millis(500),
)
});
assert!(resolved.is_none(), "an unset variable must not resolve");
assert_logged(&lines, "TM_TEST_UNREGISTERED_8236", "absent");
assert_eq!(
calls.load(Ordering::SeqCst),
0,
"an unregistered name must never reach the credential store"
);
}
struct HoldingStore {
calls: Arc<AtomicUsize>,
}
impl KeyStore for HoldingStore {
fn get(&self, _provider: &str) -> Option<String> {
self.calls.fetch_add(1, Ordering::SeqCst);
Some("from-the-store-9121".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()
}
}
const SANDBOX_TEST_VAR: &str = "FIREWORKS_API_KEY";
#[test]
#[serial]
fn sandbox_mode_never_consults_the_store() {
let _env = EnvVarGuard::unset(SANDBOX_TEST_VAR);
let calls = Arc::new(AtomicUsize::new(0));
let store = Arc::new(HoldingStore {
calls: Arc::clone(&calls),
});
let (resolved, lines) = capture(|| {
resolve_gated(true, SANDBOX_TEST_VAR, || {
resolve_secret_with(SANDBOX_TEST_VAR, store, Duration::from_millis(500))
})
});
assert!(resolved.is_none(), "a sandbox must resolve no credential");
assert_eq!(calls.load(Ordering::SeqCst), 0, "the store was read");
assert!(
lines
.iter()
.any(|l| l.starts_with("WARN") && l.contains(SANDBOX_TEST_VAR)),
"the skip must be logged by name: {lines:?}"
);
assert!(
!lines.iter().any(|l| l.contains("from-the-store")),
"no value may be logged: {lines:?}"
);
}
#[test]
#[serial]
fn outside_sandbox_mode_the_store_is_consulted() {
let _env = EnvVarGuard::unset(SANDBOX_TEST_VAR);
let calls = Arc::new(AtomicUsize::new(0));
let store = Arc::new(HoldingStore {
calls: Arc::clone(&calls),
});
let resolved = resolve_gated(false, SANDBOX_TEST_VAR, || {
resolve_secret_with(SANDBOX_TEST_VAR, store, Duration::from_millis(500))
});
assert!(
resolved.as_deref() == Some("from-the-store-9121"),
"the open gate did not return the store's value"
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn a_sandbox_store_never_builds_the_real_store() {
let store = store_gated(true, || panic!("the real store was built in sandbox mode"));
assert!(store.get("telegram").is_none());
assert!(store.get("openrouter").is_none());
assert!(store.list().is_empty(), "the sandbox store is not empty");
}
#[test]
fn outside_sandbox_mode_the_real_store_is_built() {
let calls = Arc::new(AtomicUsize::new(0));
let reads = Arc::clone(&calls);
let store = store_gated(false, move || Box::new(HoldingStore { calls: reads }));
assert!(
store.get("openrouter").as_deref() == Some("from-the-store-9121"),
"the open gate did not hand back the real store"
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn a_sandboxed_bounded_resolve_is_absent_and_reads_nothing() {
let (outcome, lines) = capture(|| {
bounded_gated(true, "OPENROUTER_API_KEY", |_| {
panic!("a tier was read in sandbox mode")
})
});
assert!(
matches!(&outcome, Err(SecretResolveError::Absent { var }) if var == "OPENROUTER_API_KEY"),
"a sandboxed probe must be Absent"
);
assert!(lines.is_empty(), "the probe must not log: {lines:?}");
}
#[test]
fn outside_sandbox_mode_the_bounded_resolver_runs() {
let outcome = bounded_gated(false, "OPENROUTER_API_KEY", |_| Ok("resolved".into()));
assert!(
outcome.as_deref() == Ok("resolved"),
"the open gate did not run the resolver"
);
}