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