1use crate::error::{CleanroomError, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10pub struct AssertionContext {
12 services: HashMap<String, ServiceState>,
14 test_data: HashMap<String, serde_json::Value>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ServiceState {
21 pub name: String,
23 pub service_type: String,
25 pub connection_info: HashMap<String, String>,
27 pub health: String,
29 pub metrics: HashMap<String, f64>,
31}
32
33impl AssertionContext {
34 pub fn new() -> Self {
36 Self {
37 services: HashMap::new(),
38 test_data: HashMap::new(),
39 }
40 }
41
42 pub fn add_service(&mut self, name: String, state: ServiceState) {
44 self.services.insert(name, state);
45 }
46
47 pub fn add_test_data(&mut self, key: String, value: serde_json::Value) {
49 self.test_data.insert(key, value);
50 }
51
52 pub fn get_service(&self, name: &str) -> Option<&ServiceState> {
54 self.services.get(name)
55 }
56
57 pub fn get_test_data(&self, key: &str) -> Option<&serde_json::Value> {
59 self.test_data.get(key)
60 }
61}
62
63#[allow(dead_code)]
65pub struct DatabaseAssertions {
66 service_name: String,
67}
68
69impl DatabaseAssertions {
70 pub fn new(service_name: &str) -> Self {
72 Self {
73 service_name: service_name.to_string(),
74 }
75 }
76
77 async fn self_check(&self, method_name: &str) -> Result<()> {
79 if self.service_name.is_empty() {
81 return Err(CleanroomError::internal_error("DatabaseAssertions service_name is empty"));
82 }
83
84 if method_name.is_empty() {
86 return Err(CleanroomError::internal_error("DatabaseAssertions method_name is empty"));
87 }
88
89 Ok(())
91 }
92
93 pub async fn should_have_user(&self, _user_id: i64) -> Result<()> {
95 self.self_check("should_have_user").await?;
97
98 let context = get_assertion_context()
100 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
101
102 if let Some(user_data) = context.get_test_data(&format!("user_{}", _user_id)) {
104 if user_data.is_object() {
105 return Ok(());
106 }
107 }
108
109 Err(CleanroomError::validation_error(&format!(
110 "User {} not found in database", _user_id
111 )))
112 }
113
114 pub async fn should_have_user_count(&self, _expected_count: i64) -> Result<()> {
116 self.self_check("should_have_user_count").await?;
118
119 let context = get_assertion_context()
121 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
122
123 let mut user_count = 0;
125 for (key, _value) in &context.test_data {
126 if key.starts_with("user_") && !key.contains("session") && !key.contains("email") {
127 user_count += 1;
128 }
129 }
130
131 if user_count == _expected_count {
132 Ok(())
133 } else {
134 Err(CleanroomError::validation_error(&format!(
135 "Expected {} users, found {}", _expected_count, user_count
136 )))
137 }
138 }
139
140 pub async fn should_have_table(&self, _table_name: &str) -> Result<()> {
142 self.self_check("should_have_table").await?;
144
145 let context = get_assertion_context()
147 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
148
149 if let Some(_table_data) = context.get_test_data(&format!("table_{}", _table_name)) {
151 Ok(())
152 } else {
153 Err(CleanroomError::validation_error(&format!(
154 "Table '{}' not found in database", _table_name
155 )))
156 }
157 }
158
159 pub async fn should_have_record(&self, _table: &str, _conditions: HashMap<String, String>) -> Result<()> {
161 self.self_check("should_have_record").await?;
163
164 let context = get_assertion_context()
166 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
167
168 let record_key = format!("record_{}_{}", _table,
170 _conditions.iter()
171 .map(|(k, v)| format!("{}={}", k, v))
172 .collect::<Vec<_>>()
173 .join("&")
174 );
175
176 if let Some(_record_data) = context.get_test_data(&record_key) {
177 Ok(())
178 } else {
179 Err(CleanroomError::validation_error(&format!(
180 "Record not found in table '{}' with conditions: {:?}", _table, _conditions
181 )))
182 }
183 }
184}
185
186#[allow(dead_code)]
188pub struct CacheAssertions {
189 service_name: String,
190}
191
192impl CacheAssertions {
193 pub fn new(service_name: &str) -> Self {
195 Self {
196 service_name: service_name.to_string(),
197 }
198 }
199
200 async fn self_check(&self, method_name: &str) -> Result<()> {
202 if self.service_name.is_empty() {
204 return Err(CleanroomError::internal_error("CacheAssertions service_name is empty"));
205 }
206
207 if method_name.is_empty() {
209 return Err(CleanroomError::internal_error("CacheAssertions method_name is empty"));
210 }
211
212 Ok(())
214 }
215
216 pub async fn should_have_key(&self, _key: &str) -> Result<()> {
218 self.self_check("should_have_key").await?;
220
221 let context = get_assertion_context()
223 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
224
225 if let Some(_key_data) = context.get_test_data(&format!("cache_key_{}", _key)) {
227 Ok(())
228 } else {
229 Err(CleanroomError::validation_error(&format!(
230 "Key '{}' not found in cache", _key
231 )))
232 }
233 }
234
235 pub async fn should_have_value(&self, _key: &str, _expected_value: &str) -> Result<()> {
237 self.self_check("should_have_value").await?;
239
240 let context = get_assertion_context()
242 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
243
244 if let Some(key_data) = context.get_test_data(&format!("cache_key_{}", _key)) {
246 if let Some(actual_value) = key_data.as_str() {
247 if actual_value == _expected_value {
248 Ok(())
249 } else {
250 Err(CleanroomError::validation_error(&format!(
251 "Key '{}' has value '{}', expected '{}'", _key, actual_value, _expected_value
252 )))
253 }
254 } else {
255 Err(CleanroomError::validation_error(&format!(
256 "Key '{}' exists but value is not a string", _key
257 )))
258 }
259 } else {
260 Err(CleanroomError::validation_error(&format!(
261 "Key '{}' not found in cache", _key
262 )))
263 }
264 }
265
266 pub async fn should_have_user_session(&self, _user_id: i64) -> Result<()> {
268 self.self_check("should_have_user_session").await?;
270
271 let context = get_assertion_context()
273 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
274
275 if let Some(_session_data) = context.get_test_data(&format!("user_session_{}", _user_id)) {
277 Ok(())
278 } else {
279 Err(CleanroomError::validation_error(&format!(
280 "User session for user {} not found in cache", _user_id
281 )))
282 }
283 }
284}
285
286#[allow(dead_code)]
288pub struct EmailServiceAssertions {
289 service_name: String,
290}
291
292impl EmailServiceAssertions {
293 pub fn new(service_name: &str) -> Self {
295 Self {
296 service_name: service_name.to_string(),
297 }
298 }
299
300 async fn self_check(&self, method_name: &str) -> Result<()> {
302 if self.service_name.is_empty() {
304 return Err(CleanroomError::internal_error("EmailServiceAssertions service_name is empty"));
305 }
306
307 if method_name.is_empty() {
309 return Err(CleanroomError::internal_error("EmailServiceAssertions method_name is empty"));
310 }
311
312 Ok(())
314 }
315
316 pub async fn should_have_sent_email(&self, _to: &str, _subject: &str) -> Result<()> {
318 self.self_check("should_have_sent_email").await?;
320
321 let context = get_assertion_context()
323 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
324
325 let email_key = format!("email_{}_{}", _to, _subject.replace(" ", "_"));
327 if let Some(_email_data) = context.get_test_data(&email_key) {
328 Ok(())
329 } else {
330 Err(CleanroomError::validation_error(&format!(
331 "Email to '{}' with subject '{}' was not sent", _to, _subject
332 )))
333 }
334 }
335
336 pub async fn should_have_sent_count(&self, _expected_count: i64) -> Result<()> {
338 self.self_check("should_have_sent_count").await?;
340
341 let context = get_assertion_context()
343 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
344
345 let mut email_count = 0;
347 for (key, _value) in &context.test_data {
348 if key.starts_with("email_") {
349 email_count += 1;
350 }
351 }
352
353 if email_count == _expected_count {
354 Ok(())
355 } else {
356 Err(CleanroomError::validation_error(&format!(
357 "Expected {} emails sent, found {}", _expected_count, email_count
358 )))
359 }
360 }
361
362 pub async fn should_have_sent_welcome_email(&self, _user_email: &str) -> Result<()> {
364 self.self_check("should_have_sent_welcome_email").await?;
366
367 let context = get_assertion_context()
369 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
370
371 let welcome_key = format!("welcome_email_{}", _user_email);
373 if let Some(_welcome_data) = context.get_test_data(&welcome_key) {
374 Ok(())
375 } else {
376 Err(CleanroomError::validation_error(&format!(
377 "Welcome email to '{}' was not sent", _user_email
378 )))
379 }
380 }
381}
382
383#[allow(dead_code)]
385pub struct UserAssertions {
386 user_id: i64,
387 email: String,
388}
389
390impl UserAssertions {
391 pub fn new(user_id: i64, email: String) -> Self {
393 Self { user_id, email }
394 }
395
396 async fn self_check(&self, method_name: &str) -> Result<()> {
398 if self.user_id <= 0 {
400 return Err(CleanroomError::internal_error("UserAssertions user_id is invalid"));
401 }
402
403 if self.email.is_empty() {
404 return Err(CleanroomError::internal_error("UserAssertions email is empty"));
405 }
406
407 if method_name.is_empty() {
409 return Err(CleanroomError::internal_error("UserAssertions method_name is empty"));
410 }
411
412 Ok(())
414 }
415
416 pub async fn should_exist_in_database(&self) -> Result<()> {
418 self.self_check("should_exist_in_database").await?;
420
421 let context = get_assertion_context()
423 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
424
425 if let Some(_user_data) = context.get_test_data(&format!("user_{}", self.user_id)) {
427 Ok(())
428 } else {
429 Err(CleanroomError::validation_error(&format!(
430 "User {} does not exist in database", self.user_id
431 )))
432 }
433 }
434
435 pub async fn should_have_role(&self, _expected_role: &str) -> Result<()> {
437 self.self_check("should_have_role").await?;
439
440 let context = get_assertion_context()
442 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
443
444 if let Some(user_data) = context.get_test_data(&format!("user_{}", self.user_id)) {
446 if let Some(role) = user_data.get("role").and_then(|r| r.as_str()) {
447 if role == _expected_role {
448 Ok(())
449 } else {
450 Err(CleanroomError::validation_error(&format!(
451 "User {} has role '{}', expected '{}'", self.user_id, role, _expected_role
452 )))
453 }
454 } else {
455 Err(CleanroomError::validation_error(&format!(
456 "User {} exists but has no role information", self.user_id
457 )))
458 }
459 } else {
460 Err(CleanroomError::validation_error(&format!(
461 "User {} does not exist in database", self.user_id
462 )))
463 }
464 }
465
466 pub async fn should_receive_email(&self) -> Result<()> {
468 self.self_check("should_receive_email").await?;
470
471 let context = get_assertion_context()
473 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
474
475 let email_key = format!("user_email_{}", self.user_id);
477 if let Some(_email_data) = context.get_test_data(&email_key) {
478 Ok(())
479 } else {
480 Err(CleanroomError::validation_error(&format!(
481 "User {} did not receive any email", self.user_id
482 )))
483 }
484 }
485
486 pub async fn should_have_session(&self) -> Result<()> {
488 self.self_check("should_have_session").await?;
490
491 let context = get_assertion_context()
493 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
494
495 let session_key = format!("user_session_{}", self.user_id);
497 if let Some(_session_data) = context.get_test_data(&session_key) {
498 Ok(())
499 } else {
500 Err(CleanroomError::validation_error(&format!(
501 "User {} does not have a session in cache", self.user_id
502 )))
503 }
504 }
505}
506
507thread_local! {
508 static ASSERTION_CONTEXT: std::cell::RefCell<Option<AssertionContext>> = std::cell::RefCell::new(None);
510}
511
512pub fn set_assertion_context(context: AssertionContext) {
514 ASSERTION_CONTEXT.with(|ctx| {
515 *ctx.borrow_mut() = Some(context);
516 });
517}
518
519pub fn get_assertion_context() -> Option<AssertionContext> {
521 ASSERTION_CONTEXT.with(|ctx| {
522 ctx.borrow().as_ref().map(|c| AssertionContext {
523 services: c.services.clone(),
524 test_data: c.test_data.clone(),
525 })
526 })
527}
528
529pub async fn database() -> Result<DatabaseAssertions> {
531 Ok(DatabaseAssertions::new("database"))
532}
533
534pub async fn cache() -> Result<CacheAssertions> {
536 Ok(CacheAssertions::new("cache"))
537}
538
539pub async fn email_service() -> Result<EmailServiceAssertions> {
541 Ok(EmailServiceAssertions::new("email_service"))
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 #[tokio::test]
549 async fn test_database_assertions() {
550 let assertions = DatabaseAssertions::new("database");
551
552 let result = assertions.should_have_user(123).await;
554 assert!(result.is_err());
555
556 let result = assertions.should_have_user_count(1).await;
557 assert!(result.is_err());
558
559 let result = assertions.should_have_table("users").await;
560 assert!(result.is_err());
561 }
562
563 #[tokio::test]
564 async fn test_cache_assertions() {
565 let assertions = CacheAssertions::new("cache");
566
567 let result = assertions.should_have_key("user:123").await;
568 assert!(result.is_err());
569
570 let result = assertions.should_have_value("user:123", "expected_value").await;
571 assert!(result.is_err());
572
573 let result = assertions.should_have_user_session(123).await;
574 assert!(result.is_err());
575 }
576
577 #[tokio::test]
578 async fn test_email_assertions() {
579 let assertions = EmailServiceAssertions::new("email_service");
580
581 let result = assertions.should_have_sent_email("jane@example.com", "Welcome").await;
582 assert!(result.is_err());
583
584 let result = assertions.should_have_sent_count(1).await;
585 assert!(result.is_err());
586
587 let result = assertions.should_have_sent_welcome_email("jane@example.com").await;
588 assert!(result.is_err());
589 }
590
591 #[tokio::test]
592 async fn test_user_assertions() {
593 let user = UserAssertions::new(123, "jane@example.com".to_string());
594
595 let result = user.should_exist_in_database().await;
596 assert!(result.is_err());
597
598 let result = user.should_have_role("user").await;
599 assert!(result.is_err());
600
601 let result = user.should_receive_email().await;
602 assert!(result.is_err());
603
604 let result = user.should_have_session().await;
605 assert!(result.is_err());
606 }
607
608 #[tokio::test]
609 async fn test_simple_assertions() {
610 let db = database().await.unwrap();
612 assert_eq!(db.service_name, "database");
613
614 let cache = cache().await.unwrap();
615 assert_eq!(cache.service_name, "cache");
616
617 let email = email_service().await.unwrap();
618 assert_eq!(email.service_name, "email_service");
619 }
620
621 #[tokio::test]
622 async fn test_assertion_framework_self_check() {
623 let mut context = AssertionContext::new();
625
626 context.add_test_data("user_123".to_string(), serde_json::json!({"role": "admin"}));
628 context.add_test_data("cache_key_token".to_string(), serde_json::json!("abc123"));
629 context.add_test_data("user_session_123".to_string(), serde_json::json!({"active": true}));
630
631 set_assertion_context(context);
632
633 let db = DatabaseAssertions::new("test_db");
635 let cache = CacheAssertions::new("test_cache");
636 let user = UserAssertions::new(123, "jane@example.com".to_string());
637
638 assert!(db.should_have_user(123).await.is_ok());
640 assert!(cache.should_have_key("token").await.is_ok());
641 assert!(user.should_have_role("admin").await.is_ok());
642
643 assert!(db.should_have_user(999).await.is_err());
645 assert!(cache.should_have_key("missing").await.is_err());
646 }
647}