rustling-data 0.1.0

Core data access layer for Rustling, supporting PostgreSQL and MongoDB
Documentation
#![cfg(feature = "mongo")]

use mongodb::{
    Client, Database,
    bson::{doc, oid::ObjectId},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use testcontainers_modules::{
    mongo,
    testcontainers::{ContainerAsync, runners::AsyncRunner},
};
use tokio::time::sleep;
use rustling_data::MongoDriver;

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
struct User {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<ObjectId>,
    pub name: String,
    pub email: String,
}

pub async fn setup_mongo() -> (Client, Database, ContainerAsync<mongo::Mongo>) {
    let container = mongo::Mongo::default().start().await.unwrap();
    let port = container.get_host_port_ipv4(27017).await.unwrap();
    let uri = format!("mongodb://localhost:{port}/testdb");

    let mut retries = 10;
    loop {
        match Client::with_uri_str(&uri).await {
            Ok(client) => {
                if client
                    .database("admin")
                    .run_command(doc! {"ping": 1})
                    .await
                    .is_ok()
                {
                    let db = client.database("testdb");
                    return (client, db, container);
                }
            }
            Err(_) => {}
        }

        if retries == 0 {
            panic!("MongoDB did not become ready in time");
        }
        retries -= 1;
        sleep(Duration::from_secs(1)).await;
    }
}

#[tokio::test]
async fn test_insert_one() {
    let (_client, _db, _container) = setup_mongo().await;

    let user = User {
        id: None,
        name: "Alice".into(),
        email: "alice@example.com".into(),
    };

    let mongo_repo = MongoDriver::new(_client.clone(), _db.name().to_string());

    let inserted_id = mongo_repo.insert_one("users", &user).await.unwrap();

    let found: User = mongo_repo
        .find_one("users", doc! { "_id": inserted_id })
        .await
        .unwrap()
        .unwrap();

    assert_eq!(found.name, "Alice");
    assert_eq!(found.email, "alice@example.com");

    drop(_container);
}

#[tokio::test]
async fn test_find_all() {
    let (_client, _db, _container) = setup_mongo().await;
    let mongo_repo = MongoDriver::new(_client.clone(), _db.name().to_string());

    mongo_repo.insert_one(
        "users",
        &User {
            id: None,
            name: "Bob".into(),
            email: "bob@example.com".into(),
        },
    )
        .await
        .unwrap();

    let users: Vec<User> = mongo_repo.find_all("users").await.unwrap();
    assert_eq!(users.len(), 1);
    assert_eq!(users[0].name, "Bob");

    drop(_container);
}

#[tokio::test]
async fn test_find_one() {
    let (_client, _db, _container) = setup_mongo().await;
    let mongo_repo = MongoDriver::new(_client.clone(), _db.name().to_string());

    mongo_repo
        .insert_one(
            "users",
            &User {
                id: None,
                name: "Charlie".into(),
                email: "charlie@example.com".into(),
            },
        )
        .await
        .unwrap();

    let found: User = mongo_repo
        .find_one("users", doc! { "email": "charlie@example.com" })
        .await
        .unwrap()
        .unwrap();

    assert_eq!(found.name, "Charlie");

    drop(_container);
}

#[tokio::test]
async fn test_update_one() {
    let (_client, _db, _container) = setup_mongo().await;
    let mongo_repo = MongoDriver::new(_client.clone(), _db.name().to_string());

    // Insert initial document
    let user = User {
        id: None,
        name: "David".into(),
        email: "david@example.com".into(),
    };
    mongo_repo.insert_one("users", &user).await.unwrap();

    // Update the document
    let mut updated_user = user.clone();
    updated_user.name = "Dave".into();
    let updated: Option<User> = mongo_repo
        .update_one("users", doc! { "email": "david@example.com" }, &updated_user)
        .await
        .unwrap();

    // Check that update returned the new document
    let found = updated.unwrap();
    assert_eq!(found.name, "Dave");
    assert_eq!(found.email, "david@example.com");

    // Optional: double-check by fetching from DB
    let fetched: User = mongo_repo
        .find_one("users", doc! { "email": "david@example.com" })
        .await
        .unwrap()
        .unwrap();
    assert_eq!(fetched.name, "Dave");

    drop(_container);
}


#[tokio::test]
async fn test_delete_one() {
    let (_client, _db, _container) = setup_mongo().await;
    let mongo_repo = MongoDriver::new(_client.clone(), _db.name().to_string());

    mongo_repo
        .insert_one(
            "users",
            &User {
                id: None,
                name: "Eve".into(),
                email: "eve@example.com".into(),
            },
        )
        .await
        .unwrap();

    let deleted = mongo_repo
        .delete_one("users", doc! { "email": "eve@example.com" })
        .await
        .unwrap();
    assert_eq!(deleted, 1);

    let users: Vec<User> = mongo_repo.find_all("users").await.unwrap();
    assert!(users.is_empty());

    drop(_container);
}