fastmcp_rust/testing/fixtures/
auth.rs1use std::collections::HashMap;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12fn hex_encode(data: &[u8]) -> String {
18 use std::fmt::Write;
19 data.iter()
20 .fold(String::with_capacity(data.len() * 2), |mut acc, b| {
21 let _ = write!(acc, "{b:02x}");
22 acc
23 })
24}
25
26fn hex_decode(s: &str) -> Option<Vec<u8>> {
28 if s.len() % 2 != 0 {
29 return None;
30 }
31 (0..s.len())
32 .step_by(2)
33 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
34 .collect()
35}
36
37pub const VALID_BEARER_VALUE: &str = "test-bearer-value-12345";
43
44pub const INVALID_BEARER_VALUE: &str = "invalid-value-00000";
46
47pub const VALID_API_VALUE: &str = "test-api-value-abcdef123456";
49
50pub const INVALID_API_VALUE: &str = "invalid-api-value";
52
53pub const VALID_SESSION_VALUE: &str = "sess-value-xyz789";
55
56#[must_use]
64pub fn generate_test_value(prefix: &str, length: usize) -> String {
65 use std::time::SystemTime;
66
67 let timestamp = SystemTime::now()
68 .duration_since(UNIX_EPOCH)
69 .unwrap_or_default()
70 .as_nanos();
71
72 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz0123456789".chars().collect();
73 let mut value = String::with_capacity(prefix.len() + length + 1);
74 value.push_str(prefix);
75 value.push('-');
76
77 let mut seed = timestamp as usize;
78 for _ in 0..length {
79 seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
80 let idx = seed % chars.len();
81 value.push(chars[idx]);
82 }
83
84 value
85}
86
87#[must_use]
89pub fn generate_bearer_value() -> String {
90 generate_test_value("bear", 32)
91}
92
93#[must_use]
95pub fn generate_api_value() -> String {
96 generate_test_value("api", 24)
97}
98
99#[must_use]
101pub fn generate_session_value() -> String {
102 generate_test_value("sess", 16)
103}
104
105#[derive(Debug, Clone)]
113pub struct TestJwt {
114 pub header: HashMap<String, String>,
116 pub payload: HashMap<String, serde_json::Value>,
118 pub signature: String,
120}
121
122impl TestJwt {
123 #[must_use]
125 pub fn new() -> Self {
126 let mut header = HashMap::new();
127 header.insert("alg".to_string(), "HS256".to_string());
128 header.insert("typ".to_string(), "JWT".to_string());
129
130 Self {
131 header,
132 payload: HashMap::new(),
133 signature: "test-signature".to_string(),
134 }
135 }
136
137 #[must_use]
139 pub fn subject(mut self, sub: impl Into<String>) -> Self {
140 self.payload
141 .insert("sub".to_string(), serde_json::json!(sub.into()));
142 self
143 }
144
145 #[must_use]
147 pub fn issuer(mut self, iss: impl Into<String>) -> Self {
148 self.payload
149 .insert("iss".to_string(), serde_json::json!(iss.into()));
150 self
151 }
152
153 #[must_use]
155 pub fn audience(mut self, aud: impl Into<String>) -> Self {
156 self.payload
157 .insert("aud".to_string(), serde_json::json!(aud.into()));
158 self
159 }
160
161 #[must_use]
163 pub fn expires_in(mut self, seconds: u64) -> Self {
164 let exp = SystemTime::now()
165 .duration_since(UNIX_EPOCH)
166 .unwrap_or_default()
167 .as_secs()
168 + seconds;
169 self.payload
170 .insert("exp".to_string(), serde_json::json!(exp));
171 self
172 }
173
174 #[must_use]
176 pub fn expired(mut self) -> Self {
177 let exp = SystemTime::now()
178 .duration_since(UNIX_EPOCH)
179 .unwrap_or_default()
180 .as_secs()
181 - 3600; self.payload
183 .insert("exp".to_string(), serde_json::json!(exp));
184 self
185 }
186
187 #[must_use]
189 pub fn issued_now(mut self) -> Self {
190 let iat = SystemTime::now()
191 .duration_since(UNIX_EPOCH)
192 .unwrap_or_default()
193 .as_secs();
194 self.payload
195 .insert("iat".to_string(), serde_json::json!(iat));
196 self
197 }
198
199 #[must_use]
201 pub fn claim(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
202 self.payload.insert(key.into(), value);
203 self
204 }
205
206 #[must_use]
211 pub fn encode(&self) -> String {
212 let header_json = serde_json::to_string(&self.header).unwrap_or_default();
213 let payload_json = serde_json::to_string(&self.payload).unwrap_or_default();
214
215 let header_hex = hex_encode(header_json.as_bytes());
217 let payload_hex = hex_encode(payload_json.as_bytes());
218 let sig_hex = hex_encode(self.signature.as_bytes());
219
220 format!("{header_hex}.{payload_hex}.{sig_hex}")
221 }
222
223 #[must_use]
225 pub fn is_expired(&self) -> bool {
226 if let Some(exp) = self.payload.get("exp") {
227 if let Some(exp_secs) = exp.as_u64() {
228 let now = SystemTime::now()
229 .duration_since(UNIX_EPOCH)
230 .unwrap_or_default()
231 .as_secs();
232 return now >= exp_secs;
233 }
234 }
235 false
236 }
237}
238
239impl Default for TestJwt {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245#[must_use]
251pub fn valid_jwt() -> TestJwt {
252 TestJwt::new()
253 .subject("test-user")
254 .issuer("test-issuer")
255 .audience("test-audience")
256 .issued_now()
257 .expires_in(3600) }
259
260#[must_use]
262pub fn expired_jwt() -> TestJwt {
263 TestJwt::new()
264 .subject("test-user")
265 .issuer("test-issuer")
266 .expired()
267}
268
269#[must_use]
271pub fn jwt_with_roles(roles: Vec<&str>) -> TestJwt {
272 let roles: Vec<String> = roles.into_iter().map(String::from).collect();
273 valid_jwt().claim("roles", serde_json::json!(roles))
274}
275
276#[must_use]
278pub fn admin_jwt() -> TestJwt {
279 jwt_with_roles(vec!["admin", "user"])
280}
281
282#[must_use]
284pub fn readonly_jwt() -> TestJwt {
285 jwt_with_roles(vec!["readonly"])
286}
287
288#[must_use]
294pub fn bearer_auth_header(value: &str) -> String {
295 format!("Bearer {value}")
296}
297
298#[must_use]
303pub fn basic_auth_header(username: &str, password: &str) -> String {
304 let credentials = format!("{username}:{password}");
305 let encoded = hex_encode(credentials.as_bytes());
306 format!("Basic {encoded}")
307}
308
309#[must_use]
311pub fn api_value_header(value: &str) -> String {
312 value.to_string()
313}
314
315#[derive(Debug, Clone)]
321pub struct TestCredentials {
322 pub username: String,
324 pub password: String,
326}
327
328impl TestCredentials {
329 #[must_use]
331 pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
332 Self {
333 username: username.into(),
334 password: password.into(),
335 }
336 }
337
338 #[must_use]
340 pub fn valid() -> Self {
341 Self::new("test-user", "test-credential-123")
342 }
343
344 #[must_use]
346 pub fn invalid() -> Self {
347 Self::new("invalid-user", "wrong-credential")
348 }
349
350 #[must_use]
352 pub fn admin() -> Self {
353 Self::new("admin", "admin-credential-456")
354 }
355
356 #[must_use]
358 pub fn to_basic_auth(&self) -> String {
359 basic_auth_header(&self.username, &self.password)
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_static_tokens() {
369 assert!(VALID_BEARER_VALUE.len() > 10);
370 assert!(INVALID_BEARER_VALUE.len() > 0);
371 assert!(VALID_API_VALUE.starts_with("test-api-"));
372 assert!(VALID_SESSION_VALUE.starts_with("sess-"));
373 }
374
375 #[test]
376 fn test_generate_test_token() {
377 let value1 = generate_test_value("test", 16);
378 let _value2 = generate_test_value("test", 16);
379
380 assert!(value1.starts_with("test-"));
381 assert!(value1.len() == "test-".len() + 16);
382 assert!(
385 value1
386 .chars()
387 .all(|c| c.is_ascii_alphanumeric() || c == '-')
388 );
389 }
390
391 #[test]
392 fn test_generate_bearer_token() {
393 let value = generate_bearer_value();
394 assert!(value.starts_with("bear-"));
395 }
396
397 #[test]
398 fn test_generate_api_key() {
399 let value = generate_api_value();
400 assert!(value.starts_with("api-"));
401 }
402
403 #[test]
404 fn test_test_jwt_creation() {
405 let jwt = TestJwt::new()
406 .subject("user123")
407 .issuer("test-app")
408 .expires_in(3600);
409
410 assert!(jwt.payload.contains_key("sub"));
411 assert!(jwt.payload.contains_key("iss"));
412 assert!(jwt.payload.contains_key("exp"));
413 }
414
415 #[test]
416 fn test_test_jwt_encode() {
417 let jwt = valid_jwt();
418 let encoded = jwt.encode();
419
420 let parts: Vec<_> = encoded.split('.').collect();
422 assert_eq!(parts.len(), 3);
423 }
424
425 #[test]
426 fn test_test_jwt_expired() {
427 let expired = expired_jwt();
428 assert!(expired.is_expired());
429
430 let valid = valid_jwt();
431 assert!(!valid.is_expired());
432 }
433
434 #[test]
435 fn test_jwt_with_roles() {
436 let jwt = jwt_with_roles(vec!["admin", "user"]);
437 assert!(jwt.payload.contains_key("roles"));
438
439 let roles = jwt.payload.get("roles").unwrap();
440 let roles_arr = roles.as_array().unwrap();
441 assert_eq!(roles_arr.len(), 2);
442 }
443
444 #[test]
445 fn test_bearer_auth_header() {
446 let header = bearer_auth_header("value123");
447 assert_eq!(header, "Bearer value123");
448 }
449
450 #[test]
451 fn test_basic_auth_header() {
452 let header = basic_auth_header("user", "pass");
453 assert!(header.starts_with("Basic "));
454
455 let encoded = header.strip_prefix("Basic ").unwrap();
457 let decoded = hex_decode(encoded).unwrap();
458 let credentials = String::from_utf8(decoded).unwrap();
459 assert_eq!(credentials, "user:pass");
460 }
461
462 #[test]
463 fn test_test_credentials() {
464 let valid = TestCredentials::valid();
465 assert!(!valid.username.is_empty());
466 assert!(!valid.password.is_empty());
467
468 let admin = TestCredentials::admin();
469 assert_eq!(admin.username, "admin");
470 }
471
472 #[test]
473 fn test_credentials_to_basic_auth() {
474 let creds = TestCredentials::new("user", "pass");
475 let header = creds.to_basic_auth();
476 assert!(header.starts_with("Basic "));
477 }
478}