🚧 Prevent database errors in test cases

This commit is contained in:
Manuel Cillero 2023-10-26 13:25:17 +02:00
parent d7762a10fa
commit 46ccbc10eb
7 changed files with 194 additions and 105 deletions

View file

@ -60,6 +60,7 @@ impl MigrationTrait for Migration {
.values_panic(vec!["administrator".into(), "3".into()]), .values_panic(vec!["administrator".into(), "3".into()]),
) )
.await .await
.unwrap_or_error(|e| DbErr::Custom(e.message()))
.map(|_| ()) .map(|_| ())
} }

View file

@ -1,7 +1,8 @@
use crate::core::action::add_action; use crate::core::action::add_action;
use crate::core::module::ModuleRef; use crate::core::module::ModuleRef;
use crate::core::theme::all::THEMES; use crate::core::theme::all::THEMES;
use crate::{service, trace, LazyStatic}; use crate::locale::L10n;
use crate::{service, trace, LazyStatic, ResultExt};
#[cfg(feature = "database")] #[cfg(feature = "database")]
use crate::db::*; use crate::db::*;
@ -109,35 +110,37 @@ pub fn init_modules() {
#[cfg(feature = "database")] #[cfg(feature = "database")]
pub fn run_migrations() { pub fn run_migrations() {
run_now({ if let Some(dbconn) = &*DBCONN {
struct Migrator; run_now({
impl MigratorTrait for Migrator { struct Migrator;
fn migrations() -> Vec<MigrationItem> { impl MigratorTrait for Migrator {
let mut migrations = vec![]; fn migrations() -> Vec<MigrationItem> {
for m in ENABLED_MODULES.read().unwrap().iter() { let mut migrations = vec![];
migrations.append(&mut m.migrations()); for m in ENABLED_MODULES.read().unwrap().iter() {
migrations.append(&mut m.migrations());
}
migrations
} }
migrations
} }
} Migrator::up(SchemaManagerConnection::Connection(dbconn), None)
Migrator::up(SchemaManagerConnection::Connection(&DBCONN), None) })
}) .expect_or_log(L10n::l("db_migration_fail").message().as_str());
.unwrap();
run_now({ run_now({
struct Migrator; struct Migrator;
impl MigratorTrait for Migrator { impl MigratorTrait for Migrator {
fn migrations() -> Vec<MigrationItem> { fn migrations() -> Vec<MigrationItem> {
let mut migrations = vec![]; let mut migrations = vec![];
for m in DROPPED_MODULES.read().unwrap().iter() { for m in DROPPED_MODULES.read().unwrap().iter() {
migrations.append(&mut m.migrations()); migrations.append(&mut m.migrations());
}
migrations
} }
migrations
} }
} Migrator::down(SchemaManagerConnection::Connection(dbconn), None)
Migrator::down(SchemaManagerConnection::Connection(&DBCONN), None) })
}) .expect_or_log(L10n::l("db_migration_fail").message().as_str());
.unwrap(); }
} }
// CONFIGURE SERVICES ****************************************************************************** // CONFIGURE SERVICES ******************************************************************************

View file

@ -1,5 +1,7 @@
//! Acceso unificado y normalizado a base de datos. //! Acceso unificado y normalizado a base de datos.
use crate::locale::L10n;
use crate::result::{SafeResult, TraceErr};
use crate::{config, trace, LazyStatic, ResultExt}; use crate::{config, trace, LazyStatic, ResultExt};
pub use url::Url as DbUri; pub use url::Url as DbUri;
@ -10,97 +12,140 @@ use sea_orm::{ConnectOptions, ConnectionTrait, Database, DatabaseBackend, Statem
pub(crate) use futures::executor::block_on as run_now; pub(crate) use futures::executor::block_on as run_now;
pub(crate) static DBCONN: LazyStatic<DbConn> = LazyStatic::new(|| { pub(crate) static DBCONN: LazyStatic<Option<DbConn>> = LazyStatic::new(|| {
trace::info!( if !config::SETTINGS.database.db_name.trim().is_empty() {
"Connecting to database \"{}\" using a pool of {} connections", trace::info!(
&config::SETTINGS.database.db_name, "Connecting to database \"{}\" using a pool of {} connections",
&config::SETTINGS.database.max_pool_size &config::SETTINGS.database.db_name,
); &config::SETTINGS.database.max_pool_size
);
let db_uri = match config::SETTINGS.database.db_type.as_str() { let db_uri = match config::SETTINGS.database.db_type.as_str() {
"mysql" | "postgres" => { "mysql" | "postgres" => {
let mut tmp_uri = DbUri::parse( let mut tmp_uri = DbUri::parse(
format!(
"{}://{}/{}",
&config::SETTINGS.database.db_type,
&config::SETTINGS.database.db_host,
&config::SETTINGS.database.db_name
)
.as_str(),
)
.unwrap();
tmp_uri
.set_username(config::SETTINGS.database.db_user.as_str())
.unwrap();
// https://github.com/launchbadge/sqlx/issues/1624
tmp_uri
.set_password(Some(config::SETTINGS.database.db_pass.as_str()))
.unwrap();
if config::SETTINGS.database.db_port != 0 {
tmp_uri
.set_port(Some(config::SETTINGS.database.db_port))
.unwrap();
}
tmp_uri
}
"sqlite" => DbUri::parse(
format!( format!(
"{}://{}/{}", "{}://{}",
&config::SETTINGS.database.db_type, &config::SETTINGS.database.db_type,
&config::SETTINGS.database.db_host,
&config::SETTINGS.database.db_name &config::SETTINGS.database.db_name
) )
.as_str(), .as_str(),
) )
.unwrap(); .unwrap(),
tmp_uri _ => {
.set_username(config::SETTINGS.database.db_user.as_str()) trace::error!(
.unwrap(); "Unrecognized database type \"{}\"",
// https://github.com/launchbadge/sqlx/issues/1624 &config::SETTINGS.database.db_type
tmp_uri );
.set_password(Some(config::SETTINGS.database.db_pass.as_str())) DbUri::parse("").unwrap()
.unwrap();
if config::SETTINGS.database.db_port != 0 {
tmp_uri
.set_port(Some(config::SETTINGS.database.db_port))
.unwrap();
} }
tmp_uri };
}
"sqlite" => DbUri::parse(
format!(
"{}://{}",
&config::SETTINGS.database.db_type,
&config::SETTINGS.database.db_name
)
.as_str(),
)
.unwrap(),
_ => {
trace::error!(
"Unrecognized database type \"{}\"",
&config::SETTINGS.database.db_type
);
DbUri::parse("").unwrap()
}
};
run_now(Database::connect::<ConnectOptions>({ Some(
let mut db_opt = ConnectOptions::new(db_uri.to_string()); run_now(Database::connect::<ConnectOptions>({
db_opt.max_connections(config::SETTINGS.database.max_pool_size); let mut db_opt = ConnectOptions::new(db_uri.to_string());
db_opt db_opt.max_connections(config::SETTINGS.database.max_pool_size);
})) db_opt
.expect_or_log("Failed to connect to database") }))
.expect_or_log(L10n::l("db_connection_fail").message().as_str()),
)
} else {
None
}
}); });
static DBBACKEND: LazyStatic<DatabaseBackend> = LazyStatic::new(|| DBCONN.get_database_backend()); pub async fn query<Q: QueryStatementWriter>(stmt: &mut Q) -> SafeResult<Option<Vec<QueryResult>>> {
match &*DBCONN {
pub async fn query<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Vec<QueryResult>, DbErr> { Some(dbconn) => {
DBCONN let dbbackend = dbconn.get_database_backend();
.query_all(Statement::from_string( match dbconn
*DBBACKEND, .query_all(Statement::from_string(
match *DBBACKEND { dbbackend,
DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), match dbbackend {
DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
}, DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
)) },
.await ))
.await
{
Ok(result) => SafeResult::Ok(Some(result)),
Err(e) => SafeResult::Err(TraceErr::error(L10n::n(e.to_string()), None)),
}
}
None => SafeResult::Err(TraceErr::trace(
L10n::l("db_connection_not_initialized"),
None,
)),
}
} }
pub async fn exec<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Option<QueryResult>, DbErr> { pub async fn exec<Q: QueryStatementWriter>(stmt: &mut Q) -> SafeResult<Option<QueryResult>> {
DBCONN match &*DBCONN {
.query_one(Statement::from_string( Some(dbconn) => {
*DBBACKEND, let dbbackend = dbconn.get_database_backend();
match *DBBACKEND { match dbconn
DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), .query_one(Statement::from_string(
DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), dbbackend,
DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), match dbbackend {
}, DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
)) DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
.await DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
},
))
.await
{
Ok(result) => SafeResult::Ok(result),
Err(e) => SafeResult::Err(TraceErr::error(L10n::n(e.to_string()), None)),
}
}
None => SafeResult::Err(TraceErr::trace(
L10n::l("db_connection_not_initialized"),
None,
)),
}
} }
pub async fn exec_raw(stmt: String) -> Result<ExecResult, DbErr> { pub async fn exec_raw(stmt: String) -> SafeResult<Option<ExecResult>> {
DBCONN match &*DBCONN {
.execute(Statement::from_string(*DBBACKEND, stmt)) Some(dbconn) => {
.await let dbbackend = dbconn.get_database_backend();
match dbconn
.execute(Statement::from_string(dbbackend, stmt))
.await
{
Ok(result) => SafeResult::Ok(Some(result)),
Err(e) => SafeResult::Err(TraceErr::error(L10n::n(e.to_string()), None)),
}
}
None => SafeResult::Err(TraceErr::trace(
L10n::l("db_connection_not_initialized"),
None,
)),
}
} }
// El siguiente módulo migration es una versión simplificada del módulo sea_orm_migration (v0.11.3) // El siguiente módulo migration es una versión simplificada del módulo sea_orm_migration (v0.11.3)

View file

@ -2,3 +2,7 @@
# ERRORS. # ERRORS.
language_set_failure = Failed to set language. Unicode Language Identifier "{$language}" is not accepted. Using "en-US", check the settings file language_set_failure = Failed to set language. Unicode Language Identifier "{$language}" is not accepted. Using "en-US", check the settings file
db_connection_fail = Failed to connect to database
db_connection_not_initialized = Database connection not initialized
db_migration_fail = Database update failed

View file

@ -1,4 +1,8 @@
# DEBUG. # DEBUG.
# ERRORS. # ERRORS.
language_set_failure = Error al establecer idioma. El Identificador de Lenguaje Unicode "{$language}" no es válido. Se usará "en-US". Comprobar archivo de configuración language_set_failure = Fallo al asignar idioma. El Identificador de Lenguaje Unicode "{$language}" no es válido. Se usará "en-US". Comprobar archivo de configuración
db_connection_fail = Fallo al conectar con la base de datos
db_connection_not_initialized = Conexión a la base de datos no inicializada
db_migration_fail = Fallo en la actualización de la base de datos

View file

@ -9,6 +9,24 @@ pub struct TraceErr<T> {
} }
impl<T> TraceErr<T> { impl<T> TraceErr<T> {
pub fn trace(trace: L10n, fallback: T) -> Self {
let message = trace.message();
trace::trace!(message);
TraceErr { message, fallback }
}
pub fn debug(trace: L10n, fallback: T) -> Self {
let message = trace.message();
trace::debug!(message);
TraceErr { message, fallback }
}
pub fn info(trace: L10n, fallback: T) -> Self {
let message = trace.message();
trace::info!(message);
TraceErr { message, fallback }
}
pub fn warn(trace: L10n, fallback: T) -> Self { pub fn warn(trace: L10n, fallback: T) -> Self {
let message = trace.message(); let message = trace.message();
trace::warn!(message); trace::warn!(message);
@ -21,6 +39,8 @@ impl<T> TraceErr<T> {
TraceErr { message, fallback } TraceErr { message, fallback }
} }
// TraceErr GETTERS.
pub fn message(self) -> String { pub fn message(self) -> String {
self.message self.message
} }
@ -36,10 +56,22 @@ pub enum SafeResult<T> {
} }
impl<T> SafeResult<T> { impl<T> SafeResult<T> {
#[inline]
pub fn unwrap_or_error<F, E>(self, f: F) -> Result<T, E>
where
F: FnOnce(TraceErr<T>) -> E,
{
match self {
SafeResult::Ok(r) => Ok(r),
SafeResult::Err(e) => Err(f(e)),
}
}
#[inline]
pub fn unwrap_or_fallback(self) -> T { pub fn unwrap_or_fallback(self) -> T {
match self { match self {
SafeResult::Ok(result) => result, SafeResult::Ok(r) => r,
SafeResult::Err(trace) => trace.fallback(), SafeResult::Err(e) => e.fallback(),
} }
} }
} }

View file

@ -16,5 +16,5 @@ async fn health_check_works() {
let req = service::test::TestRequest::get().uri("/").to_request(); let req = service::test::TestRequest::get().uri("/").to_request();
let _resp = service::test::call_service(&app, req).await; let _resp = service::test::call_service(&app, req).await;
// assert_eq!("OK", "OK"); // assert_eq!("OK", "OK");
} }