1pub mod assertions;
133pub mod client;
134pub mod debug;
135pub mod factory;
136pub mod fixtures;
137pub mod http;
138pub mod logging;
139pub mod messages;
140pub mod mock;
141pub mod resource;
142pub mod response;
143pub mod server;
144pub mod testcase;
145pub mod views;
146pub mod viewsets;
147
148#[cfg(feature = "testcontainers")]
149pub mod containers;
150
151pub mod websocket;
152
153#[cfg(feature = "testcontainers")]
155pub use testcontainers;
156
157#[cfg(feature = "testcontainers")]
158pub use testcontainers_modules;
159
160#[cfg(feature = "static")]
161pub mod static_files;
162
163#[cfg(feature = "wasm")]
164pub mod wasm;
165
166#[cfg(feature = "server-fn-test")]
167pub mod server_fn;
168
169#[doc(hidden)]
171pub use paste;
172#[doc(hidden)]
173pub use reinhardt_db::orm::inspection;
174#[doc(hidden)]
175pub use reinhardt_db::orm::relationship;
176#[doc(hidden)]
177pub use reinhardt_db::orm::{FieldSelector, Model};
178
179pub use assertions::*;
180pub use client::{APIClient, APIClientBuilder, ClientError, HttpVersion};
181pub use debug::{DebugEntry, DebugPanel, DebugToolbar, SqlQuery, TimingInfo};
182pub use factory::{APIRequestFactory, RequestBuilder};
183pub use fixtures::{
184 Factory, FactoryBuilder, FixtureError, FixtureLoader, FixtureResult, api_client_from_url,
185 random_test_key, test_config_value, test_server_guard,
186};
187
188pub use reinhardt_urls::routers::ServerRouter;
190
191#[cfg(feature = "testcontainers")]
192pub use fixtures::{postgres_container, redis_container};
193pub use http::{
194 assert_has_header, assert_header_contains, assert_header_equals, assert_no_header,
195 assert_status, create_insecure_request, create_request, create_response_with_headers,
196 create_response_with_status, create_secure_request, create_test_request, create_test_response,
197 extract_json, get_header, has_header, header_contains, header_equals,
198};
199pub use logging::init_test_logging;
200pub use messages::{
201 MessagesTestMixin, assert_message_count, assert_message_exists, assert_message_level,
202 assert_message_tags, assert_messages,
203};
204pub use mock::{CallRecord, MockFunction, SimpleHandler, Spy};
205pub use resource::{
206 AsyncTeardownGuard, AsyncTestResource, SuiteGuard, SuiteResource, TeardownGuard, TestResource,
207 acquire_suite,
208};
209pub use response::{ResponseExt, TestResponse};
210pub use server::{
211 BodyEchoHandler, DelayedHandler, EchoPathHandler, LargeResponseHandler, MethodEchoHandler,
212 RouterHandler, StatusCodeHandler, shutdown_test_server, spawn_test_server,
213};
214pub use testcase::APITestCase;
215pub use views::{
216 ApiTestModel, ErrorKind, ErrorTestView, SimpleTestView, TestModel, create_api_test_objects,
217 create_json_request, create_large_test_objects, create_request as create_view_request,
218 create_request_with_headers, create_request_with_path_params, create_test_objects,
219};
220pub use viewsets::{SimpleViewSet, TestViewSet};
221
222#[cfg(feature = "testcontainers")]
223pub use containers::{
224 MailpitContainer, MySqlContainer, PostgresContainer, RabbitMQContainer, RedisContainer,
225 TestDatabase, with_mailpit, with_mysql, with_postgres, with_rabbitmq, with_redis,
226};
227
228#[cfg(feature = "static")]
229pub use static_files::*;
230
231pub use websocket::WebSocketTestClient;
232
233pub mod prelude {
235 pub use super::assertions::*;
236 pub use super::client::APIClient;
237 pub use super::debug::DebugToolbar;
238 pub use super::factory::APIRequestFactory;
239 pub use super::fixtures::{
240 Factory, FactoryBuilder, FixtureLoader, api_client_from_url, random_test_key,
241 test_config_value,
242 };
243
244 #[cfg(feature = "testcontainers")]
245 pub use super::fixtures::{postgres_container, redis_container};
246 pub use super::http::{
247 assert_has_header, assert_header_contains, assert_header_equals, assert_no_header,
248 assert_status, create_insecure_request, create_request, create_response_with_headers,
249 create_response_with_status, create_secure_request, create_test_request,
250 create_test_response, extract_json, get_header, has_header, header_contains, header_equals,
251 };
252 pub use super::logging::init_test_logging;
253 pub use super::messages::{
254 MessagesTestMixin, assert_message_count, assert_message_exists, assert_messages,
255 };
256 pub use super::mock::{MockFunction, SimpleHandler, Spy};
257 pub use super::poll_until;
258 pub use super::resource::{
259 AsyncTeardownGuard, AsyncTestResource, SuiteGuard, SuiteResource, TeardownGuard,
260 TestResource, acquire_suite,
261 };
262 pub use super::response::TestResponse;
263 pub use super::server::{
264 BodyEchoHandler, DelayedHandler, EchoPathHandler, LargeResponseHandler, MethodEchoHandler,
265 RouterHandler, StatusCodeHandler, shutdown_test_server, spawn_test_server,
266 };
267 #[cfg(feature = "testcontainers")]
268 pub use super::testcase::TransactionHandle;
269 pub use super::testcase::{APITestCase, TeardownError};
270 pub use super::views::{
271 ApiTestModel, ErrorTestView, SimpleTestView, TestModel, create_api_test_objects,
272 create_test_objects,
273 };
274 pub use super::viewsets::{SimpleViewSet, TestViewSet};
275
276 #[cfg(feature = "testcontainers")]
277 pub use super::containers::{
278 MySqlContainer, PostgresContainer, RedisContainer, TestDatabase, with_mysql, with_postgres,
279 with_redis,
280 };
281
282 #[cfg(feature = "static")]
283 pub use super::static_files::*;
284}
285
286pub async fn poll_until<F, Fut>(
322 timeout: std::time::Duration,
323 interval: std::time::Duration,
324 mut condition: F,
325) -> Result<(), String>
326where
327 F: FnMut() -> Fut,
328 Fut: std::future::Future<Output = bool>,
329{
330 let start = std::time::Instant::now();
331 while start.elapsed() < timeout {
332 if condition().await {
333 return Ok(());
334 }
335 tokio::time::sleep(interval).await;
336 }
337 Err(format!("Timeout after {:?} waiting for condition", timeout))
338}
339
340#[macro_export]
426macro_rules! impl_test_model {
427 (
429 $model:ident,
430 $pk:ty,
431 $table:expr,
432 $app:expr,
433 relationships: [
434 $(($rel_type:ident, $rel_name:expr, $related:expr, $fk:expr, $back_pop:expr)),* $(,)?
435 ],
436 many_to_many: [
437 $(($m2m_name:expr, $m2m_related:expr, $m2m_through:expr, $m2m_source:expr, $m2m_target:expr)),* $(,)?
438 ]
439 ) => {
440 $crate::paste::paste! {
441 #[derive(Debug, Clone)]
442 pub struct [<$model Fields>];
443
444 impl $crate::FieldSelector for [<$model Fields>] {
445 fn with_alias(self, _alias: &str) -> Self {
446 self
447 }
448 }
449
450 impl $crate::Model for $model {
451 type PrimaryKey = $pk;
452 type Fields = [<$model Fields>];
453
454 fn table_name() -> &'static str {
455 $table
456 }
457
458 fn app_label() -> &'static str {
459 $app
460 }
461
462 fn primary_key(&self) -> Option<Self::PrimaryKey> {
463 self.id
464 }
465
466 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
467 self.id = Some(value);
468 }
469
470 fn primary_key_field() -> &'static str {
471 "id"
472 }
473
474 fn new_fields() -> Self::Fields {
475 [<$model Fields>]
476 }
477
478 fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
479 let mut relations = vec![
481 $(
482 $crate::inspection::RelationInfo {
483 name: $rel_name.to_string(),
484 relationship_type: $crate::relationship::RelationshipType::$rel_type,
485 related_model: $related.to_string(),
486 foreign_key: Some($fk.to_string()),
487 back_populates: Some($back_pop.to_string()),
488 through_table: None,
489 source_field: None,
490 target_field: None,
491 }
492 ),*
493 ];
494
495 relations.extend(vec![
497 $(
498 $crate::inspection::RelationInfo {
499 name: $m2m_name.to_string(),
500 relationship_type: $crate::relationship::RelationshipType::ManyToMany,
501 related_model: $m2m_related.to_string(),
502 foreign_key: None,
503 back_populates: None,
504 through_table: Some($m2m_through.to_string()),
505 source_field: Some($m2m_source.to_string()),
506 target_field: Some($m2m_target.to_string()),
507 }
508 ),*
509 ]);
510
511 relations
512 }
513 }
514 }
515 };
516
517 (
519 $model:ident,
520 $pk:ty,
521 $table:expr,
522 $app:expr,
523 relationships: [
524 $(($rel_type:ident, $rel_name:expr, $related:expr, $fk:expr, $back_pop:expr)),* $(,)?
525 ]
526 ) => {
527 $crate::paste::paste! {
528 #[derive(Debug, Clone)]
529 pub struct [<$model Fields>];
530
531 impl $crate::FieldSelector for [<$model Fields>] {
532 fn with_alias(self, _alias: &str) -> Self {
533 self
534 }
535 }
536
537 impl $crate::Model for $model {
538 type PrimaryKey = $pk;
539 type Fields = [<$model Fields>];
540
541 fn table_name() -> &'static str {
542 $table
543 }
544
545 fn app_label() -> &'static str {
546 $app
547 }
548
549 fn primary_key(&self) -> Option<Self::PrimaryKey> {
550 self.id
551 }
552
553 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
554 self.id = Some(value);
555 }
556
557 fn primary_key_field() -> &'static str {
558 "id"
559 }
560
561 fn new_fields() -> Self::Fields {
562 [<$model Fields>]
563 }
564
565 fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
566 vec![
567 $(
568 $crate::inspection::RelationInfo {
569 name: $rel_name.to_string(),
570 relationship_type: $crate::relationship::RelationshipType::$rel_type,
571 related_model: $related.to_string(),
572 foreign_key: Some($fk.to_string()),
573 back_populates: Some($back_pop.to_string()),
574 through_table: None,
575 source_field: None,
576 target_field: None,
577 }
578 ),*
579 ]
580 }
581 }
582 }
583 };
584
585 (
587 $model:ident,
588 $pk:ty,
589 $table:expr,
590 $app:expr,
591 many_to_many: [
592 $(($rel_name:expr, $related:expr, $through:expr, $source:expr, $target:expr)),* $(,)?
593 ]
594 ) => {
595 $crate::paste::paste! {
596 #[derive(Debug, Clone)]
597 pub struct [<$model Fields>];
598
599 impl $crate::FieldSelector for [<$model Fields>] {
600 fn with_alias(self, _alias: &str) -> Self {
601 self
602 }
603 }
604
605 impl $crate::Model for $model {
606 type PrimaryKey = $pk;
607 type Fields = [<$model Fields>];
608
609 fn table_name() -> &'static str {
610 $table
611 }
612
613 fn app_label() -> &'static str {
614 $app
615 }
616
617 fn primary_key(&self) -> Option<Self::PrimaryKey> {
618 self.id
619 }
620
621 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
622 self.id = Some(value);
623 }
624
625 fn primary_key_field() -> &'static str {
626 "id"
627 }
628
629 fn new_fields() -> Self::Fields {
630 [<$model Fields>]
631 }
632
633 fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
634 vec![
635 $(
636 $crate::inspection::RelationInfo {
637 name: $rel_name.to_string(),
638 relationship_type: $crate::relationship::RelationshipType::ManyToMany,
639 related_model: $related.to_string(),
640 foreign_key: None,
641 back_populates: None,
642 through_table: Some($through.to_string()),
643 source_field: Some($source.to_string()),
644 target_field: Some($target.to_string()),
645 }
646 ),*
647 ]
648 }
649 }
650 }
651 };
652
653 ($model:ident, $pk:ty, $table:expr, $app:expr) => {
655 $crate::paste::paste! {
656 #[derive(Debug, Clone)]
657 pub struct [<$model Fields>];
658
659 impl $crate::FieldSelector for [<$model Fields>] {
660 fn with_alias(self, _alias: &str) -> Self {
661 self
662 }
663 }
664
665 impl $crate::Model for $model {
666 type PrimaryKey = $pk;
667 type Fields = [<$model Fields>];
668
669 fn table_name() -> &'static str {
670 $table
671 }
672
673 fn app_label() -> &'static str {
674 $app
675 }
676
677 fn primary_key(&self) -> Option<Self::PrimaryKey> {
678 self.id
679 }
680
681 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
682 self.id = Some(value);
683 }
684
685 fn primary_key_field() -> &'static str {
686 "id"
687 }
688
689 fn new_fields() -> Self::Fields {
690 [<$model Fields>]
691 }
692 }
693 }
694 };
695
696 ($model:ident, $pk:ty, $table:expr) => {
698 $crate::impl_test_model!($model, $pk, $table, "default");
699 };
700
701 ($model:ident, $pk:ty, $table:expr, $app:expr, non_option_pk) => {
705 $crate::paste::paste! {
706 #[derive(Debug, Clone)]
707 pub struct [<$model Fields>];
708
709 impl $crate::FieldSelector for [<$model Fields>] {
710 fn with_alias(self, _alias: &str) -> Self {
711 self
712 }
713 }
714
715 impl $crate::Model for $model {
716 type PrimaryKey = $pk;
717 type Fields = [<$model Fields>];
718
719 fn table_name() -> &'static str {
720 $table
721 }
722
723 fn app_label() -> &'static str {
724 $app
725 }
726
727 fn primary_key(&self) -> Option<Self::PrimaryKey> {
728 Some(self.id)
729 }
730
731 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
732 self.id = value;
733 }
734
735 fn primary_key_field() -> &'static str {
736 "id"
737 }
738
739 fn new_fields() -> Self::Fields {
740 [<$model Fields>]
741 }
742 }
743 }
744 };
745
746 ($model:ident, $pk:ty, $table:expr, non_option_pk) => {
748 $crate::impl_test_model!($model, $pk, $table, "default", non_option_pk);
749 };
750}