axess_identity/
testing.rs1use uuid::Uuid;
30
31use crate::id::{DeviceId, EventId, SessionId, TenantId, UserId};
32use crate::{IdentityError, Principal, PrincipalResolver};
33
34pub const TEST_NAMESPACE: Uuid = Uuid::from_bytes([
40 0x9a, 0x36, 0xb1, 0xfe, 0x3c, 0xf2, 0x4b, 0xa5, 0x88, 0x46, 0xe1, 0x47, 0x65, 0x09, 0xc7, 0x12,
41]);
42
43#[inline]
45pub fn tenant(label: &str) -> TenantId {
46 TenantId::from_namespaced_str(TEST_NAMESPACE, label)
47}
48
49#[inline]
51pub fn user(label: &str) -> UserId {
52 UserId::from_namespaced_str(TEST_NAMESPACE, label)
53}
54
55#[inline]
57pub fn device(label: &str) -> DeviceId {
58 DeviceId::from_namespaced_str(TEST_NAMESPACE, label)
59}
60
61#[inline]
65pub fn session(label: &str) -> SessionId {
66 SessionId::from_namespaced_str(TEST_NAMESPACE, label)
67}
68
69#[inline]
71pub fn event(label: &str) -> EventId {
72 EventId::from_namespaced_str(TEST_NAMESPACE, label)
73}
74
75#[derive(Debug, Clone)]
80pub struct MockResolver {
81 outcome: Result<Principal, MockErrorShape>,
82}
83
84#[derive(Debug, Clone)]
89enum MockErrorShape {
90 NotAuthenticated,
91 InvalidComponent(String),
92}
93
94impl MockErrorShape {
95 fn rebuild(&self) -> IdentityError {
96 match self {
97 Self::NotAuthenticated => IdentityError::NotAuthenticated,
98 Self::InvalidComponent(msg) => IdentityError::InvalidComponent(msg.clone()),
99 }
100 }
101}
102
103impl MockResolver {
104 pub fn new(principal: Principal) -> Self {
106 Self {
107 outcome: Ok(principal),
108 }
109 }
110
111 pub fn not_authenticated() -> Self {
115 Self {
116 outcome: Err(MockErrorShape::NotAuthenticated),
117 }
118 }
119
120 pub fn invalid_component(message: impl Into<String>) -> Self {
123 Self {
124 outcome: Err(MockErrorShape::InvalidComponent(message.into())),
125 }
126 }
127}
128
129impl PrincipalResolver for MockResolver {
130 async fn resolve(&self) -> Result<Principal, IdentityError> {
131 match &self.outcome {
132 Ok(p) => Ok(p.clone()),
133 Err(shape) => Err(shape.rebuild()),
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use std::collections::BTreeMap;
141
142 use super::*;
143 use crate::{HumanPrincipal, Issuer, TrustDomain, WorkloadId, WorkloadPrincipal};
144 use crate::{TenantId, UserId};
145
146 fn sample_human() -> Principal {
147 Principal::Human(HumanPrincipal {
148 user_id: UserId::from_bytes([1u8; 16]),
149 tenant_id: TenantId::from_bytes([2u8; 16]),
150 session_id: None,
151 attributes: BTreeMap::new(),
152 })
153 }
154
155 fn sample_workload() -> Principal {
156 let trust = TrustDomain::new("gnomes.local").unwrap();
157 let wid = WorkloadId::build(&trust, "compute-worker", "ekekrantz").unwrap();
158 Principal::Workload(WorkloadPrincipal {
159 workload_id: wid,
160 trust_domain: trust,
161 issuer: Issuer::Cli,
162 tenant_id: TenantId::from_bytes([3u8; 16]),
163 tenant_slug: "ekekrantz".to_string(),
164 service_name: "compute-worker".to_string(),
165 attributes: BTreeMap::new(),
166 })
167 }
168
169 #[tokio::test]
170 async fn mock_returns_canned_human_principal() {
171 let canned = sample_human();
172 let mock = MockResolver::new(canned.clone());
173 let resolved = mock.resolve().await.unwrap();
174 assert_eq!(resolved, canned);
175 }
176
177 #[tokio::test]
178 async fn mock_returns_canned_workload_principal() {
179 let canned = sample_workload();
180 let mock = MockResolver::new(canned.clone());
181 let resolved = mock.resolve().await.unwrap();
182 assert_eq!(resolved, canned);
183 }
184
185 #[tokio::test]
186 async fn mock_is_idempotent_across_calls() {
187 let mock = MockResolver::new(sample_human());
188 let a = mock.resolve().await.unwrap();
189 let b = mock.resolve().await.unwrap();
190 assert_eq!(a, b);
191 }
192
193 #[tokio::test]
194 async fn mock_not_authenticated_returns_error() {
195 let mock = MockResolver::not_authenticated();
196 let err = mock.resolve().await.unwrap_err();
197 assert!(matches!(err, IdentityError::NotAuthenticated));
198 }
199
200 #[tokio::test]
201 async fn mock_invalid_component_carries_message() {
202 let mock = MockResolver::invalid_component("oops");
203 let err = mock.resolve().await.unwrap_err();
204 match err {
205 IdentityError::InvalidComponent(msg) => assert_eq!(msg, "oops"),
206 other => panic!("expected InvalidComponent, got {other:?}"),
207 }
208 }
209}