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 Default for AssertionContext {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl AssertionContext {
40 pub fn new() -> Self {
42 Self {
43 services: HashMap::new(),
44 test_data: HashMap::new(),
45 }
46 }
47
48 pub fn add_service(&mut self, name: String, state: ServiceState) {
50 self.services.insert(name, state);
51 }
52
53 pub fn add_test_data(&mut self, key: String, value: serde_json::Value) {
55 self.test_data.insert(key, value);
56 }
57
58 pub fn get_service(&self, name: &str) -> Option<&ServiceState> {
60 self.services.get(name)
61 }
62
63 pub fn get_test_data(&self, key: &str) -> Option<&serde_json::Value> {
65 self.test_data.get(key)
66 }
67}
68
69#[allow(dead_code)]
71pub struct DatabaseAssertions {
72 service_name: String,
73}
74
75impl DatabaseAssertions {
76 pub fn new(service_name: &str) -> Self {
78 Self {
79 service_name: service_name.to_string(),
80 }
81 }
82
83 async fn self_check(&self, method_name: &str) -> Result<()> {
85 if self.service_name.is_empty() {
87 return Err(CleanroomError::internal_error(
88 "DatabaseAssertions service_name is empty",
89 ));
90 }
91
92 if method_name.is_empty() {
94 return Err(CleanroomError::internal_error(
95 "DatabaseAssertions method_name is empty",
96 ));
97 }
98
99 Ok(())
101 }
102
103 pub async fn should_have_user(&self, _user_id: i64) -> Result<()> {
105 self.self_check("should_have_user").await?;
107
108 let context = get_assertion_context()
110 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
111
112 if let Some(user_data) = context.get_test_data(&format!("user_{}", _user_id)) {
114 if user_data.is_object() {
115 return Ok(());
116 }
117 }
118
119 Err(CleanroomError::validation_error(format!(
120 "User {} not found in database",
121 _user_id
122 )))
123 }
124
125 pub async fn should_have_user_count(&self, _expected_count: i64) -> Result<()> {
127 self.self_check("should_have_user_count").await?;
129
130 let context = get_assertion_context()
132 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
133
134 let mut user_count = 0;
136 for key in context.test_data.keys() {
137 if key.starts_with("user_") && !key.contains("session") && !key.contains("email") {
138 user_count += 1;
139 }
140 }
141
142 if user_count == _expected_count {
143 Ok(())
144 } else {
145 Err(CleanroomError::validation_error(format!(
146 "Expected {} users, found {}",
147 _expected_count, user_count
148 )))
149 }
150 }
151
152 pub async fn should_have_table(&self, _table_name: &str) -> Result<()> {
154 self.self_check("should_have_table").await?;
156
157 let context = get_assertion_context()
159 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
160
161 if let Some(_table_data) = context.get_test_data(&format!("table_{}", _table_name)) {
163 Ok(())
164 } else {
165 Err(CleanroomError::validation_error(format!(
166 "Table '{}' not found in database",
167 _table_name
168 )))
169 }
170 }
171
172 pub async fn should_have_record(
174 &self,
175 _table: &str,
176 _conditions: HashMap<String, String>,
177 ) -> Result<()> {
178 self.self_check("should_have_record").await?;
180
181 let context = get_assertion_context()
183 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
184
185 let record_key = format!(
187 "record_{}_{}",
188 _table,
189 _conditions
190 .iter()
191 .map(|(k, v)| format!("{}={}", k, v))
192 .collect::<Vec<_>>()
193 .join("&")
194 );
195
196 if let Some(_record_data) = context.get_test_data(&record_key) {
197 Ok(())
198 } else {
199 Err(CleanroomError::validation_error(format!(
200 "Record not found in table '{}' with conditions: {:?}",
201 _table, _conditions
202 )))
203 }
204 }
205}
206
207#[allow(dead_code)]
209pub struct CacheAssertions {
210 service_name: String,
211}
212
213impl CacheAssertions {
214 pub fn new(service_name: &str) -> Self {
216 Self {
217 service_name: service_name.to_string(),
218 }
219 }
220
221 async fn self_check(&self, method_name: &str) -> Result<()> {
223 if self.service_name.is_empty() {
225 return Err(CleanroomError::internal_error(
226 "CacheAssertions service_name is empty",
227 ));
228 }
229
230 if method_name.is_empty() {
232 return Err(CleanroomError::internal_error(
233 "CacheAssertions method_name is empty",
234 ));
235 }
236
237 Ok(())
239 }
240
241 pub async fn should_have_key(&self, _key: &str) -> Result<()> {
243 self.self_check("should_have_key").await?;
245
246 let context = get_assertion_context()
248 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
249
250 if let Some(_key_data) = context.get_test_data(&format!("cache_key_{}", _key)) {
252 Ok(())
253 } else {
254 Err(CleanroomError::validation_error(format!(
255 "Key '{}' not found in cache",
256 _key
257 )))
258 }
259 }
260
261 pub async fn should_have_value(&self, _key: &str, _expected_value: &str) -> Result<()> {
263 self.self_check("should_have_value").await?;
265
266 let context = get_assertion_context()
268 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
269
270 if let Some(key_data) = context.get_test_data(&format!("cache_key_{}", _key)) {
272 if let Some(actual_value) = key_data.as_str() {
273 if actual_value == _expected_value {
274 Ok(())
275 } else {
276 Err(CleanroomError::validation_error(format!(
277 "Key '{}' has value '{}', expected '{}'",
278 _key, actual_value, _expected_value
279 )))
280 }
281 } else {
282 Err(CleanroomError::validation_error(format!(
283 "Key '{}' exists but value is not a string",
284 _key
285 )))
286 }
287 } else {
288 Err(CleanroomError::validation_error(format!(
289 "Key '{}' not found in cache",
290 _key
291 )))
292 }
293 }
294
295 pub async fn should_have_user_session(&self, _user_id: i64) -> Result<()> {
297 self.self_check("should_have_user_session").await?;
299
300 let context = get_assertion_context()
302 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
303
304 if let Some(_session_data) = context.get_test_data(&format!("user_session_{}", _user_id)) {
306 Ok(())
307 } else {
308 Err(CleanroomError::validation_error(format!(
309 "User session for user {} not found in cache",
310 _user_id
311 )))
312 }
313 }
314}
315
316#[allow(dead_code)]
318pub struct EmailServiceAssertions {
319 service_name: String,
320}
321
322impl EmailServiceAssertions {
323 pub fn new(service_name: &str) -> Self {
325 Self {
326 service_name: service_name.to_string(),
327 }
328 }
329
330 async fn self_check(&self, method_name: &str) -> Result<()> {
332 if self.service_name.is_empty() {
334 return Err(CleanroomError::internal_error(
335 "EmailServiceAssertions service_name is empty",
336 ));
337 }
338
339 if method_name.is_empty() {
341 return Err(CleanroomError::internal_error(
342 "EmailServiceAssertions method_name is empty",
343 ));
344 }
345
346 Ok(())
348 }
349
350 pub async fn should_have_sent_email(&self, _to: &str, _subject: &str) -> Result<()> {
352 self.self_check("should_have_sent_email").await?;
354
355 let context = get_assertion_context()
357 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
358
359 let email_key = format!("email_{}_{}", _to, _subject.replace(" ", "_"));
361 if let Some(_email_data) = context.get_test_data(&email_key) {
362 Ok(())
363 } else {
364 Err(CleanroomError::validation_error(format!(
365 "Email to '{}' with subject '{}' was not sent",
366 _to, _subject
367 )))
368 }
369 }
370
371 pub async fn should_have_sent_count(&self, _expected_count: i64) -> Result<()> {
373 self.self_check("should_have_sent_count").await?;
375
376 let context = get_assertion_context()
378 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
379
380 let mut email_count = 0;
382 for key in context.test_data.keys() {
383 if key.starts_with("email_") {
384 email_count += 1;
385 }
386 }
387
388 if email_count == _expected_count {
389 Ok(())
390 } else {
391 Err(CleanroomError::validation_error(format!(
392 "Expected {} emails sent, found {}",
393 _expected_count, email_count
394 )))
395 }
396 }
397
398 pub async fn should_have_sent_welcome_email(&self, _user_email: &str) -> Result<()> {
400 self.self_check("should_have_sent_welcome_email").await?;
402
403 let context = get_assertion_context()
405 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
406
407 let welcome_key = format!("welcome_email_{}", _user_email);
409 if let Some(_welcome_data) = context.get_test_data(&welcome_key) {
410 Ok(())
411 } else {
412 Err(CleanroomError::validation_error(format!(
413 "Welcome email to '{}' was not sent",
414 _user_email
415 )))
416 }
417 }
418}
419
420#[allow(dead_code)]
422pub struct UserAssertions {
423 user_id: i64,
424 email: String,
425}
426
427impl UserAssertions {
428 pub fn new(user_id: i64, email: String) -> Self {
430 Self { user_id, email }
431 }
432
433 async fn self_check(&self, method_name: &str) -> Result<()> {
435 if self.user_id <= 0 {
437 return Err(CleanroomError::internal_error(
438 "UserAssertions user_id is invalid",
439 ));
440 }
441
442 if self.email.is_empty() {
443 return Err(CleanroomError::internal_error(
444 "UserAssertions email is empty",
445 ));
446 }
447
448 if method_name.is_empty() {
450 return Err(CleanroomError::internal_error(
451 "UserAssertions method_name is empty",
452 ));
453 }
454
455 Ok(())
457 }
458
459 pub async fn should_exist_in_database(&self) -> Result<()> {
461 self.self_check("should_exist_in_database").await?;
463
464 let context = get_assertion_context()
466 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
467
468 if let Some(_user_data) = context.get_test_data(&format!("user_{}", self.user_id)) {
470 Ok(())
471 } else {
472 Err(CleanroomError::validation_error(format!(
473 "User {} does not exist in database",
474 self.user_id
475 )))
476 }
477 }
478
479 pub async fn should_have_role(&self, _expected_role: &str) -> Result<()> {
481 self.self_check("should_have_role").await?;
483
484 let context = get_assertion_context()
486 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
487
488 if let Some(user_data) = context.get_test_data(&format!("user_{}", self.user_id)) {
490 if let Some(role) = user_data.get("role").and_then(|r| r.as_str()) {
491 if role == _expected_role {
492 Ok(())
493 } else {
494 Err(CleanroomError::validation_error(format!(
495 "User {} has role '{}', expected '{}'",
496 self.user_id, role, _expected_role
497 )))
498 }
499 } else {
500 Err(CleanroomError::validation_error(format!(
501 "User {} exists but has no role information",
502 self.user_id
503 )))
504 }
505 } else {
506 Err(CleanroomError::validation_error(format!(
507 "User {} does not exist in database",
508 self.user_id
509 )))
510 }
511 }
512
513 pub async fn should_receive_email(&self) -> Result<()> {
515 self.self_check("should_receive_email").await?;
517
518 let context = get_assertion_context()
520 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
521
522 let email_key = format!("user_email_{}", self.user_id);
524 if let Some(_email_data) = context.get_test_data(&email_key) {
525 Ok(())
526 } else {
527 Err(CleanroomError::validation_error(format!(
528 "User {} did not receive any email",
529 self.user_id
530 )))
531 }
532 }
533
534 pub async fn should_have_session(&self) -> Result<()> {
536 self.self_check("should_have_session").await?;
538
539 let context = get_assertion_context()
541 .ok_or_else(|| CleanroomError::internal_error("No assertion context available"))?;
542
543 let session_key = format!("user_session_{}", self.user_id);
545 if let Some(_session_data) = context.get_test_data(&session_key) {
546 Ok(())
547 } else {
548 Err(CleanroomError::validation_error(format!(
549 "User {} does not have a session in cache",
550 self.user_id
551 )))
552 }
553 }
554}
555
556thread_local! {
557 static ASSERTION_CONTEXT: std::cell::RefCell<Option<AssertionContext>> = const { std::cell::RefCell::new(None) };
559}
560
561pub fn set_assertion_context(context: AssertionContext) {
563 ASSERTION_CONTEXT.with(|ctx| {
564 *ctx.borrow_mut() = Some(context);
565 });
566}
567
568pub fn get_assertion_context() -> Option<AssertionContext> {
570 ASSERTION_CONTEXT.with(|ctx| {
571 ctx.borrow().as_ref().map(|c| AssertionContext {
572 services: c.services.clone(),
573 test_data: c.test_data.clone(),
574 })
575 })
576}
577
578pub async fn database() -> Result<DatabaseAssertions> {
580 Ok(DatabaseAssertions::new("database"))
581}
582
583pub async fn cache() -> Result<CacheAssertions> {
585 Ok(CacheAssertions::new("cache"))
586}
587
588pub async fn email_service() -> Result<EmailServiceAssertions> {
590 Ok(EmailServiceAssertions::new("email_service"))
591}