1use super::providers::{
6 ClockProvider, EnvProvider, FsProvider, HttpProvider, MockClock, MockEnv, MockFs, MockHttp,
7 MockRng, MockSecrets, MockUuid, RealClock, RealEnv, RealFs, RealHttp, RealRng, RealUuid,
8 RngProvider, SecretsProvider, UuidProvider,
9};
10use super::recording::EventRecorder;
11use crate::arena::{ArenaConfig, ArenaReader, ArenaWriter};
12use crate::error::Result;
13use crate::types::{ArenaOffset, NodeId, RelPtr, TraceId};
14use crate::wal::{Wal, WalConfig};
15use std::sync::Arc;
16
17pub struct TestContext {
40 trace_id: TraceId,
42 node_id: NodeId,
43 reader: ArenaReader,
44 writer: ArenaWriter,
45 wal: Arc<Wal>,
46
47 pub clock: Arc<dyn ClockProvider>,
50 pub http: Arc<dyn HttpProvider>,
52 pub rng: Arc<dyn RngProvider>,
54 pub uuid: Arc<dyn UuidProvider>,
56 pub fs: Arc<dyn FsProvider>,
58 pub env: Arc<dyn EnvProvider>,
60 pub secrets: Arc<dyn SecretsProvider>,
62
63 recorder: Option<Arc<EventRecorder>>,
65}
66
67impl TestContext {
68 pub fn trace_id(&self) -> TraceId {
70 self.trace_id
71 }
72
73 pub fn node_id(&self) -> NodeId {
75 self.node_id
76 }
77
78 pub fn read_bytes(&self, ptr: RelPtr<()>) -> Result<Vec<u8>> {
80 self.reader.read_bytes(ptr.offset(), ptr.size() as usize)
81 }
82
83 pub fn write_bytes(&self, bytes: &[u8]) -> Result<RelPtr<()>> {
85 self.writer.write_bytes(bytes)
86 }
87
88 pub fn read_raw(&self, offset: ArenaOffset, size: usize) -> Result<Vec<u8>> {
90 self.reader.read_bytes(offset, size)
91 }
92
93 pub fn write_raw(&self, bytes: &[u8]) -> Result<RelPtr<()>> {
95 self.writer.write_bytes(bytes)
96 }
97
98 pub fn log(&self, message: impl AsRef<str>) {
100 tracing::info!(
101 trace_id = %self.trace_id,
102 node_id = %self.node_id,
103 "{}",
104 message.as_ref()
105 );
106 }
107
108 pub fn warn(&self, message: impl AsRef<str>) {
110 tracing::warn!(
111 trace_id = %self.trace_id,
112 node_id = %self.node_id,
113 "{}",
114 message.as_ref()
115 );
116 }
117
118 pub fn error(&self, message: impl AsRef<str>) {
120 tracing::error!(
121 trace_id = %self.trace_id,
122 node_id = %self.node_id,
123 "{}",
124 message.as_ref()
125 );
126 }
127
128 pub fn write_position(&self) -> ArenaOffset {
130 self.writer.write_position()
131 }
132
133 pub fn wal(&self) -> &Wal {
135 &self.wal
136 }
137
138 pub fn recorder(&self) -> Option<&Arc<EventRecorder>> {
140 self.recorder.as_ref()
141 }
142
143 pub fn record(&self, event: super::recording::RecordedEvent) {
145 if let Some(recorder) = &self.recorder {
146 recorder.record(event);
147 }
148 }
149
150 pub fn now(&self) -> u64 {
154 let nanos = self.clock.now();
155 self.record(super::recording::RecordedEvent::ClockNow { nanos });
156 nanos
157 }
158
159 pub fn system_time_millis(&self) -> u64 {
161 let millis = self.clock.system_time_millis();
162 self.record(super::recording::RecordedEvent::SystemTime { millis });
163 millis
164 }
165
166 pub fn new_uuid(&self) -> uuid::Uuid {
168 let uuid = self.uuid.new_v4();
169 self.record(super::recording::RecordedEvent::UuidGenerated {
170 uuid: uuid.to_string(),
171 });
172 uuid
173 }
174
175 pub fn random_u64(&self) -> u64 {
177 let value = self.rng.next_u64();
178 self.record(super::recording::RecordedEvent::RandomU64 { value });
179 value
180 }
181
182 pub fn random_f64(&self) -> f64 {
184 let value = self.rng.next_f64();
185 self.record(super::recording::RecordedEvent::RandomF64 { value });
186 value
187 }
188
189 pub fn env_var(&self, key: &str) -> Option<String> {
191 let value = self.env.var(key);
192 self.record(super::recording::RecordedEvent::EnvRead {
193 key: key.to_string(),
194 value: value.clone(),
195 });
196 value
197 }
198
199 pub fn secret(&self, key: &str) -> Option<String> {
201 let value = self.secrets.get(key);
202 self.record(super::recording::RecordedEvent::SecretRead {
203 key: key.to_string(),
204 found: value.is_some(),
205 });
206 value
207 }
208}
209
210pub struct TestContextBuilder {
233 trace_id: Option<TraceId>,
234 node_id: Option<NodeId>,
235 arena_config: ArenaConfig,
236 wal_config: WalConfig,
237
238 clock: Option<Arc<dyn ClockProvider>>,
239 http: Option<Arc<dyn HttpProvider>>,
240 rng: Option<Arc<dyn RngProvider>>,
241 uuid: Option<Arc<dyn UuidProvider>>,
242 fs: Option<Arc<dyn FsProvider>>,
243 env: Option<Arc<dyn EnvProvider>>,
244 secrets: Option<Arc<dyn SecretsProvider>>,
245
246 recording: bool,
247}
248
249impl TestContextBuilder {
250 pub fn new() -> Self {
252 Self {
253 trace_id: None,
254 node_id: None,
255 arena_config: ArenaConfig::in_memory(),
256 wal_config: WalConfig::in_memory(),
257
258 clock: None,
259 http: None,
260 rng: None,
261 uuid: None,
262 fs: None,
263 env: None,
264 secrets: None,
265
266 recording: false,
267 }
268 }
269
270 pub fn with_trace_id(mut self, trace_id: TraceId) -> Self {
272 self.trace_id = Some(trace_id);
273 self
274 }
275
276 pub fn with_node_id(mut self, node_id: NodeId) -> Self {
278 self.node_id = Some(node_id);
279 self
280 }
281
282 pub fn with_arena_config(mut self, config: ArenaConfig) -> Self {
284 self.arena_config = config;
285 self
286 }
287
288 pub fn with_wal_config(mut self, config: WalConfig) -> Self {
290 self.wal_config = config;
291 self
292 }
293
294 pub fn with_fixed_time(mut self, iso_time: &str) -> Self {
298 self.clock = Some(Arc::new(MockClock::fixed(iso_time)));
299 self
300 }
301
302 pub fn with_mock_clock(mut self) -> Self {
304 self.clock = Some(Arc::new(MockClock::new()));
305 self
306 }
307
308 pub fn with_clock(mut self, clock: Arc<dyn ClockProvider>) -> Self {
310 self.clock = Some(clock);
311 self
312 }
313
314 pub fn with_real_clock(mut self) -> Self {
316 self.clock = Some(Arc::new(RealClock::new()));
317 self
318 }
319
320 pub fn with_mock_http(mut self, mock: MockHttp) -> Self {
324 self.http = Some(Arc::new(mock));
325 self
326 }
327
328 pub fn with_http(mut self, http: Arc<dyn HttpProvider>) -> Self {
330 self.http = Some(http);
331 self
332 }
333
334 pub fn with_real_http(mut self) -> Self {
336 self.http = Some(Arc::new(RealHttp::new()));
337 self
338 }
339
340 pub fn with_seed(mut self, seed: u64) -> Self {
344 self.rng = Some(Arc::new(MockRng::seeded(seed)));
345 self
346 }
347
348 pub fn with_rng(mut self, rng: Arc<dyn RngProvider>) -> Self {
350 self.rng = Some(rng);
351 self
352 }
353
354 pub fn with_real_rng(mut self) -> Self {
356 self.rng = Some(Arc::new(RealRng::new()));
357 self
358 }
359
360 pub fn with_sequential_uuids(mut self) -> Self {
364 self.uuid = Some(Arc::new(MockUuid::sequential()));
365 self
366 }
367
368 pub fn with_predetermined_uuids(mut self, uuids: &[&str]) -> Self {
370 self.uuid = Some(Arc::new(MockUuid::from_strings(uuids)));
371 self
372 }
373
374 pub fn with_uuid(mut self, uuid: Arc<dyn UuidProvider>) -> Self {
376 self.uuid = Some(uuid);
377 self
378 }
379
380 pub fn with_real_uuid(mut self) -> Self {
382 self.uuid = Some(Arc::new(RealUuid::new()));
383 self
384 }
385
386 pub fn with_memory_fs(mut self) -> Self {
390 self.fs = Some(Arc::new(MockFs::new()));
391 self
392 }
393
394 pub fn with_mock_fs(mut self, mock: MockFs) -> Self {
396 self.fs = Some(Arc::new(mock));
397 self
398 }
399
400 pub fn with_fs(mut self, fs: Arc<dyn FsProvider>) -> Self {
402 self.fs = Some(fs);
403 self
404 }
405
406 pub fn with_real_fs(mut self) -> Self {
408 self.fs = Some(Arc::new(RealFs::new()));
409 self
410 }
411
412 pub fn with_env_vars(mut self, vars: &[(&str, &str)]) -> Self {
416 self.env = Some(Arc::new(MockEnv::from_pairs(vars)));
417 self
418 }
419
420 pub fn with_mock_env(mut self, mock: MockEnv) -> Self {
422 self.env = Some(Arc::new(mock));
423 self
424 }
425
426 pub fn with_env(mut self, env: Arc<dyn EnvProvider>) -> Self {
428 self.env = Some(env);
429 self
430 }
431
432 pub fn with_real_env(mut self) -> Self {
434 self.env = Some(Arc::new(RealEnv::new()));
435 self
436 }
437
438 pub fn with_secrets(mut self, secrets: &[(&str, &str)]) -> Self {
442 self.secrets = Some(Arc::new(MockSecrets::from_pairs(secrets)));
443 self
444 }
445
446 pub fn with_mock_secrets(mut self, mock: MockSecrets) -> Self {
448 self.secrets = Some(Arc::new(mock));
449 self
450 }
451
452 pub fn with_secrets_provider(mut self, secrets: Arc<dyn SecretsProvider>) -> Self {
454 self.secrets = Some(secrets);
455 self
456 }
457
458 pub fn with_recording(mut self) -> Self {
462 self.recording = true;
463 self
464 }
465
466 pub fn build(self) -> Result<TestContext> {
468 let trace_id = self.trace_id.unwrap_or_else(TraceId::new);
469 let node_id = self.node_id.unwrap_or_else(|| NodeId::new(0));
470
471 let arena = crate::arena::Arena::create(trace_id, &self.arena_config)?;
473 let reader = arena.reader();
474 let writer = arena.writer();
475
476 let wal = Arc::new(Wal::open(self.wal_config.clone())?);
478
479 let clock = self.clock.unwrap_or_else(|| Arc::new(MockClock::new()));
481 let http = self.http.unwrap_or_else(|| Arc::new(MockHttp::new()));
482 let rng = self.rng.unwrap_or_else(|| Arc::new(MockRng::seeded(0)));
483 let uuid = self
484 .uuid
485 .unwrap_or_else(|| Arc::new(MockUuid::sequential()));
486 let fs = self.fs.unwrap_or_else(|| Arc::new(MockFs::new()));
487 let env = self.env.unwrap_or_else(|| Arc::new(MockEnv::new()));
488 let secrets = self.secrets.unwrap_or_else(|| Arc::new(MockSecrets::new()));
489
490 let recorder = if self.recording {
491 Some(Arc::new(EventRecorder::new()))
492 } else {
493 None
494 };
495
496 Ok(TestContext {
497 trace_id,
498 node_id,
499 reader,
500 writer,
501 wal,
502
503 clock,
504 http,
505 rng,
506 uuid,
507 fs,
508 env,
509 secrets,
510
511 recorder,
512 })
513 }
514}
515
516impl Default for TestContextBuilder {
517 fn default() -> Self {
518 Self::new()
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn build_default_context() {
528 let ctx = TestContextBuilder::new().build().unwrap();
529
530 assert!(ctx.clock.is_mock());
532 assert!(ctx.http.is_mock());
533 assert!(ctx.rng.is_mock());
534 assert!(ctx.uuid.is_mock());
535 assert!(ctx.fs.is_mock());
536 assert!(ctx.env.is_mock());
537 assert!(ctx.secrets.is_mock());
538 }
539
540 #[test]
541 fn build_with_fixed_time() {
542 let ctx = TestContextBuilder::new()
543 .with_fixed_time("2024-01-15T10:30:00Z")
544 .build()
545 .unwrap();
546
547 assert_eq!(ctx.system_time_millis(), 1705314600000);
549 }
550
551 #[test]
552 fn build_with_seed() {
553 let ctx1 = TestContextBuilder::new().with_seed(42).build().unwrap();
554 let ctx2 = TestContextBuilder::new().with_seed(42).build().unwrap();
555
556 let v1 = ctx1.random_u64();
557 let v2 = ctx2.random_u64();
558
559 assert_eq!(v1, v2);
560 }
561
562 #[test]
563 fn build_with_sequential_uuids() {
564 let ctx = TestContextBuilder::new()
565 .with_sequential_uuids()
566 .build()
567 .unwrap();
568
569 let id1 = ctx.new_uuid();
570 let id2 = ctx.new_uuid();
571
572 assert_eq!(id1.to_string(), "00000000-0000-0000-0000-000000000001");
573 assert_eq!(id2.to_string(), "00000000-0000-0000-0000-000000000002");
574 }
575
576 #[test]
577 fn build_with_env_vars() {
578 let ctx = TestContextBuilder::new()
579 .with_env_vars(&[("FOO", "bar"), ("BAZ", "qux")])
580 .build()
581 .unwrap();
582
583 assert_eq!(ctx.env_var("FOO"), Some("bar".to_string()));
584 assert_eq!(ctx.env_var("BAZ"), Some("qux".to_string()));
585 assert_eq!(ctx.env_var("MISSING"), None);
586 }
587
588 #[test]
589 fn build_with_secrets() {
590 let ctx = TestContextBuilder::new()
591 .with_secrets(&[("API_KEY", "secret-123")])
592 .build()
593 .unwrap();
594
595 assert_eq!(ctx.secret("API_KEY"), Some("secret-123".to_string()));
596 assert_eq!(ctx.secret("MISSING"), None);
597 }
598
599 #[test]
600 fn build_with_recording() {
601 let ctx = TestContextBuilder::new().with_recording().build().unwrap();
602
603 ctx.now();
605 ctx.new_uuid();
606 ctx.random_u64();
607
608 let recorder = ctx.recorder().unwrap();
609 assert_eq!(recorder.len(), 3);
610 assert!(recorder.assert_recorded("clock_now"));
611 assert!(recorder.assert_recorded("uuid_generated"));
612 assert!(recorder.assert_recorded("random_u64"));
613 }
614
615 #[test]
616 fn arena_operations() {
617 let ctx = TestContextBuilder::new().build().unwrap();
618
619 let data = b"hello world";
620 let ptr = ctx.write_bytes(data).unwrap();
621
622 let read_data = ctx.read_bytes(ptr).unwrap();
623 assert_eq!(read_data, data);
624 }
625}