#![warn(missing_docs)]
pub mod assertions;
pub mod client;
pub mod debug;
pub mod factory;
pub mod fixtures;
pub mod http;
pub mod logging;
#[cfg(feature = "messages")]
pub mod messages;
pub mod mock;
pub mod resource;
pub mod response;
pub mod server;
pub mod testcase;
pub mod views;
#[cfg(feature = "viewsets")]
pub mod viewsets;
#[cfg(feature = "testcontainers")]
pub mod containers;
pub mod auth;
pub mod server_fn;
pub mod websocket;
#[cfg(feature = "testcontainers")]
pub use testcontainers;
#[cfg(feature = "testcontainers")]
pub use testcontainers_modules;
#[cfg(feature = "static")]
pub mod static_files;
pub use reinhardt_di::{DependencyScope, DiError};
pub use reinhardt_testkit_macros::with_di_overrides;
#[doc(hidden)]
pub use paste;
#[doc(hidden)]
pub use reinhardt_db::orm::inspection;
#[doc(hidden)]
pub use reinhardt_db::orm::relationship;
#[doc(hidden)]
pub use reinhardt_db::orm::{FieldSelector, Model};
pub use assertions::*;
pub use client::{APIClient, APIClientBuilder, ClientError, HttpVersion};
pub use debug::{DebugEntry, DebugPanel, DebugToolbar, SqlQuery, TimingInfo};
pub use factory::{APIRequestFactory, RequestBuilder};
pub use fixtures::{
Factory, FactoryBuilder, FixtureError, FixtureLoader, FixtureResult, api_client_from_url,
random_test_key, test_config_value, test_server_guard,
};
pub use reinhardt_urls::routers::ServerRouter;
pub use reinhardt_urls;
#[cfg(feature = "testcontainers")]
pub use fixtures::{postgres_container, redis_container};
pub use http::{
assert_has_header, assert_header_contains, assert_header_equals, assert_no_header,
assert_status, create_insecure_request, create_request, create_response_with_headers,
create_response_with_status, create_secure_request, create_test_request, create_test_response,
extract_json, get_header, has_header, header_contains, header_equals,
};
pub use logging::init_test_logging;
#[cfg(feature = "messages")]
pub use messages::{
MessagesTestMixin, assert_message_count, assert_message_exists, assert_message_level,
assert_message_tags, assert_messages,
};
pub use mock::{CallRecord, MockFunction, SimpleHandler, Spy};
pub use resource::{
AsyncTeardownGuard, AsyncTestResource, SuiteGuard, SuiteResource, TeardownGuard, TestResource,
acquire_suite,
};
pub use response::{ResponseExt, TestResponse};
pub use server::{
BodyEchoHandler, DelayedHandler, EchoPathHandler, LargeResponseHandler, MethodEchoHandler,
RouterHandler, StatusCodeHandler, shutdown_test_server, spawn_test_server,
};
pub use testcase::APITestCase;
pub use views::{
ApiTestModel, ErrorKind, ErrorTestView, SimpleTestView, TestModel, create_api_test_objects,
create_json_request, create_large_test_objects, create_request as create_view_request,
create_request_with_headers, create_request_with_path_params, create_test_objects,
};
#[cfg(feature = "viewsets")]
pub use viewsets::{SimpleViewSet, TestViewSet};
#[cfg(feature = "testcontainers")]
pub use containers::{
MailpitContainer, MySqlContainer, PostgresContainer, RabbitMQContainer, RedisContainer,
TestDatabase, with_mailpit, with_mysql, with_postgres, with_rabbitmq, with_redis,
};
#[cfg(feature = "static")]
pub use static_files::*;
pub use websocket::WebSocketTestClient;
pub mod prelude {
pub use super::assertions::*;
pub use super::client::APIClient;
pub use super::debug::DebugToolbar;
pub use super::factory::APIRequestFactory;
pub use super::fixtures::{
Factory, FactoryBuilder, FixtureLoader, api_client_from_url, random_test_key,
test_config_value,
};
#[cfg(feature = "testcontainers")]
pub use super::fixtures::{postgres_container, redis_container};
pub use super::http::{
assert_has_header, assert_header_contains, assert_header_equals, assert_no_header,
assert_status, create_insecure_request, create_request, create_response_with_headers,
create_response_with_status, create_secure_request, create_test_request,
create_test_response, extract_json, get_header, has_header, header_contains, header_equals,
};
pub use super::logging::init_test_logging;
#[cfg(feature = "messages")]
pub use super::messages::{
MessagesTestMixin, assert_message_count, assert_message_exists, assert_messages,
};
pub use super::mock::{MockFunction, SimpleHandler, Spy};
pub use super::poll_until;
pub use super::resource::{
AsyncTeardownGuard, AsyncTestResource, SuiteGuard, SuiteResource, TeardownGuard,
TestResource, acquire_suite,
};
pub use super::response::TestResponse;
pub use super::server::{
BodyEchoHandler, DelayedHandler, EchoPathHandler, LargeResponseHandler, MethodEchoHandler,
RouterHandler, StatusCodeHandler, shutdown_test_server, spawn_test_server,
};
#[cfg(feature = "testcontainers")]
pub use super::testcase::TransactionHandle;
pub use super::testcase::{APITestCase, TeardownError};
pub use super::views::{
ApiTestModel, ErrorTestView, SimpleTestView, TestModel, create_api_test_objects,
create_test_objects,
};
#[cfg(feature = "viewsets")]
pub use super::viewsets::{SimpleViewSet, TestViewSet};
#[cfg(feature = "testcontainers")]
pub use super::containers::{
MySqlContainer, PostgresContainer, RedisContainer, TestDatabase, with_mysql, with_postgres,
with_redis,
};
#[cfg(feature = "static")]
pub use super::static_files::*;
}
pub async fn poll_until<F, Fut>(
timeout: std::time::Duration,
interval: std::time::Duration,
mut condition: F,
) -> Result<(), String>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = bool>,
{
let start = std::time::Instant::now();
while start.elapsed() < timeout {
if condition().await {
return Ok(());
}
tokio::time::sleep(interval).await;
}
Err(format!("Timeout after {:?} waiting for condition", timeout))
}
#[macro_export]
macro_rules! impl_test_model {
(
$model:ident,
$pk:ty,
$table:expr,
$app:expr,
relationships: [
$(($rel_type:ident, $rel_name:expr, $related:expr, $fk:expr, $back_pop:expr)),* $(,)?
],
many_to_many: [
$(($m2m_name:expr, $m2m_related:expr, $m2m_through:expr, $m2m_source:expr, $m2m_target:expr)),* $(,)?
]
) => {
$crate::paste::paste! {
#[derive(Debug, Clone)]
pub struct [<$model Fields>];
impl $crate::FieldSelector for [<$model Fields>] {
fn with_alias(self, _alias: &str) -> Self {
self
}
}
impl $crate::Model for $model {
type PrimaryKey = $pk;
type Fields = [<$model Fields>];
fn table_name() -> &'static str {
$table
}
fn app_label() -> &'static str {
$app
}
fn primary_key(&self) -> Option<Self::PrimaryKey> {
self.id
}
fn set_primary_key(&mut self, value: Self::PrimaryKey) {
self.id = Some(value);
}
fn primary_key_field() -> &'static str {
"id"
}
fn new_fields() -> Self::Fields {
[<$model Fields>]
}
fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
let mut relations = vec![
$(
$crate::inspection::RelationInfo {
name: $rel_name.to_string(),
relationship_type: $crate::relationship::RelationshipType::$rel_type,
related_model: $related.to_string(),
foreign_key: Some($fk.to_string()),
back_populates: Some($back_pop.to_string()),
through_table: None,
source_field: None,
target_field: None,
}
),*
];
relations.extend(vec![
$(
$crate::inspection::RelationInfo {
name: $m2m_name.to_string(),
relationship_type: $crate::relationship::RelationshipType::ManyToMany,
related_model: $m2m_related.to_string(),
foreign_key: None,
back_populates: None,
through_table: Some($m2m_through.to_string()),
source_field: Some($m2m_source.to_string()),
target_field: Some($m2m_target.to_string()),
}
),*
]);
relations
}
}
}
};
(
$model:ident,
$pk:ty,
$table:expr,
$app:expr,
relationships: [
$(($rel_type:ident, $rel_name:expr, $related:expr, $fk:expr, $back_pop:expr)),* $(,)?
]
) => {
$crate::paste::paste! {
#[derive(Debug, Clone)]
pub struct [<$model Fields>];
impl $crate::FieldSelector for [<$model Fields>] {
fn with_alias(self, _alias: &str) -> Self {
self
}
}
impl $crate::Model for $model {
type PrimaryKey = $pk;
type Fields = [<$model Fields>];
fn table_name() -> &'static str {
$table
}
fn app_label() -> &'static str {
$app
}
fn primary_key(&self) -> Option<Self::PrimaryKey> {
self.id
}
fn set_primary_key(&mut self, value: Self::PrimaryKey) {
self.id = Some(value);
}
fn primary_key_field() -> &'static str {
"id"
}
fn new_fields() -> Self::Fields {
[<$model Fields>]
}
fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
vec![
$(
$crate::inspection::RelationInfo {
name: $rel_name.to_string(),
relationship_type: $crate::relationship::RelationshipType::$rel_type,
related_model: $related.to_string(),
foreign_key: Some($fk.to_string()),
back_populates: Some($back_pop.to_string()),
through_table: None,
source_field: None,
target_field: None,
}
),*
]
}
}
}
};
(
$model:ident,
$pk:ty,
$table:expr,
$app:expr,
many_to_many: [
$(($rel_name:expr, $related:expr, $through:expr, $source:expr, $target:expr)),* $(,)?
]
) => {
$crate::paste::paste! {
#[derive(Debug, Clone)]
pub struct [<$model Fields>];
impl $crate::FieldSelector for [<$model Fields>] {
fn with_alias(self, _alias: &str) -> Self {
self
}
}
impl $crate::Model for $model {
type PrimaryKey = $pk;
type Fields = [<$model Fields>];
fn table_name() -> &'static str {
$table
}
fn app_label() -> &'static str {
$app
}
fn primary_key(&self) -> Option<Self::PrimaryKey> {
self.id
}
fn set_primary_key(&mut self, value: Self::PrimaryKey) {
self.id = Some(value);
}
fn primary_key_field() -> &'static str {
"id"
}
fn new_fields() -> Self::Fields {
[<$model Fields>]
}
fn relationship_metadata() -> Vec<$crate::inspection::RelationInfo> {
vec![
$(
$crate::inspection::RelationInfo {
name: $rel_name.to_string(),
relationship_type: $crate::relationship::RelationshipType::ManyToMany,
related_model: $related.to_string(),
foreign_key: None,
back_populates: None,
through_table: Some($through.to_string()),
source_field: Some($source.to_string()),
target_field: Some($target.to_string()),
}
),*
]
}
}
}
};
($model:ident, $pk:ty, $table:expr, $app:expr) => {
$crate::paste::paste! {
#[derive(Debug, Clone)]
pub struct [<$model Fields>];
impl $crate::FieldSelector for [<$model Fields>] {
fn with_alias(self, _alias: &str) -> Self {
self
}
}
impl $crate::Model for $model {
type PrimaryKey = $pk;
type Fields = [<$model Fields>];
fn table_name() -> &'static str {
$table
}
fn app_label() -> &'static str {
$app
}
fn primary_key(&self) -> Option<Self::PrimaryKey> {
self.id
}
fn set_primary_key(&mut self, value: Self::PrimaryKey) {
self.id = Some(value);
}
fn primary_key_field() -> &'static str {
"id"
}
fn new_fields() -> Self::Fields {
[<$model Fields>]
}
}
}
};
($model:ident, $pk:ty, $table:expr) => {
$crate::impl_test_model!($model, $pk, $table, "default");
};
($model:ident, $pk:ty, $table:expr, $app:expr, non_option_pk) => {
$crate::paste::paste! {
#[derive(Debug, Clone)]
pub struct [<$model Fields>];
impl $crate::FieldSelector for [<$model Fields>] {
fn with_alias(self, _alias: &str) -> Self {
self
}
}
impl $crate::Model for $model {
type PrimaryKey = $pk;
type Fields = [<$model Fields>];
fn table_name() -> &'static str {
$table
}
fn app_label() -> &'static str {
$app
}
fn primary_key(&self) -> Option<Self::PrimaryKey> {
Some(self.id)
}
fn set_primary_key(&mut self, value: Self::PrimaryKey) {
self.id = value;
}
fn primary_key_field() -> &'static str {
"id"
}
fn new_fields() -> Self::Fields {
[<$model Fields>]
}
}
}
};
($model:ident, $pk:ty, $table:expr, non_option_pk) => {
$crate::impl_test_model!($model, $pk, $table, "default", non_option_pk);
};
}