refactor(tests): use in-process server and shared AppState factory
Replace CLI test child process with in-process server on a background thread, eliminating orphan processes after test runs. Simplify server test helpers to use AppState::from_database() instead of manual repo construction.
This commit is contained in:
parent
9a46bb3613
commit
f8ffb37110
2 changed files with 130 additions and 307 deletions
|
|
@ -1,30 +1,51 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::Mutex;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Shared test server state
|
||||
/// Fields prefixed with `_` are kept alive to prevent cleanup:
|
||||
/// - `_temp_dir`: keeps temporary database file from being deleted
|
||||
/// - `_process`: keeps server process running
|
||||
use once_cell::sync::Lazy;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use webauthn_rs::prelude::*;
|
||||
|
||||
use brewlog::application::routes::app_router;
|
||||
use brewlog::application::state::{AppState, AppStateConfig};
|
||||
use brewlog::infrastructure::database::Database;
|
||||
|
||||
/// Shared test server state.
|
||||
/// `_temp_dir` is kept alive to prevent the temporary database file from being deleted.
|
||||
/// The server itself runs on a background thread and dies when the test binary exits.
|
||||
struct SharedServer {
|
||||
address: String,
|
||||
db_url: String,
|
||||
_temp_dir: TempDir,
|
||||
_process: std::process::Child,
|
||||
}
|
||||
|
||||
/// Single shared test server for all CLI tests
|
||||
static TEST_SERVER: Lazy<Mutex<Option<SharedServer>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
fn test_webauthn() -> Arc<Webauthn> {
|
||||
let rp_origin = url::Url::parse("http://localhost:0").expect("valid URL");
|
||||
Arc::new(
|
||||
WebauthnBuilder::new("localhost", &rp_origin)
|
||||
.expect("valid RP config")
|
||||
.rp_name("Brewlog Test")
|
||||
.build()
|
||||
.expect("valid WebAuthn"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get path to the brewlog binary
|
||||
pub fn brewlog_bin() -> String {
|
||||
// Cargo sets this for integration tests - works for both debug and release
|
||||
env!("CARGO_BIN_EXE_brewlog").to_string()
|
||||
}
|
||||
|
||||
/// Get or start the shared test server - handles mutex poisoning gracefully
|
||||
/// Get or start the shared test server - handles mutex poisoning gracefully.
|
||||
///
|
||||
/// Spawns the server in-process on a background thread (not as a child process),
|
||||
/// so the server dies automatically when the test binary exits.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn ensure_server_started() -> Result<(String, String), String> {
|
||||
let mut server = match TEST_SERVER.lock() {
|
||||
Ok(guard) => guard,
|
||||
|
|
@ -34,74 +55,79 @@ fn ensure_server_started() -> Result<(String, String), String> {
|
|||
if server.is_none() {
|
||||
eprintln!("Starting test server...");
|
||||
|
||||
// Create temporary database
|
||||
let temp_dir = TempDir::new().map_err(|e| format!("Failed to create temp dir: {}", e))?;
|
||||
// Create temporary database (file-based so create_token() can connect independently)
|
||||
let temp_dir = TempDir::new().map_err(|e| format!("Failed to create temp dir: {e}"))?;
|
||||
let db_path = temp_dir.path().join("test.db");
|
||||
let db_url = format!("sqlite:{}", db_path.display());
|
||||
|
||||
// Start server on a random port
|
||||
// Pick a random port
|
||||
let port = portpicker::pick_unused_port().ok_or("No ports available")?;
|
||||
let address = format!("http://localhost:{}", port);
|
||||
let address = format!("http://127.0.0.1:{port}");
|
||||
let bind_address = format!("127.0.0.1:{port}");
|
||||
|
||||
eprintln!("Starting server on {}", address);
|
||||
eprintln!("Starting server on {address}");
|
||||
|
||||
let bind_address = format!("127.0.0.1:{}", port);
|
||||
let process = Command::new(brewlog_bin())
|
||||
.args(&[
|
||||
"serve",
|
||||
"--bind-address",
|
||||
&bind_address,
|
||||
"--database-url",
|
||||
&db_url,
|
||||
])
|
||||
.env("BREWLOG_RP_ID", "localhost")
|
||||
.env("BREWLOG_RP_ORIGIN", &address)
|
||||
.env("BREWLOG_OPENROUTER_API_KEY", "test-key")
|
||||
.env("BREWLOG_FOURSQUARE_API_KEY", "test-key")
|
||||
.env("RUST_LOG", "error")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start brewlog server: {}", e))?;
|
||||
let db_url_for_thread = db_url.clone();
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel::<()>(1);
|
||||
|
||||
// Start server on a background thread with its own tokio runtime.
|
||||
// The thread (and server) live until the test binary exits — no cleanup needed.
|
||||
std::thread::spawn(move || {
|
||||
#[allow(clippy::expect_used)]
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create tokio runtime");
|
||||
|
||||
rt.block_on(async move {
|
||||
#[allow(clippy::expect_used)]
|
||||
let database = Database::connect(&db_url_for_thread)
|
||||
.await
|
||||
.expect("Failed to connect to test database");
|
||||
|
||||
let state = AppState::from_database(
|
||||
&database,
|
||||
AppStateConfig {
|
||||
webauthn: test_webauthn(),
|
||||
foursquare_url: brewlog::infrastructure::foursquare::FOURSQUARE_SEARCH_URL
|
||||
.to_string(),
|
||||
foursquare_api_key: String::new(),
|
||||
openrouter_url: brewlog::infrastructure::ai::OPENROUTER_URL.to_string(),
|
||||
openrouter_api_key: String::new(),
|
||||
openrouter_model: "openrouter/free".to_string(),
|
||||
},
|
||||
);
|
||||
|
||||
let app = app_router(state);
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let listener = TcpListener::bind(&bind_address)
|
||||
.await
|
||||
.expect("Failed to bind to port");
|
||||
|
||||
// Signal readiness to the main thread
|
||||
let _ = tx.send(());
|
||||
|
||||
// Run server until the process exits
|
||||
#[allow(clippy::expect_used)]
|
||||
axum::serve(listener, app).await.expect("Server failed");
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for server to be ready
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(Duration::from_secs(3))
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
|
||||
let health_url = format!("{}/api/v1/roasters", address);
|
||||
rx.recv_timeout(Duration::from_secs(30))
|
||||
.map_err(|e| format!("Server failed to start within timeout: {e}"))?;
|
||||
|
||||
let mut server_ready = false;
|
||||
for attempt in 0..100 {
|
||||
match client.get(&health_url).send() {
|
||||
Ok(_) => {
|
||||
eprintln!("Server ready after {} attempts", attempt + 1);
|
||||
server_ready = true;
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
if attempt == 99 {
|
||||
return Err(
|
||||
"Server failed to start after 100 attempts (10 seconds)".to_string()
|
||||
);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !server_ready {
|
||||
return Err("Server never became ready".to_string());
|
||||
}
|
||||
eprintln!("Server ready on {address}");
|
||||
|
||||
*server = Some(SharedServer {
|
||||
address: address.clone(),
|
||||
db_url: db_url.clone(),
|
||||
_temp_dir: temp_dir,
|
||||
_process: process,
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let srv = server.as_ref().unwrap();
|
||||
Ok((srv.address.clone(), srv.db_url.clone()))
|
||||
}
|
||||
|
|
@ -115,18 +141,21 @@ pub fn server_info() -> (String, String) {
|
|||
pub fn create_token(name: &str) -> String {
|
||||
let (_, db_url) = ensure_server_started().expect("Failed to start test server");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create tokio runtime");
|
||||
|
||||
rt.block_on(async {
|
||||
#[allow(clippy::expect_used)]
|
||||
let pool = sqlx::SqlitePool::connect(&db_url)
|
||||
.await
|
||||
.expect("Failed to connect to test database");
|
||||
|
||||
// Ensure a user exists (INSERT OR IGNORE to handle concurrent test threads)
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
#[allow(clippy::expect_used)]
|
||||
sqlx::query(
|
||||
"INSERT OR IGNORE INTO users (username, uuid, created_at) VALUES (?, ?, datetime('now'))",
|
||||
)
|
||||
|
|
@ -136,6 +165,7 @@ pub fn create_token(name: &str) -> String {
|
|||
.await
|
||||
.expect("Failed to ensure test user");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let user_id: i64 =
|
||||
sqlx::query_scalar::<_, i64>("SELECT id FROM users WHERE username = ?")
|
||||
.bind("admin")
|
||||
|
|
@ -144,10 +174,12 @@ pub fn create_token(name: &str) -> String {
|
|||
.expect("Failed to query test user");
|
||||
|
||||
// Generate and insert a bearer token
|
||||
#[allow(clippy::expect_used)]
|
||||
let token_value =
|
||||
brewlog::infrastructure::auth::generate_token().expect("Failed to generate token");
|
||||
let token_hash = brewlog::infrastructure::auth::hash_token(&token_value);
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
sqlx::query(
|
||||
"INSERT INTO tokens (user_id, token_hash, name, created_at) VALUES (?, ?, ?, datetime('now'))",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,34 +1,15 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use brewlog::application::routes::app_router;
|
||||
use brewlog::application::server::AppState;
|
||||
use brewlog::application::services::{
|
||||
BagService, BrewService, CafeService, CupService, GearService, RoastService, RoasterService,
|
||||
};
|
||||
use brewlog::application::state::{AppState, AppStateConfig};
|
||||
use brewlog::domain::cafes::{Cafe, NewCafe};
|
||||
use brewlog::domain::repositories::{
|
||||
BagRepository, BrewRepository, CafeRepository, CupRepository, GearRepository,
|
||||
PasskeyCredentialRepository, RegistrationTokenRepository, RoastRepository, RoasterRepository,
|
||||
SessionRepository, TimelineEventRepository, TokenRepository, UserRepository,
|
||||
CafeRepository, RoastRepository, RoasterRepository, SessionRepository, TimelineEventRepository,
|
||||
TokenRepository, UserRepository,
|
||||
};
|
||||
use brewlog::domain::roasters::{NewRoaster, Roaster};
|
||||
use brewlog::domain::users::NewUser;
|
||||
use brewlog::infrastructure::backup::BackupService;
|
||||
use brewlog::infrastructure::database::Database;
|
||||
use brewlog::infrastructure::repositories::bags::SqlBagRepository;
|
||||
use brewlog::infrastructure::repositories::brews::SqlBrewRepository;
|
||||
use brewlog::infrastructure::repositories::cafes::SqlCafeRepository;
|
||||
use brewlog::infrastructure::repositories::cups::SqlCupRepository;
|
||||
use brewlog::infrastructure::repositories::gear::SqlGearRepository;
|
||||
use brewlog::infrastructure::repositories::passkey_credentials::SqlPasskeyCredentialRepository;
|
||||
use brewlog::infrastructure::repositories::registration_tokens::SqlRegistrationTokenRepository;
|
||||
use brewlog::infrastructure::repositories::roasters::SqlRoasterRepository;
|
||||
use brewlog::infrastructure::repositories::roasts::SqlRoastRepository;
|
||||
use brewlog::infrastructure::repositories::sessions::SqlSessionRepository;
|
||||
use brewlog::infrastructure::repositories::timeline_events::SqlTimelineEventRepository;
|
||||
use brewlog::infrastructure::repositories::tokens::SqlTokenRepository;
|
||||
use brewlog::infrastructure::repositories::users::SqlUserRepository;
|
||||
use brewlog::infrastructure::webauthn::ChallengeStore;
|
||||
use reqwest::Client;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::task::AbortHandle;
|
||||
|
|
@ -83,160 +64,42 @@ fn test_webauthn() -> Arc<Webauthn> {
|
|||
}
|
||||
|
||||
pub async fn spawn_app() -> TestApp {
|
||||
// Use in-memory SQLite database for testing
|
||||
let database = Database::connect("sqlite::memory:")
|
||||
.await
|
||||
.expect("Failed to connect to in-memory database");
|
||||
|
||||
// Run migrations
|
||||
database
|
||||
.migrate()
|
||||
.await
|
||||
.expect("Failed to migrate database");
|
||||
|
||||
// Create repositories
|
||||
let roaster_repo = Arc::new(SqlRoasterRepository::new(database.clone_pool()));
|
||||
let roast_repo = Arc::new(SqlRoastRepository::new(database.clone_pool()));
|
||||
let bag_repo = Arc::new(SqlBagRepository::new(database.clone_pool()));
|
||||
let gear_repo = Arc::new(SqlGearRepository::new(database.clone_pool()));
|
||||
let brew_repo = Arc::new(SqlBrewRepository::new(database.clone_pool()));
|
||||
let cafe_repo = Arc::new(SqlCafeRepository::new(database.clone_pool()));
|
||||
let cup_repo = Arc::new(SqlCupRepository::new(database.clone_pool()));
|
||||
let timeline_repo = Arc::new(SqlTimelineEventRepository::new(database.clone_pool()));
|
||||
let user_repo: Arc<dyn UserRepository> =
|
||||
Arc::new(SqlUserRepository::new(database.clone_pool()));
|
||||
let token_repo: Arc<dyn TokenRepository> =
|
||||
Arc::new(SqlTokenRepository::new(database.clone_pool()));
|
||||
let session_repo: Arc<dyn SessionRepository> =
|
||||
Arc::new(SqlSessionRepository::new(database.clone_pool()));
|
||||
let passkey_repo: Arc<dyn PasskeyCredentialRepository> =
|
||||
Arc::new(SqlPasskeyCredentialRepository::new(database.clone_pool()));
|
||||
let registration_token_repo: Arc<dyn RegistrationTokenRepository> =
|
||||
Arc::new(SqlRegistrationTokenRepository::new(database.clone_pool()));
|
||||
|
||||
spawn_app_inner(
|
||||
database,
|
||||
roaster_repo,
|
||||
roast_repo,
|
||||
bag_repo,
|
||||
gear_repo,
|
||||
brew_repo,
|
||||
cafe_repo,
|
||||
cup_repo,
|
||||
timeline_repo,
|
||||
user_repo,
|
||||
token_repo,
|
||||
session_repo,
|
||||
passkey_repo,
|
||||
registration_token_repo,
|
||||
brewlog::infrastructure::foursquare::FOURSQUARE_SEARCH_URL.to_string(),
|
||||
String::new(),
|
||||
brewlog::infrastructure::ai::OPENROUTER_URL.to_string(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
spawn_app_inner(database, test_state_config(), None).await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn spawn_app_inner(
|
||||
_database: Database,
|
||||
roaster_repo: Arc<SqlRoasterRepository>,
|
||||
roast_repo: Arc<SqlRoastRepository>,
|
||||
bag_repo: Arc<SqlBagRepository>,
|
||||
gear_repo: Arc<SqlGearRepository>,
|
||||
brew_repo: Arc<SqlBrewRepository>,
|
||||
cafe_repo: Arc<SqlCafeRepository>,
|
||||
cup_repo: Arc<SqlCupRepository>,
|
||||
timeline_repo: Arc<SqlTimelineEventRepository>,
|
||||
user_repo: Arc<dyn UserRepository>,
|
||||
token_repo: Arc<dyn TokenRepository>,
|
||||
session_repo: Arc<dyn SessionRepository>,
|
||||
passkey_repo: Arc<dyn PasskeyCredentialRepository>,
|
||||
registration_token_repo: Arc<dyn RegistrationTokenRepository>,
|
||||
foursquare_url: String,
|
||||
foursquare_api_key: String,
|
||||
openrouter_url: String,
|
||||
mock_server: Option<wiremock::MockServer>,
|
||||
) -> TestApp {
|
||||
let backup_service = Arc::new(BackupService::new(_database.clone_pool()));
|
||||
|
||||
let session_repo_clone: Arc<dyn SessionRepository> = session_repo.clone();
|
||||
|
||||
// Create services
|
||||
let roaster_service = RoasterService::new(
|
||||
roaster_repo.clone() as Arc<dyn RoasterRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
let roast_service = RoastService::new(
|
||||
roast_repo.clone() as Arc<dyn RoastRepository>,
|
||||
roaster_repo.clone() as Arc<dyn RoasterRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
let bag_service = BagService::new(
|
||||
bag_repo.clone() as Arc<dyn BagRepository>,
|
||||
roast_repo.clone() as Arc<dyn RoastRepository>,
|
||||
roaster_repo.clone() as Arc<dyn RoasterRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
let brew_service = BrewService::new(
|
||||
brew_repo.clone() as Arc<dyn BrewRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
let gear_service = GearService::new(
|
||||
gear_repo.clone() as Arc<dyn GearRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
let cafe_service = CafeService::new(
|
||||
cafe_repo.clone() as Arc<dyn CafeRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
let cup_service = CupService::new(
|
||||
cup_repo.clone() as Arc<dyn CupRepository>,
|
||||
timeline_repo.clone() as Arc<dyn TimelineEventRepository>,
|
||||
);
|
||||
|
||||
// Create application state
|
||||
let state = AppState {
|
||||
roaster_repo: roaster_repo.clone(),
|
||||
roast_repo: roast_repo.clone(),
|
||||
bag_repo: bag_repo.clone(),
|
||||
gear_repo: gear_repo.clone(),
|
||||
brew_repo: brew_repo.clone(),
|
||||
cafe_repo: cafe_repo.clone(),
|
||||
cup_repo: cup_repo.clone(),
|
||||
timeline_repo: timeline_repo.clone(),
|
||||
user_repo: user_repo.clone(),
|
||||
token_repo: token_repo.clone(),
|
||||
session_repo,
|
||||
passkey_repo,
|
||||
registration_token_repo,
|
||||
ai_usage_repo: Arc::new(
|
||||
brewlog::infrastructure::repositories::ai_usage::SqlAiUsageRepository::new(
|
||||
_database.clone_pool(),
|
||||
),
|
||||
),
|
||||
fn test_state_config() -> AppStateConfig {
|
||||
AppStateConfig {
|
||||
webauthn: test_webauthn(),
|
||||
challenge_store: Arc::new(ChallengeStore::new()),
|
||||
http_client: reqwest::Client::new(),
|
||||
foursquare_url,
|
||||
foursquare_api_key,
|
||||
openrouter_url,
|
||||
foursquare_url: brewlog::infrastructure::foursquare::FOURSQUARE_SEARCH_URL.to_string(),
|
||||
foursquare_api_key: String::new(),
|
||||
openrouter_url: brewlog::infrastructure::ai::OPENROUTER_URL.to_string(),
|
||||
openrouter_api_key: String::new(),
|
||||
openrouter_model: "openrouter/free".to_string(),
|
||||
backup_service,
|
||||
roaster_service,
|
||||
roast_service,
|
||||
bag_service,
|
||||
brew_service,
|
||||
gear_service,
|
||||
cafe_service,
|
||||
cup_service,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_app_inner(
|
||||
database: Database,
|
||||
config: AppStateConfig,
|
||||
mock_server: Option<wiremock::MockServer>,
|
||||
) -> TestApp {
|
||||
let state = AppState::from_database(&database, config);
|
||||
|
||||
// Clone repos we need for TestApp before consuming state in the router
|
||||
let roaster_repo = state.roaster_repo.clone();
|
||||
let roast_repo = state.roast_repo.clone();
|
||||
let cafe_repo = state.cafe_repo.clone();
|
||||
let timeline_repo = state.timeline_repo.clone();
|
||||
let user_repo = state.user_repo.clone();
|
||||
let token_repo = state.token_repo.clone();
|
||||
let session_repo = state.session_repo.clone();
|
||||
|
||||
// Create router
|
||||
let app = app_router(state);
|
||||
|
||||
// Bind to a random port
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.expect("Failed to bind to random port");
|
||||
|
|
@ -244,7 +107,6 @@ async fn spawn_app_inner(
|
|||
let local_addr = listener.local_addr().expect("Failed to get local address");
|
||||
let address = format!("http://{}", local_addr);
|
||||
|
||||
// Spawn the server in a background task
|
||||
let server_handle = tokio::spawn(async move {
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
|
|
@ -260,7 +122,7 @@ async fn spawn_app_inner(
|
|||
timeline_repo,
|
||||
user_repo: Some(user_repo),
|
||||
token_repo: Some(token_repo),
|
||||
session_repo: Some(session_repo_clone),
|
||||
session_repo: Some(session_repo),
|
||||
auth_token: None,
|
||||
mock_server,
|
||||
server_handle,
|
||||
|
|
@ -280,48 +142,13 @@ pub async fn spawn_app_with_foursquare_mock() -> TestApp {
|
|||
.await
|
||||
.expect("Failed to connect to in-memory database");
|
||||
|
||||
database
|
||||
.migrate()
|
||||
.await
|
||||
.expect("Failed to migrate database");
|
||||
|
||||
let roaster_repo = Arc::new(SqlRoasterRepository::new(database.clone_pool()));
|
||||
let roast_repo = Arc::new(SqlRoastRepository::new(database.clone_pool()));
|
||||
let bag_repo = Arc::new(SqlBagRepository::new(database.clone_pool()));
|
||||
let gear_repo = Arc::new(SqlGearRepository::new(database.clone_pool()));
|
||||
let brew_repo = Arc::new(SqlBrewRepository::new(database.clone_pool()));
|
||||
let cafe_repo = Arc::new(SqlCafeRepository::new(database.clone_pool()));
|
||||
let cup_repo = Arc::new(SqlCupRepository::new(database.clone_pool()));
|
||||
let timeline_repo = Arc::new(SqlTimelineEventRepository::new(database.clone_pool()));
|
||||
let user_repo: Arc<dyn UserRepository> =
|
||||
Arc::new(SqlUserRepository::new(database.clone_pool()));
|
||||
let token_repo: Arc<dyn TokenRepository> =
|
||||
Arc::new(SqlTokenRepository::new(database.clone_pool()));
|
||||
let session_repo: Arc<dyn SessionRepository> =
|
||||
Arc::new(SqlSessionRepository::new(database.clone_pool()));
|
||||
let passkey_repo: Arc<dyn PasskeyCredentialRepository> =
|
||||
Arc::new(SqlPasskeyCredentialRepository::new(database.clone_pool()));
|
||||
let registration_token_repo: Arc<dyn RegistrationTokenRepository> =
|
||||
Arc::new(SqlRegistrationTokenRepository::new(database.clone_pool()));
|
||||
|
||||
let app = spawn_app_inner(
|
||||
database,
|
||||
roaster_repo,
|
||||
roast_repo,
|
||||
bag_repo,
|
||||
gear_repo,
|
||||
brew_repo,
|
||||
cafe_repo,
|
||||
cup_repo,
|
||||
timeline_repo,
|
||||
user_repo,
|
||||
token_repo,
|
||||
session_repo,
|
||||
passkey_repo,
|
||||
registration_token_repo,
|
||||
AppStateConfig {
|
||||
foursquare_url,
|
||||
"test-api-key".to_string(),
|
||||
brewlog::infrastructure::ai::OPENROUTER_URL.to_string(),
|
||||
foursquare_api_key: "test-api-key".to_string(),
|
||||
..test_state_config()
|
||||
},
|
||||
Some(mock_server),
|
||||
)
|
||||
.await;
|
||||
|
|
@ -628,48 +455,12 @@ pub async fn spawn_app_with_openrouter_mock() -> TestApp {
|
|||
.await
|
||||
.expect("Failed to connect to in-memory database");
|
||||
|
||||
database
|
||||
.migrate()
|
||||
.await
|
||||
.expect("Failed to migrate database");
|
||||
|
||||
let roaster_repo = Arc::new(SqlRoasterRepository::new(database.clone_pool()));
|
||||
let roast_repo = Arc::new(SqlRoastRepository::new(database.clone_pool()));
|
||||
let bag_repo = Arc::new(SqlBagRepository::new(database.clone_pool()));
|
||||
let gear_repo = Arc::new(SqlGearRepository::new(database.clone_pool()));
|
||||
let brew_repo = Arc::new(SqlBrewRepository::new(database.clone_pool()));
|
||||
let cafe_repo = Arc::new(SqlCafeRepository::new(database.clone_pool()));
|
||||
let cup_repo = Arc::new(SqlCupRepository::new(database.clone_pool()));
|
||||
let timeline_repo = Arc::new(SqlTimelineEventRepository::new(database.clone_pool()));
|
||||
let user_repo: Arc<dyn UserRepository> =
|
||||
Arc::new(SqlUserRepository::new(database.clone_pool()));
|
||||
let token_repo: Arc<dyn TokenRepository> =
|
||||
Arc::new(SqlTokenRepository::new(database.clone_pool()));
|
||||
let session_repo: Arc<dyn SessionRepository> =
|
||||
Arc::new(SqlSessionRepository::new(database.clone_pool()));
|
||||
let passkey_repo: Arc<dyn PasskeyCredentialRepository> =
|
||||
Arc::new(SqlPasskeyCredentialRepository::new(database.clone_pool()));
|
||||
let registration_token_repo: Arc<dyn RegistrationTokenRepository> =
|
||||
Arc::new(SqlRegistrationTokenRepository::new(database.clone_pool()));
|
||||
|
||||
let app = spawn_app_inner(
|
||||
database,
|
||||
roaster_repo,
|
||||
roast_repo,
|
||||
bag_repo,
|
||||
gear_repo,
|
||||
brew_repo,
|
||||
cafe_repo,
|
||||
cup_repo,
|
||||
timeline_repo,
|
||||
user_repo,
|
||||
token_repo,
|
||||
session_repo,
|
||||
passkey_repo,
|
||||
registration_token_repo,
|
||||
brewlog::infrastructure::foursquare::FOURSQUARE_SEARCH_URL.to_string(),
|
||||
String::new(),
|
||||
AppStateConfig {
|
||||
openrouter_url,
|
||||
..test_state_config()
|
||||
},
|
||||
Some(mock_server),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
Loading…
Reference in a new issue