v0.2.2
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-31 16:27:41 +01:00
1 parent 95c0587c1e
commit 5b22add1f3
21 files changed
+293 -89

No files matched your search

+75 -12
View File
@@ -9,7 +9,7 @@ use rusqlite::OptionalExtension as _;
use rusqlite::types::{ToSql, ToSqlOutput, Value};
use sha2::{Digest, Sha256};
use sqlx::pool::PoolConnection;
use sqlx::{MySql, MySqlPool, PgPool, Postgres, Row as SqlxRow};
use sqlx::{Executor as _, MySql, MySqlPool, PgPool, Postgres, Row as SqlxRow};
use tokio::runtime::{Builder, Handle};
use tokio::sync::Mutex as AsyncMutex;
@@ -180,14 +180,6 @@ fn global_schema(schema: &str) -> Result<String, String> {
schema_section(schema, "-- RUSTBOX:GLOBAL-BEGIN", "-- RUSTBOX:GLOBAL-END")
}
pub(crate) fn global_schema_for_backend(backend: DatabaseBackend) -> Result<String, String> {
match backend {
DatabaseBackend::Sqlite => global_schema(SQLITE_SCHEMA),
DatabaseBackend::Mysql => global_schema(MYSQL_SCHEMA),
DatabaseBackend::Pgsql => global_schema(PGSQL_SCHEMA),
}
}
fn tenant_schema(schema: &str, prefix: &str) -> Result<String, String> {
Ok(
schema_section(schema, "-- RUSTBOX:TENANT-BEGIN", "-- RUSTBOX:TENANT-END")?
@@ -236,7 +228,22 @@ pub async fn configure(config: &AppConfig) -> Result<(), String> {
.map_err(|e| format!("failed to configure SQLite database: {e}"))?;
conn.execute_batch(&global_schema(SQLITE_SCHEMA)?)
.map_err(|e| format!("SQLite global schema initialization failed: {e}"))?;
ConfiguredDatabase::Sqlite { path }
// Unit tests create many independent temporary data directories in one
// process. Pinning the first SQLite path in the process-wide OnceLock
// makes later tests reuse a TempDir that may already have been deleted,
// causing a cascade of "unable to open database file" failures.
// Initialize the requested test database, but let open_user_db() resolve
// each test's own data_dir instead of publishing this path globally.
#[cfg(test)]
{
return Ok(());
}
#[cfg(not(test))]
{
ConfiguredDatabase::Sqlite { path }
}
}
DatabaseBackend::Mysql => {
let url = config
@@ -280,10 +287,29 @@ pub async fn configure(config: &AppConfig) -> Result<(), String> {
let schema = global_schema(PGSQL_SCHEMA)?;
let init_pool = pool.clone();
runtime_wait(&runtime, async move {
sqlx::raw_sql(&schema)
.execute(&init_pool)
// PostgreSQL emits NOTICE for every CREATE ... IF NOT EXISTS that
// already exists. Keep expected idempotent startup DDL quiet without
// changing application-wide logging or server settings.
let mut tx = init_pool
.begin()
.await
.map_err(|e| format!("PostgreSQL global schema transaction failed: {e}"))?;
sqlx::query("SET LOCAL client_min_messages TO WARNING")
.execute(&mut *tx)
.await
.map_err(|e| format!("failed to quiet PostgreSQL schema notices: {e}"))?;
// `raw_sql(...).execute(&mut PgConnection)` can trigger SQLx's
// "Executor is not general enough" lifetime error when this
// future is spawned onto the dedicated database runtime. Execute
// the unprepared multi-statement DDL directly through `Executor`
// instead; this is SQLx's supported path for raw DDL batches.
(&mut *tx)
.execute(schema.as_str())
.await
.map_err(|e| format!("PostgreSQL global schema initialization failed: {e}"))?;
tx.commit()
.await
.map_err(|e| format!("PostgreSQL global schema commit failed: {e}"))?;
Ok(())
})?;
ConfiguredDatabase::Pgsql { pool, runtime }
@@ -2287,6 +2313,31 @@ pub fn open_test_db() -> Connection {
mod tests {
use super::*;
#[tokio::test]
async fn sqlite_test_configuration_does_not_pin_deleted_tempdir() {
let first = tempfile::tempdir().expect("failed to create first temp dir");
let config = AppConfig {
database_backend: "sqlite".to_string(),
database_url: None,
data_dir: first.path().to_string_lossy().into_owned(),
..Default::default()
};
configure(&config)
.await
.expect("failed to initialize first SQLite test database");
assert!(first.path().join("rustbox.sqlite").is_file());
drop(first);
let second = tempfile::tempdir().expect("failed to create second temp dir");
let second_dir = second.path().to_string_lossy().into_owned();
let connection = open_user_db(&second_dir, "second-test-account")
.expect("second test must not reuse the deleted first SQLite path");
assert_eq!(connection.backend(), DatabaseBackend::Sqlite);
assert!(second.path().join("rustbox.sqlite").is_file());
}
#[test]
fn table_prefix_is_short_and_deterministic() {
let hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
@@ -2322,6 +2373,18 @@ mod tests {
);
}
#[test]
fn tenant_rewriter_prefixes_header_sync_target_qualifier() {
let sql = prefix_tenant_tables(
"INSERT INTO user_meta (key, value) VALUES ('header_sync_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(user_meta.value AS INTEGER) + 1",
"u_test__",
);
assert_eq!(
sql,
"INSERT INTO u_test__user_meta (key, value) VALUES ('header_sync_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(u_test__user_meta.value AS INTEGER) + 1"
);
}
#[test]
fn test_schema_contains_sessions_and_user_tables() {
let conn = open_test_db();