1use secrecy::SecretString;
11
12pub trait TokenProvider: Send + Sync {
21 fn github_token(&self) -> Option<SecretString>;
25
26 fn ai_api_key(&self, provider: &str) -> Option<SecretString>;
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use crate::ai::registry::all_providers;
39 use std::collections::HashMap;
40
41 struct MockTokenProvider {
43 github_token: Option<SecretString>,
44 ai_keys: HashMap<String, SecretString>,
45 }
46
47 impl Drop for MockTokenProvider {
48 fn drop(&mut self) {
49 use zeroize::Zeroize;
50 if let Some(ref mut gh_token) = self.github_token {
51 gh_token.zeroize();
52 }
53 for ai_key in self.ai_keys.values_mut() {
54 ai_key.zeroize();
55 }
56 }
57 }
58
59 impl TokenProvider for MockTokenProvider {
60 fn github_token(&self) -> Option<SecretString> {
61 self.github_token.clone()
62 }
63
64 fn ai_api_key(&self, provider: &str) -> Option<SecretString> {
65 self.ai_keys.get(provider).cloned()
66 }
67 }
68
69 #[test]
70 fn test_mock_provider_with_tokens() {
71 let mut ai_keys = HashMap::new();
72 for provider_config in all_providers() {
73 ai_keys.insert(
74 provider_config.name.to_string(),
75 SecretString::from(format!("{}_key", provider_config.name)),
76 );
77 }
78
79 let provider = MockTokenProvider {
80 github_token: Some(SecretString::from("gh_token")),
81 ai_keys,
82 };
83
84 assert!(provider.github_token().is_some());
85 for provider_config in all_providers() {
86 assert!(
87 provider.ai_api_key(provider_config.name).is_some(),
88 "Expected key for provider: {}",
89 provider_config.name
90 );
91 }
92 }
93
94 #[test]
95 fn test_mock_provider_without_tokens() {
96 let provider = MockTokenProvider {
97 github_token: None,
98 ai_keys: HashMap::new(),
99 };
100
101 assert!(provider.github_token().is_none());
102 for provider_config in all_providers() {
103 assert!(
104 provider.ai_api_key(provider_config.name).is_none(),
105 "Expected no key for provider: {}",
106 provider_config.name
107 );
108 }
109 }
110}