1#![warn(missing_docs)]
39
40pub mod assertions;
42pub mod client;
44pub mod debug;
46pub mod factory;
48pub mod fixtures;
50pub mod http;
52pub mod logging;
54#[cfg(feature = "messages")]
56pub mod messages;
57pub mod mock;
59pub mod resource;
61pub mod response;
63pub mod server;
65pub mod testcase;
67pub mod views;
69#[cfg(feature = "viewsets")]
71pub mod viewsets;
72
73#[cfg(feature = "testcontainers")]
75pub mod containers;
76
77pub mod auth;
79pub mod server_fn;
81pub mod websocket;
83
84#[cfg(feature = "testcontainers")]
86pub use testcontainers;
87
88#[cfg(feature = "testcontainers")]
89pub use testcontainers_modules;
90
91#[cfg(feature = "static")]
92pub mod static_files;
93
94pub use reinhardt_di::{DependencyScope, DiError};
98pub use reinhardt_testkit_macros::with_di_overrides;
99
100#[doc(hidden)]
102pub use paste;
103#[doc(hidden)]
104pub use reinhardt_db::orm::inspection;
105#[doc(hidden)]
106pub use reinhardt_db::orm::manager::Manager;
107#[doc(hidden)]
108pub use reinhardt_db::orm::relationship;
109#[doc(hidden)]
110pub use reinhardt_db::orm::{FieldSelector, Model};
111
112pub use assertions::*;
113pub use client::{APIClient, APIClientBuilder, ClientError, HttpVersion};
114pub use debug::{DebugEntry, DebugPanel, DebugToolbar, SqlQuery, TimingInfo};
115pub use factory::{APIRequestFactory, RequestBuilder};
116pub use fixtures::{
117 Factory, FactoryBuilder, FixtureError, FixtureLoader, FixtureResult, api_client_from_url,
118 random_test_key, test_config_value, test_server_guard,
119};
120
121pub use reinhardt_urls::routers::ServerRouter;
123
124pub use reinhardt_urls;
126
127#[cfg(feature = "testcontainers")]
128pub use fixtures::{postgres_container, redis_container};
129pub use http::{
130 assert_has_header, assert_header_contains, assert_header_equals, assert_no_header,
131 assert_status, create_insecure_request, create_request, create_response_with_headers,
132 create_response_with_status, create_secure_request, create_test_request, create_test_response,
133 extract_json, get_header, has_header, header_contains, header_equals,
134};
135pub use logging::init_test_logging;
136#[cfg(feature = "messages")]
137pub use messages::{
138 MessagesTestMixin, assert_message_count, assert_message_exists, assert_message_level,
139 assert_message_tags, assert_messages,
140};
141pub use mock::{CallRecord, MockFunction, SimpleHandler, Spy};
142pub use resource::{
143 AsyncTeardownGuard, AsyncTestResource, SuiteGuard, SuiteResource, TeardownGuard, TestResource,
144 acquire_suite,
145};
146pub use response::{ResponseExt, TestResponse};
147pub use server::{
148 BodyEchoHandler, DelayedHandler, EchoPathHandler, LargeResponseHandler, MethodEchoHandler,
149 RouterHandler, StatusCodeHandler, shutdown_test_server, spawn_test_server,
150};
151pub use testcase::APITestCase;
152pub use views::{
153 ApiTestModel, ErrorKind, ErrorTestView, SimpleTestView, TestModel, create_api_test_objects,
154 create_json_request, create_large_test_objects, create_request as create_view_request,
155 create_request_with_headers, create_request_with_path_params, create_test_objects,
156};
157#[cfg(feature = "viewsets")]
158pub use viewsets::{SimpleViewSet, TestViewSet};
159
160#[cfg(feature = "testcontainers")]
161pub use containers::{
162 MailpitContainer, MySqlContainer, PostgresContainer, RabbitMQContainer, RedisContainer,
163 TestDatabase, with_mailpit, with_mysql, with_postgres, with_rabbitmq, with_redis,
164};
165
166#[cfg(feature = "static")]
167pub use static_files::*;
168
169pub use websocket::WebSocketTestClient;
170
171pub mod prelude {
173 pub use super::assertions::*;
174 pub use super::client::APIClient;
175 pub use super::debug::DebugToolbar;
176 pub use super::factory::APIRequestFactory;
177 pub use super::fixtures::{
178 Factory, FactoryBuilder, FixtureLoader, api_client_from_url, random_test_key,
179 test_config_value,
180 };
181
182 #[cfg(feature = "testcontainers")]
183 pub use super::fixtures::{postgres_container, redis_container};
184 pub use super::http::{
185 assert_has_header, assert_header_contains, assert_header_equals, assert_no_header,
186 assert_status, create_insecure_request, create_request, create_response_with_headers,
187 create_response_with_status, create_secure_request, create_test_request,
188 create_test_response, extract_json, get_header, has_header, header_contains, header_equals,
189 };
190 pub use super::logging::init_test_logging;
191 #[cfg(feature = "messages")]
192 pub use super::messages::{
193 MessagesTestMixin, assert_message_count, assert_message_exists, assert_messages,
194 };
195 pub use super::mock::{MockFunction, SimpleHandler, Spy};
196 pub use super::poll_until;
197 pub use super::resource::{
198 AsyncTeardownGuard, AsyncTestResource, SuiteGuard, SuiteResource, TeardownGuard,
199 TestResource, acquire_suite,
200 };
201 pub use super::response::TestResponse;
202 pub use super::server::{
203 BodyEchoHandler, DelayedHandler, EchoPathHandler, LargeResponseHandler, MethodEchoHandler,
204 RouterHandler, StatusCodeHandler, shutdown_test_server, spawn_test_server,
205 };
206 #[cfg(feature = "testcontainers")]
207 pub use super::testcase::TransactionHandle;
208 pub use super::testcase::{APITestCase, TeardownError};
209 pub use super::views::{
210 ApiTestModel, ErrorTestView, SimpleTestView, TestModel, create_api_test_objects,
211 create_test_objects,
212 };
213 #[cfg(feature = "viewsets")]
214 pub use super::viewsets::{SimpleViewSet, TestViewSet};
215
216 #[cfg(feature = "testcontainers")]
217 pub use super::containers::{
218 MySqlContainer, PostgresContainer, RedisContainer, TestDatabase, with_mysql, with_postgres,
219 with_redis,
220 };
221
222 #[cfg(feature = "static")]
223 pub use super::static_files::*;
224}
225
226pub async fn poll_until<F, Fut>(
262 timeout: std::time::Duration,
263 interval: std::time::Duration,
264 mut condition: F,
265) -> Result<(), String>
266where
267 F: FnMut() -> Fut,
268 Fut: std::future::Future<Output = bool>,
269{
270 let start = std::time::Instant::now();
271 while start.elapsed() < timeout {
272 if condition().await {
273 return Ok(());
274 }
275 tokio::time::sleep(interval).await;
276 }
277 Err(format!("Timeout after {:?} waiting for condition", timeout))
278}
279
280#[macro_export]
348macro_rules! impl_test_model {
349 (
351 $model:ident,
352 $pk:ty,
353 $table:expr,
354 $app:expr,
355 relationships: [
356 $(($rel_type:ident, $rel_name:expr, $related:expr, $fk:expr, $back_pop:expr)),* $(,)?
357 ],
358 many_to_many: [
359 $(($m2m_name:expr, $m2m_related:expr, $m2m_through:expr, $m2m_source:expr, $m2m_target:expr)),* $(,)?
360 ]
361 ) => {
362 $crate::paste::paste! {
363 #[derive(Debug, Clone)]
365 pub struct [<$model Fields>];
366
367 impl $crate::FieldSelector for [<$model Fields>] {
368 fn with_alias(self, _alias: &str) -> Self {
369 self
370 }
371 }
372
373 impl $crate::Model for $model {
374 type PrimaryKey = $pk;
375 type Fields = [<$model Fields>];
376 type Objects = $crate::Manager<Self>;
377
378 fn table_name() -> &'static str {
379 $table
380 }
381
382 fn app_label() -> &'static str {
383 $app
384 }
385
386 fn primary_key(&self) -> Option<Self::PrimaryKey> {
387 self.id
388 }
389
390 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
391 self.id = Some(value);
392 }
393
394 fn primary_key_field() -> &'static str {
395 "id"
396 }
397
398 fn new_fields() -> Self::Fields {
399 [<$model Fields>]
400 }
401
402 fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
403 let mut relations = vec![
405 $(
406 $crate::inspection::RelationInfo {
407 name: $rel_name.to_string(),
408 relationship_type: $crate::relationship::RelationshipType::$rel_type,
409 related_model: $related.to_string(),
410 foreign_key: Some($fk.to_string()),
411 back_populates: Some($back_pop.to_string()),
412 through_table: None,
413 source_field: None,
414 target_field: None,
415 }
416 ),*
417 ];
418
419 relations.extend(vec![
421 $(
422 $crate::inspection::RelationInfo {
423 name: $m2m_name.to_string(),
424 relationship_type: $crate::relationship::RelationshipType::ManyToMany,
425 related_model: $m2m_related.to_string(),
426 foreign_key: None,
427 back_populates: None,
428 through_table: Some($m2m_through.to_string()),
429 source_field: Some($m2m_source.to_string()),
430 target_field: Some($m2m_target.to_string()),
431 }
432 ),*
433 ]);
434
435 relations
436 }
437 }
438 }
439 };
440
441 (
443 $model:ident,
444 $pk:ty,
445 $table:expr,
446 $app:expr,
447 relationships: [
448 $(($rel_type:ident, $rel_name:expr, $related:expr, $fk:expr, $back_pop:expr)),* $(,)?
449 ]
450 ) => {
451 $crate::paste::paste! {
452 #[derive(Debug, Clone)]
454 pub struct [<$model Fields>];
455
456 impl $crate::FieldSelector for [<$model Fields>] {
457 fn with_alias(self, _alias: &str) -> Self {
458 self
459 }
460 }
461
462 impl $crate::Model for $model {
463 type PrimaryKey = $pk;
464 type Fields = [<$model Fields>];
465 type Objects = $crate::Manager<Self>;
466
467 fn table_name() -> &'static str {
468 $table
469 }
470
471 fn app_label() -> &'static str {
472 $app
473 }
474
475 fn primary_key(&self) -> Option<Self::PrimaryKey> {
476 self.id
477 }
478
479 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
480 self.id = Some(value);
481 }
482
483 fn primary_key_field() -> &'static str {
484 "id"
485 }
486
487 fn new_fields() -> Self::Fields {
488 [<$model Fields>]
489 }
490
491 fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
492 vec![
493 $(
494 $crate::inspection::RelationInfo {
495 name: $rel_name.to_string(),
496 relationship_type: $crate::relationship::RelationshipType::$rel_type,
497 related_model: $related.to_string(),
498 foreign_key: Some($fk.to_string()),
499 back_populates: Some($back_pop.to_string()),
500 through_table: None,
501 source_field: None,
502 target_field: None,
503 }
504 ),*
505 ]
506 }
507 }
508 }
509 };
510
511 (
513 $model:ident,
514 $pk:ty,
515 $table:expr,
516 $app:expr,
517 many_to_many: [
518 $(($rel_name:expr, $related:expr, $through:expr, $source:expr, $target:expr)),* $(,)?
519 ]
520 ) => {
521 $crate::paste::paste! {
522 #[derive(Debug, Clone)]
524 pub struct [<$model Fields>];
525
526 impl $crate::FieldSelector for [<$model Fields>] {
527 fn with_alias(self, _alias: &str) -> Self {
528 self
529 }
530 }
531
532 impl $crate::Model for $model {
533 type PrimaryKey = $pk;
534 type Fields = [<$model Fields>];
535 type Objects = $crate::Manager<Self>;
536
537 fn table_name() -> &'static str {
538 $table
539 }
540
541 fn app_label() -> &'static str {
542 $app
543 }
544
545 fn primary_key(&self) -> Option<Self::PrimaryKey> {
546 self.id
547 }
548
549 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
550 self.id = Some(value);
551 }
552
553 fn primary_key_field() -> &'static str {
554 "id"
555 }
556
557 fn new_fields() -> Self::Fields {
558 [<$model Fields>]
559 }
560
561 fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
562 vec![
563 $(
564 $crate::inspection::RelationInfo {
565 name: $rel_name.to_string(),
566 relationship_type: $crate::relationship::RelationshipType::ManyToMany,
567 related_model: $related.to_string(),
568 foreign_key: None,
569 back_populates: None,
570 through_table: Some($through.to_string()),
571 source_field: Some($source.to_string()),
572 target_field: Some($target.to_string()),
573 }
574 ),*
575 ]
576 }
577 }
578 }
579 };
580
581 ($model:ident, $pk:ty, $table:expr, $app:expr) => {
583 $crate::paste::paste! {
584 #[derive(Debug, Clone)]
586 pub struct [<$model Fields>];
587
588 impl $crate::FieldSelector for [<$model Fields>] {
589 fn with_alias(self, _alias: &str) -> Self {
590 self
591 }
592 }
593
594 impl $crate::Model for $model {
595 type PrimaryKey = $pk;
596 type Fields = [<$model Fields>];
597 type Objects = $crate::Manager<Self>;
598
599 fn table_name() -> &'static str {
600 $table
601 }
602
603 fn app_label() -> &'static str {
604 $app
605 }
606
607 fn primary_key(&self) -> Option<Self::PrimaryKey> {
608 self.id
609 }
610
611 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
612 self.id = Some(value);
613 }
614
615 fn primary_key_field() -> &'static str {
616 "id"
617 }
618
619 fn new_fields() -> Self::Fields {
620 [<$model Fields>]
621 }
622 }
623 }
624 };
625
626 ($model:ident, $pk:ty, $table:expr) => {
628 $crate::impl_test_model!($model, $pk, $table, "default");
629 };
630
631 ($model:ident, $pk:ty, $table:expr, $app:expr, non_option_pk) => {
635 $crate::paste::paste! {
636 #[derive(Debug, Clone)]
638 pub struct [<$model Fields>];
639
640 impl $crate::FieldSelector for [<$model Fields>] {
641 fn with_alias(self, _alias: &str) -> Self {
642 self
643 }
644 }
645
646 impl $crate::Model for $model {
647 type PrimaryKey = $pk;
648 type Fields = [<$model Fields>];
649 type Objects = $crate::Manager<Self>;
650
651 fn table_name() -> &'static str {
652 $table
653 }
654
655 fn app_label() -> &'static str {
656 $app
657 }
658
659 fn primary_key(&self) -> Option<Self::PrimaryKey> {
660 Some(self.id)
661 }
662
663 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
664 self.id = value;
665 }
666
667 fn primary_key_field() -> &'static str {
668 "id"
669 }
670
671 fn new_fields() -> Self::Fields {
672 [<$model Fields>]
673 }
674 }
675 }
676 };
677
678 ($model:ident, $pk:ty, $table:expr, non_option_pk) => {
680 $crate::impl_test_model!($model, $pk, $table, "default", non_option_pk);
681 };
682}