Создать соединение с БД из тестовой функции (rocket_db_pools)
От: zelenprog  
Дата: 17.03.25 18:32
Оценка:
Добрый день!

Есть простая программа — HTTP-сервис.
При обработке get-запроса программа использует файловую базу данных sqlite. Для работы с БД используется rocket_db_pools.
Все сделано и работает как описано вот в этой статье:
https://docs.rs/rocket_db_pools/latest/rocket_db_pools/

В обычном "рабочем" режиме библиотека rocket "автоматически" вызывает функцию-обработчик get-запроса, и в качестве параметра передает ей объект соединения с БД Connection<LogsDB>. Этот параметр-объект создается "автоматически" внутри библиотеки rocket.

Проблема возникла при реализации тестовой функции.
Я из тестовой функции хочу вызвать функцию, которая реализует обработку get-запроса. Для этого нужно "вручную" создать объект Connection<LogsDB> и передать его в обработчик-запроса.
Но как это сделать?.
Как в тестовой функции "вручную" создать объект Connection<LogsDB>?
Я читал описание этой библиотеки, но так и не смог ничего найти.
Подскажите пожалуйста.

Вот код программы:
use rocket::get;
use rocket_db_pools::{Connection, Database};
use rocket_db_pools::sqlx::Row;
 
#[derive(Database)]
#[database("data_store")]
struct LogsDB(sqlx::SqlitePool);
 
#[get("/<id>")]
//async fn read(mut db: Connection<LogsDB>, id: i64) -> Option<Log> {
async fn read(mut db: Connection<LogsDB>, id: i64) {
    let res = sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id)
        .fetch_one(&mut **db).await;
//        .and_then(|r| Ok(log::Log(r.try_get(0)?)))
//        .ok()
}
 
#[cfg(test)]
mod tests {
    use super::*;
 
    #[tokio::test]
    async fn test_load_requests_and_write_updxmlfile() {
 
        let l_rocket = rocket::build().attach(LogsDB::init());
        //let l_database = MarkingDatabase::init();
        let l_db1 = LogsDB::fetch(&l_rocket).unwrap();
        let mut l_db_connection1 = l_db1.aquire().await.unwrap();        
        
        read(l_db_connection1, 50)?;
    }
 
}


Я так понимаю, что объект Connection<LogsDB> надо как-то получить в строке
"let mut l_db_connection1 = l_db1.aquire().await.unwrap();"

Компилятор выдает ошибку на эту строку.
Re: Создать соединение с БД из тестовой функции (rocket_db_pools)
От: sergii.p  
Дата: 17.03.25 19:39
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Вот код программы:

Z>
Z>struct LogsDB(sqlx::SqlitePool);
Z>


это кортеж. Метод LogsDB::fetch() выдаст структуру LogsDB. Но надо добраться до вложенного типа. Значит надо писать

let mut l_db_connection1 = l_db1.0.aquire().await.unwrap();


или достать через паттерн матчинг

let (l_db1,) = l_db1;
Re: Создать соединение с БД из тестовой функции (rocket_db_pools)
От: Doom100500 Израиль  
Дата: 18.03.25 06:18
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Подскажите пожалуйста.


Если вот ни разу не про rust, но:
А счего бы вдруг sql запросы отправляются в connection напрямую из get обработчика?
Разве не должна быть модель, реализующая интрефейс (или трейт), и её мы подменяем в тестах хоть на inmemory, хоть на другое хранилище. Мы же не хранилище тестируем, так?
Спасибо за внимание
Re[2]: Создать соединение с БД из тестовой функции (rocket_db_pools)
От: zelenprog  
Дата: 18.03.25 06:55
Оценка:
SP>... Метод LogsDB::fetch() выдаст структуру LogsDB. Но надо добраться до вложенного типа. Значит надо писать
SP>
SP>let mut l_db_connection1 = l_db1.0.aquire().await.unwrap();
SP>


Проверил...
Выражение "l_db1.0.acquire().await.unwrap();" возвращает значение типа "sqlx::pool::PoolConnection<sqlx::Sqlite>".
При вызове функции "read" компилятор выдает ошибку:

error[E0308]: mismatched types
--> src\lib.rs:32:14
|
32 | read(l_db_connection1, 50);
| ---- ^^^^^^^^^^^^^^^^ expected `Connection<LogsDB>`, found `PoolConnection<Sqlite>`
| |
| arguments to this function are incorrect
|
= note: expected struct `rocket_db_pools::Connection<LogsDB>`
found struct `PoolConnection<Sqlite>`


Значит, надо еще какие-то манипуляции сделать, чтобы добраться до типа Connection<LogsDB>.

Получается, мы получили пул соединений.
Может быть теперь надо из этого пула взять одно из соединений? Или создать новое?

Z>>
Z>>struct LogsDB(sqlx::SqlitePool);
Z>>

SP>это кортеж...

Насколько я помню из документации, при использовании кортежа из одного элемента в конце надо обязательно ставить запятую.
Чтобы отличить кортеж с одним элементом от простого выражения в скобках.
Или я что-то неправильно понял?
Re[2]: Создать соединение с БД из тестовой функции (rocket_d
От: zelenprog  
Дата: 18.03.25 07:30
Оценка:
D>А счего бы вдруг sql запросы отправляются в connection напрямую из get обработчика?

Вообще в "рабочем" коде из get-обработчика вызывается специальная функция-реализация, которая вызывает другие объекты и методы и т.д.
Цепочка вызовов получается достаточно длинная.

Это я для упрощения примера сделал показательный get-обработчик, чтобы показать в чем у меня проблема.
А проблема у меня — получить соединение с БД конкретного типа, который нужен для чтения данных из БД.

D>Разве не должна быть модель, реализующая интрефейс (или трейт), и её мы подменяем в тестах хоть на inmemory, хоть на другое хранилище...


Вообще в принципе — да, согласен.
Но хранилище не надо подменять.

D>... Мы же не хранилище тестируем, так?


В данном случае мне надо протестировать полную реализацию get-запроса, которая выполняет много разных действий и в том числе работает с хранилищем.
При работе с хранилищем (например при чтении из хранилища данных) используется вот такой код:
async fn read(mut db: Connection<LogsDB>, id: i64) {
    let res = sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id)
        .fetch_one(&mut **db).await;
    ....

db — это параметр типа "Connection<LogsDB>", который передается в эту функцию.
Вот хорошо бы этот код тоже протестировать.

Конечно, можно было бы вместо параметра "db" типа "Connection<LogsDB>" сделать интерфейс (трейт) с одной функцией "get_connection()" и передавать в функцию этот трейт вместо "Connection<LogsDB>".
Но тогда я не протестирую "рабочий" код.
Отредактировано 18.03.2025 7:30 zelenprog . Предыдущая версия .
Re[3]: Создать соединение с БД из тестовой функции (rocket_db_pools)
От: sergii.p  
Дата: 18.03.25 10:34
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>Значит, надо еще какие-то манипуляции сделать, чтобы добраться до типа Connection<LogsDB>.


надо. Почитайте документацию. Там уже у Pool есть метод acquire, который как раз возвращает Connection. Возможно подойдёт.

Z>Насколько я помню из документации, при использовании кортежа из одного элемента в конце надо обязательно ставить запятую.


при объявлении структуры не надо. Это при паттерн-матчинге и при объявлении кортежа на лету только

let tuple = (42u8, );
let (int, ) = tuple;
Re[4]: Создать соединение с БД из тестовой функции (rocket_db_pools)
От: zelenprog  
Дата: 18.03.25 11:21
Оценка:
SP>Почитайте документацию. Там уже у Pool есть метод acquire, который как раз возвращает Connection. Возможно подойдёт.

В том то и дело, что читаю уже почти неделю.
А найти не могу. Маловато опыта в Rust-е.

Сегодня мне "помог" DeepSeek.
Как-то я догадался указать в запросе версию библиотеки rocket_db_pools 0.1.0.
И как только я указал эту версию он мне выдал ответ, который все объясняет:

В версии rocket_db_pools 0.1.0 API немного отличается от более поздних версий. В этой версии Connection<MyDB> не предоставляет прямого способа создания вручную, как в более новых версиях.

Re[3]: Создать соединение с БД из тестовой функции (rocket_d
От: Doom100500 Израиль  
Дата: 20.03.25 06:55
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>В данном случае мне надо протестировать полную реализацию get-запроса, которая выполняет много разных действий и в том числе работает с хранилищем.

Z>При работе с хранилищем (например при чтении из хранилища данных) используется вот такой код:
Z>
Z>async fn read(mut db: Connection<LogsDB>, id: i64) {
Z>    let res = sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id)
Z>        .fetch_one(&mut **db).await;
Z>    ....
Z>

Z>db — это параметр типа "Connection<LogsDB>", который передается в эту функцию.
Z>Вот хорошо бы этот код тоже протестировать.

Z>Конечно, можно было бы вместо параметра "db" типа "Connection<LogsDB>" сделать интерфейс (трейт) с одной функцией "get_connection()" и передавать в функцию этот трейт вместо "Connection<LogsDB>".

Z>Но тогда я не протестирую "рабочий" код.

Я точно не имею огромного опыта в rust, и не знаю нюансов использования какого там фреймворка, но что значит "рабочий код"? Точно не внутри библиотечный — же. Мы не проверяем как именно работает условный fetch, нам надо убедиться, что "рабочий код" создал корректный запрос и адекватно реагирует на описанные в документации этого fetch ошибки.

Тогда мы мокаем эту самую библиотечную сущность и описывем в них тестовое поведение для "рабочего кода". Используя старые добрые разные реализации интерфейсов, которые и дёргаем в нашем "рабочем коде".
Спасибо за внимание
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.