max / makenotwork
2 files changed,
+42 insertions,
-67 deletions
| @@ -17,10 +17,9 @@ use docengine::DocLoader; | |||
| 17 | 17 | use makenotwork::email::{EmailClient, EmailConfig}; | |
| 18 | 18 | use makenotwork::payments::{PaymentProvider, StripeClient}; | |
| 19 | 19 | use makenotwork::scanning::ScanPipeline; | |
| 20 | - | use makenotwork::{build_app, AppCaches, AppLimiters, AppState, AppStorage}; | |
| 20 | + | use makenotwork::{build_app, AppState, AppStateParts, AppStorage}; | |
| 21 | 21 | use sqlx::PgPool; | |
| 22 | 22 | use std::sync::Arc; | |
| 23 | - | use std::time::Instant; | |
| 24 | 23 | use tower_sessions::cookie::time::Duration as CookieDuration; | |
| 25 | 24 | use tower_sessions::cookie::SameSite; | |
| 26 | 25 | use tower_sessions::{Expiry, SessionManagerLayer}; | |
| @@ -375,9 +374,27 @@ impl TestHarness { | |||
| 375 | 374 | // no bucket isolation) so the cross-bucket image promote resolves. | |
| 376 | 375 | let public_s3 = s3.clone(); | |
| 377 | 376 | ||
| 378 | - | let state = AppState { | |
| 377 | + | // Route through the same `AppState::build` constructor production uses | |
| 378 | + | // (main.rs), so the derived in-memory state — start timestamps, the empty | |
| 379 | + | // cache maps, the concurrency semaphores — has a single source of truth. | |
| 380 | + | // The harness only supplies the externally-wired dependencies. | |
| 381 | + | let state = AppState::build(AppStateParts { | |
| 379 | 382 | db: pool.clone(), | |
| 380 | 383 | config, | |
| 384 | + | storage: AppStorage { | |
| 385 | + | s3, | |
| 386 | + | synckit_s3, | |
| 387 | + | public_s3, | |
| 388 | + | }, | |
| 389 | + | stripe: opts.stripe_client, | |
| 390 | + | email, | |
| 391 | + | docs: Arc::new(DocLoader::load(std::path::Path::new("."), &docengine::DocLoaderConfig { | |
| 392 | + | sections: vec![], | |
| 393 | + | link_prefix: "/docs".to_string(), | |
| 394 | + | unpublished_pattern: None, | |
| 395 | + | examples_path: None, | |
| 396 | + | pre_process: None, | |
| 397 | + | })), | |
| 381 | 398 | tier_prices: { | |
| 382 | 399 | // Install the process-global TierPrices so handler code paths | |
| 383 | 400 | // that call CreatorTier::{price_cents,max_file_bytes, | |
| @@ -390,47 +407,18 @@ impl TestHarness { | |||
| 390 | 407 | pricing_comparison: makenotwork::pricing_comparison::PricingComparison::load( | |
| 391 | 408 | "docs/business/assumptions.toml", | |
| 392 | 409 | ), | |
| 393 | - | stripe: opts.stripe_client, | |
| 394 | - | email, | |
| 395 | - | docs: Arc::new(DocLoader::load(std::path::Path::new("."), &docengine::DocLoaderConfig { | |
| 396 | - | sections: vec![], | |
| 397 | - | link_prefix: "/docs".to_string(), | |
| 398 | - | unpublished_pattern: None, | |
| 399 | - | examples_path: None, | |
| 400 | - | pre_process: None, | |
| 401 | - | })), | |
| 402 | 410 | scanner: opts.scanner, | |
| 403 | 411 | webauthn, | |
| 404 | 412 | syntax: None, | |
| 405 | - | started_at: chrono::Utc::now(), | |
| 406 | - | start_instant: Instant::now(), | |
| 407 | 413 | mt_client: opts.mt_base_url.zip(opts.internal_shared_secret).map( | |
| 408 | 414 | |(url, secret)| makenotwork::mt_client::MtClient::new(url, secret), | |
| 409 | 415 | ), | |
| 410 | 416 | wam: None, | |
| 411 | - | restart_at: Arc::new(std::sync::atomic::AtomicI64::new(0)), | |
| 417 | + | domain_cache: Arc::new(dashmap::DashMap::new()), | |
| 412 | 418 | metrics_handle: None, | |
| 413 | 419 | page_view_tx: makenotwork::db::page_views::spawn_batcher(pool.clone()), | |
| 414 | 420 | bg: makenotwork::background::spawn_pool_detached(), | |
| 415 | - | storage: AppStorage { | |
| 416 | - | s3, | |
| 417 | - | synckit_s3, | |
| 418 | - | public_s3, | |
| 419 | - | }, | |
| 420 | - | caches: AppCaches { | |
| 421 | - | session_cache: Arc::new(dashmap::DashMap::new()), | |
| 422 | - | domain_cache: Arc::new(dashmap::DashMap::new()), | |
| 423 | - | sync_notify: Arc::new(dashmap::DashMap::new()), | |
| 424 | - | sse_connections: Arc::new(dashmap::DashMap::new()), | |
| 425 | - | }, | |
| 426 | - | limiters: AppLimiters { | |
| 427 | - | scan_semaphore: Arc::new(tokio::sync::Semaphore::new(4)), | |
| 428 | - | caddy_ask_semaphore: Arc::new(tokio::sync::Semaphore::new(8)), | |
| 429 | - | git_smart_http_semaphore: Arc::new(tokio::sync::Semaphore::new( | |
| 430 | - | makenotwork::constants::GIT_SMART_HTTP_MAX_CONCURRENT, | |
| 431 | - | )), | |
| 432 | - | }, | |
| 433 | - | }; | |
| 421 | + | }); | |
| 434 | 422 | ||
| 435 | 423 | // Capture scan deps before `build_app` consumes `state`. | |
| 436 | 424 | let scan_deps = match (state.scanner.clone(), state.storage.s3.clone()) { |
| @@ -14,7 +14,7 @@ use tower_sessions_sqlx_store::PostgresStore; | |||
| 14 | 14 | use makenotwork::config::{BuildConfig, Config, CreatorTierPricing, EmailWebhookConfig, IntegrationsConfig}; | |
| 15 | 15 | use docengine::DocLoader; | |
| 16 | 16 | use makenotwork::email::{EmailClient, EmailConfig}; | |
| 17 | - | use makenotwork::{build_app, AppCaches, AppLimiters, AppState, AppStorage}; | |
| 17 | + | use makenotwork::{build_app, AppState, AppStateParts, AppStorage}; | |
| 18 | 18 | ||
| 19 | 19 | use crate::harness::client::TestClient; | |
| 20 | 20 | use crate::harness::db::TestDb; | |
| @@ -111,19 +111,16 @@ pub async fn run(config: LoadConfig) { | |||
| 111 | 111 | .expect("Webauthn"), | |
| 112 | 112 | ); | |
| 113 | 113 | ||
| 114 | - | let state = AppState { | |
| 114 | + | // Route through the shared `AppState::build` constructor (same as main.rs | |
| 115 | + | // and the integration harness) — derived in-memory state has one source. | |
| 116 | + | let state = AppState::build(AppStateParts { | |
| 115 | 117 | db: pool.clone(), | |
| 116 | 118 | config: app_config, | |
| 117 | - | tier_prices: { | |
| 118 | - | // Install the process-global TierPrices so CreatorTier accessors | |
| 119 | - | // work under the load harness (they read the global). Idempotent. | |
| 120 | - | makenotwork::tier_prices::TierPrices::install_test_default(); | |
| 121 | - | makenotwork::tier_prices::TierPrices::global().clone() | |
| 119 | + | storage: AppStorage { | |
| 120 | + | s3: None, | |
| 121 | + | synckit_s3: None, | |
| 122 | + | public_s3: None, | |
| 122 | 123 | }, | |
| 123 | - | runway_config: makenotwork::tier_prices::RunwayConfig::default(), | |
| 124 | - | pricing_comparison: makenotwork::pricing_comparison::PricingComparison::load( | |
| 125 | - | "docs/business/assumptions.toml", | |
| 126 | - | ), | |
| 127 | 124 | stripe: None, | |
| 128 | 125 | email, | |
| 129 | 126 | docs: Arc::new(DocLoader::load(std::path::Path::new("."), &docengine::DocLoaderConfig { | |
| @@ -133,36 +130,26 @@ pub async fn run(config: LoadConfig) { | |||
| 133 | 130 | examples_path: None, | |
| 134 | 131 | pre_process: None, | |
| 135 | 132 | })), | |
| 133 | + | tier_prices: { | |
| 134 | + | // Install the process-global TierPrices so CreatorTier accessors | |
| 135 | + | // work under the load harness (they read the global). Idempotent. | |
| 136 | + | makenotwork::tier_prices::TierPrices::install_test_default(); | |
| 137 | + | makenotwork::tier_prices::TierPrices::global().clone() | |
| 138 | + | }, | |
| 139 | + | runway_config: makenotwork::tier_prices::RunwayConfig::default(), | |
| 140 | + | pricing_comparison: makenotwork::pricing_comparison::PricingComparison::load( | |
| 141 | + | "docs/business/assumptions.toml", | |
| 142 | + | ), | |
| 136 | 143 | scanner: None, | |
| 137 | 144 | webauthn, | |
| 138 | 145 | syntax: None, | |
| 139 | - | started_at: chrono::Utc::now(), | |
| 140 | - | start_instant: Instant::now(), | |
| 141 | 146 | mt_client: None, | |
| 142 | 147 | wam: None, | |
| 143 | - | restart_at: Arc::new(std::sync::atomic::AtomicI64::new(0)), | |
| 148 | + | domain_cache: Arc::new(dashmap::DashMap::new()), | |
| 144 | 149 | metrics_handle: None, | |
| 145 | 150 | page_view_tx: makenotwork::db::page_views::spawn_batcher(pool.clone()), | |
| 146 | 151 | bg: makenotwork::background::spawn_pool_detached(), | |
| 147 | - | storage: AppStorage { | |
| 148 | - | s3: None, | |
| 149 | - | synckit_s3: None, | |
| 150 | - | public_s3: None, | |
| 151 | - | }, | |
| 152 | - | caches: AppCaches { | |
| 153 | - | session_cache: Arc::new(dashmap::DashMap::new()), | |
| 154 | - | domain_cache: Arc::new(dashmap::DashMap::new()), | |
| 155 | - | sync_notify: Arc::new(dashmap::DashMap::new()), | |
| 156 | - | sse_connections: Arc::new(dashmap::DashMap::new()), | |
| 157 | - | }, | |
| 158 | - | limiters: AppLimiters { | |
| 159 | - | scan_semaphore: Arc::new(tokio::sync::Semaphore::new(4)), | |
| 160 | - | caddy_ask_semaphore: Arc::new(tokio::sync::Semaphore::new(8)), | |
| 161 | - | git_smart_http_semaphore: Arc::new(tokio::sync::Semaphore::new( | |
| 162 | - | makenotwork::constants::GIT_SMART_HTTP_MAX_CONCURRENT, | |
| 163 | - | )), | |
| 164 | - | }, | |
| 165 | - | }; | |
| 152 | + | }); | |
| 166 | 153 | ||
| 167 | 154 | let app = build_app(state, session_layer); | |
| 168 | 155 |