Skip to main content

static_credstore_plugin/domain/
client.rs

1use async_trait::async_trait;
2use credstore_sdk::{
3    CredStoreError, CredStorePluginClientV1, SecretMetadata, SecretRef, SecretValue,
4};
5use modkit_security::SecurityContext;
6
7use super::service::Service;
8
9#[async_trait]
10impl CredStorePluginClientV1 for Service {
11    async fn get(
12        &self,
13        ctx: &SecurityContext,
14        key: &SecretRef,
15    ) -> Result<Option<SecretMetadata>, CredStoreError> {
16        let tenant_id = ctx.subject_tenant_id();
17
18        let Some(entry) = self.get(tenant_id, key) else {
19            return Ok(None);
20        };
21
22        Ok(Some(SecretMetadata {
23            value: SecretValue::new(entry.value.as_bytes().to_vec()),
24            owner_id: entry.owner_id,
25            sharing: entry.sharing,
26            owner_tenant_id: entry.owner_tenant_id,
27        }))
28    }
29}
30
31#[cfg(test)]
32#[cfg_attr(coverage_nightly, coverage(off))]
33mod tests {
34    use super::*;
35    use crate::config::{SecretConfig, StaticCredStorePluginConfig};
36    use uuid::Uuid;
37
38    fn tenant_a() -> Uuid {
39        Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap()
40    }
41
42    fn tenant_b() -> Uuid {
43        Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap()
44    }
45
46    fn owner() -> Uuid {
47        Uuid::parse_str("33333333-3333-3333-3333-333333333333").unwrap()
48    }
49
50    fn ctx_for_tenant(tenant_id: Uuid) -> SecurityContext {
51        SecurityContext::builder()
52            .subject_id(owner())
53            .subject_tenant_id(tenant_id)
54            .build()
55            .unwrap()
56    }
57
58    fn service_with_single_secret() -> Service {
59        let cfg = StaticCredStorePluginConfig {
60            secrets: vec![SecretConfig {
61                tenant_id: tenant_a(),
62                owner_id: owner(),
63                key: "openai_api_key".to_owned(),
64                value: "sk-test-123".to_owned(),
65                sharing: credstore_sdk::SharingMode::Tenant,
66            }],
67            ..StaticCredStorePluginConfig::default()
68        };
69
70        Service::from_config(&cfg).unwrap()
71    }
72
73    #[tokio::test]
74    async fn get_returns_metadata_for_matching_tenant_and_key() {
75        let service = service_with_single_secret();
76        let plugin: &dyn CredStorePluginClientV1 = &service;
77        let key = SecretRef::new("openai_api_key").unwrap();
78
79        let result = plugin.get(&ctx_for_tenant(tenant_a()), &key).await;
80        assert!(result.is_ok());
81
82        let metadata = result.unwrap();
83        assert!(metadata.is_some());
84        let metadata = metadata.unwrap();
85        assert_eq!(metadata.value.as_bytes(), b"sk-test-123");
86        assert_eq!(metadata.owner_id, owner());
87        assert_eq!(metadata.owner_tenant_id, tenant_a());
88    }
89
90    #[tokio::test]
91    async fn get_returns_none_for_other_tenant() {
92        let service = service_with_single_secret();
93        let plugin: &dyn CredStorePluginClientV1 = &service;
94        let key = SecretRef::new("openai_api_key").unwrap();
95
96        let result = plugin.get(&ctx_for_tenant(tenant_b()), &key).await;
97        assert!(result.is_ok());
98        assert!(result.unwrap().is_none());
99    }
100
101    #[tokio::test]
102    async fn get_returns_none_for_missing_key() {
103        let service = service_with_single_secret();
104        let plugin: &dyn CredStorePluginClientV1 = &service;
105        let key = SecretRef::new("missing").unwrap();
106
107        let result = plugin.get(&ctx_for_tenant(tenant_a()), &key).await;
108        assert!(result.is_ok());
109        assert!(result.unwrap().is_none());
110    }
111
112    #[tokio::test]
113    async fn get_returns_none_when_no_secrets_configured() {
114        let service = Service::from_config(&StaticCredStorePluginConfig::default()).unwrap();
115        let plugin: &dyn CredStorePluginClientV1 = &service;
116        let key = SecretRef::new("openai_api_key").unwrap();
117
118        let result = plugin.get(&ctx_for_tenant(tenant_a()), &key).await;
119        assert!(result.is_ok());
120        assert!(result.unwrap().is_none());
121    }
122}