mongor 0.1.10

Ergonomic MongoDB ODM
Documentation
// Authors: Robert Lopez
// License: MIT (See `LICENSE.md`)

use crate::{model::Model, test_db::test_error::TestError, test_error};
use mongodb::{
    bson::{oid::ObjectId, Document},
    ClientSession,
};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

/// Wraps a reference to a model to allow easy testing assertions,
/// calling `test_error!` under the hood.
///
/// ---
/// Example Usage:
/// ```
///
/// let shark_model: Model<Shark> = ...;
///
/// AssertModel::from(&shark_model)
///     .assert_exists(doc! { "name": "Whale Shark" }, None)
///     .await?
///     .assert_count(doc! {}, 1, None)
///     .await?;
/// ```
pub struct AssertModel<'am, D>
where
    D: Serialize + for<'a> Deserialize<'a> + Send + Sync + Unpin,
{
    pub model: &'am Model<D>,
}

impl<'am, D> AssertModel<'am, D>
where
    D: Serialize + for<'a> Deserialize<'a> + Send + Sync + Unpin,
{
    /// Constructs a AssertModel from a reference to `Model`.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model: AssertModel<D> = AssertModel::from(&model);
    /// ```
    pub fn from(model: &'am Model<D>) -> AssertModel<'am, D> {
        AssertModel { model }
    }

    /// Asserts a document exists in the model by a
    /// given `filter`.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_exists(
    ///     doc! { "_id": ... },
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_exists(
        &self,
        filter: Document,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>> {
        if !self.model.exists(filter.clone(), None, session).await? {
            test_error!(
                "Document by filter: {:?} in collection {} does not exist",
                filter,
                self.model.collection.name()
            );
        }

        Ok(self)
    }

    /// Asserts a document does not exist in the model by a
    /// given `filter`.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_does_not_exists(
    ///     doc! { "_id": ... },
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_does_not_exists(
        &self,
        filter: Document,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>> {
        if self.model.exists(filter.clone(), None, session).await? {
            test_error!(
                "Document by filter: {:?} in collection {} exists",
                filter,
                self.model.collection.name()
            );
        }

        Ok(self)
    }

    /// Asserts a document exists in the model by a
    /// given oid `O` which must be, or be convertible
    /// to ObjectId.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_exists_by_oid(
    ///     "Some oid",
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_exists_by_oid(
        &self,
        oid: &ObjectId,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>> {
        if !self.model.exists_by_oid(oid, None, session).await? {
            test_error!(
                "Document by ObjectId: {:?} in collection {} does not exist",
                oid,
                self.model.collection.name()
            );
        }

        Ok(self)
    }

    /// Asserts a document does not exist in the model by
    /// a given oid `O` which must be, or be convertible
    /// to ObjectId.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_does_not_exist_by_oid(
    ///     "Some oid",
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_does_not_exist_by_oid(
        &self,
        oid: &ObjectId,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>> {
        if self.model.exists_by_oid(oid, None, session).await? {
            test_error!(
                "Document by ObjectId: {:?} in collection {} exists",
                oid,
                self.model.collection.name()
            );
        }

        Ok(self)
    }

    /// Asserts a document exists in the model by
    /// a given field and value.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_exists_by_field_value(
    ///     "Some field",
    ///     1337,
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_exists_by_field_value<F, V>(
        &self,
        field: F,
        value: V,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>>
    where
        F: Into<std::string::String> + Debug + Clone,
        std::string::String: From<F>,
        V: Into<mongodb::bson::Bson> + Debug + Clone,
        mongodb::bson::Bson: From<V>,
    {
        if !self
            .model
            .exists_by_field_value(field.clone(), value.clone(), None, session)
            .await?
        {
            test_error!(
                "Document by field: {:?} value: {:?} in collection {} does not exist",
                field,
                value,
                self.model.collection.name()
            );
        }

        Ok(self)
    }

    /// Asserts a document does not exist in the model by
    /// a given field and value.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_does_not_exist_by_field_value(
    ///     "Some field",
    ///     1337,
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_does_not_exist_by_field_value<F, V>(
        &self,
        field: F,
        value: V,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>>
    where
        F: Into<std::string::String> + Debug + Clone,
        std::string::String: From<F>,
        V: Into<mongodb::bson::Bson> + Debug + Clone,
        mongodb::bson::Bson: From<V>,
    {
        if self
            .model
            .exists_by_field_value(field.clone(), value.clone(), None, session)
            .await?
        {
            test_error!(
                "Document by field: {:?} value: {:?} in collection {} exists",
                field,
                value,
                self.model.collection.name()
            );
        }

        Ok(self)
    }

    /// Asserts a model has `count` documents by a filter.
    ///
    /// ---
    /// Example Usage:
    /// ```
    ///
    /// let model: Model<D> = ...;
    ///
    /// let assert_model = AssertModel::from(&model);
    ///
    /// assert_model.assert_count(
    ///     doc! { "_id": ... },
    ///     1337,
    ///     None,
    /// ).await?;
    /// ```
    pub async fn assert_count(
        &self,
        filter: Document,
        count: usize,
        session: Option<&mut ClientSession>,
    ) -> Result<&AssertModel<'am, D>, Box<dyn std::error::Error>> {
        let document_count = self
            .model
            .count_documents(filter.clone(), None, session)
            .await?;

        if document_count != count {
            test_error!(
            "Document count by filter: {:?} in collection {} has {} documents, but expected: {}",
            filter,
            self.model.collection.name(),
            document_count,
            count
        );
        }

        Ok(self)
    }
}