Skip to main content

aether_auth/
fake.rs

1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use async_trait::async_trait;
5
6use crate::credential::{OAuthCredential, OAuthCredentialStorage};
7use crate::error::OAuthError;
8
9#[derive(Default)]
10pub struct FakeOAuthCredentialStore {
11    credentials: Mutex<HashMap<String, OAuthCredential>>,
12}
13
14impl FakeOAuthCredentialStore {
15    pub fn new() -> Self {
16        Self { credentials: Mutex::new(HashMap::new()) }
17    }
18
19    pub fn with_credential(self, key: &str, credential: OAuthCredential) -> Self {
20        self.credentials.lock().unwrap().insert(key.to_string(), credential);
21        self
22    }
23}
24
25#[async_trait]
26impl OAuthCredentialStorage for FakeOAuthCredentialStore {
27    async fn load_credential(&self, server_id: &str) -> Result<Option<OAuthCredential>, OAuthError> {
28        Ok(self.credentials.lock().unwrap().get(server_id).cloned())
29    }
30
31    async fn save_credential(&self, key: &str, credential: OAuthCredential) -> Result<(), OAuthError> {
32        self.credentials.lock().unwrap().insert(key.to_string(), credential);
33        Ok(())
34    }
35
36    async fn delete_credential(&self, key: &str) -> Result<(), OAuthError> {
37        self.credentials.lock().unwrap().remove(key);
38        Ok(())
39    }
40
41    fn has_credential(&self, key: &str) -> bool {
42        self.credentials.lock().unwrap().contains_key(key)
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[tokio::test]
51    async fn load_returns_none_when_empty() {
52        let store = FakeOAuthCredentialStore::new();
53        let result = store.load_credential("unknown").await;
54        assert!(result.unwrap().is_none());
55    }
56
57    #[tokio::test]
58    async fn save_then_load_round_trips() {
59        let store = FakeOAuthCredentialStore::new();
60        let cred = OAuthCredential {
61            client_id: "client_1".to_string(),
62            access_token: "tok_abc".to_string(),
63            refresh_token: Some("ref_xyz".to_string()),
64            expires_at: Some(9_999_999_999_999),
65        };
66
67        store.save_credential("my-server", cred.clone()).await.unwrap();
68
69        let loaded = store.load_credential("my-server").await.unwrap().expect("should find saved credential");
70        assert_eq!(loaded.client_id, "client_1");
71        assert_eq!(loaded.access_token, "tok_abc");
72        assert_eq!(loaded.refresh_token.as_deref(), Some("ref_xyz"));
73    }
74
75    #[tokio::test]
76    async fn delete_removes_credential() {
77        let store = FakeOAuthCredentialStore::new();
78        let cred = OAuthCredential {
79            client_id: "c".to_string(),
80            access_token: "t".to_string(),
81            refresh_token: None,
82            expires_at: None,
83        };
84        store.save_credential("x", cred).await.unwrap();
85        assert!(store.has_credential("x"));
86
87        store.delete_credential("x").await.unwrap();
88        assert!(!store.has_credential("x"));
89    }
90
91    #[test]
92    fn has_credential_reflects_state() {
93        let store = FakeOAuthCredentialStore::new().with_credential(
94            "present",
95            OAuthCredential {
96                client_id: "c".to_string(),
97                access_token: "t".to_string(),
98                refresh_token: None,
99                expires_at: None,
100            },
101        );
102
103        assert!(store.has_credential("present"));
104        assert!(!store.has_credential("absent"));
105    }
106}