Skip to main content

atrg_testing/
test_app.rs

1//! Test application builder — in-memory everything, no network.
2
3use atrg_auth::session;
4use atrg_core::config::{AppConfig, AuthConfig, Config, DatabaseConfig};
5use atrg_core::state::AppState;
6use atrg_db::DbPool;
7use std::sync::Arc;
8
9/// Build a test [`AppState`] backed by in-memory SQLite.
10///
11/// Migrations are applied automatically. No outbound network calls are made.
12pub async fn test_state() -> AppState {
13    let db = atrg_db::connect("sqlite::memory:").await.expect("test db");
14    atrg_db::run_internal_migrations(&db)
15        .await
16        .expect("migrations");
17
18    let config = Config {
19        app: AppConfig {
20            name: "test-app".into(),
21            host: "127.0.0.1".into(),
22            port: 3000,
23            secret_key: "test-secret-key-at-least-32-chars!!".into(),
24            cors_origins: vec![],
25            environment: "development".into(),
26            admin_dids: vec![],
27        },
28        auth: AuthConfig {
29            client_id: "http://localhost:3000/client-metadata.json".into(),
30            redirect_uri: "http://localhost:3000/auth/callback".into(),
31            scope: "atproto transition:generic".into(),
32            post_login_redirect: "/".into(),
33        },
34        database: DatabaseConfig {
35            url: "sqlite::memory:".into(),
36        },
37        jetstream: None,
38        firehose: None,
39        feed_generator: None,
40        labeler: None,
41        rate_limit: None,
42    };
43
44    AppState {
45        config: Arc::new(config),
46        db,
47        http: reqwest::Client::new(),
48        identity: Arc::new(atrg_identity::IdentityResolver::with_defaults(
49            reqwest::Client::new(),
50        )),
51        extensions: std::sync::Arc::new(atrg_core::Extensions::new()),
52    }
53}
54
55/// Build a test [`AppState`] and an [`atrg_core::AtrgApp`] ready for `oneshot` testing.
56pub async fn test_app() -> (atrg_core::AtrgApp, AppState) {
57    let state = test_state().await;
58    let app = atrg_core::AtrgApp::new();
59    (app, state)
60}
61
62/// Seed a session into the test database for a given user.
63///
64/// Returns the session ID that can be used in `Authorization: Bearer <id>`
65/// or `Cookie: atrg_session=<id>` headers.
66pub async fn seed_session(pool: &DbPool, did: &str, handle: &str) -> String {
67    let session_id = session::generate_session_id();
68    let expires = std::time::SystemTime::now()
69        .duration_since(std::time::UNIX_EPOCH)
70        .expect("system clock before UNIX epoch")
71        .as_secs() as i64
72        + 86400; // 24 hours
73
74    session::create_session(
75        pool,
76        &session_id,
77        did,
78        handle,
79        "test_access_token",
80        Some("test_refresh_token"),
81        expires,
82    )
83    .await
84    .expect("seed session");
85
86    session_id
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[tokio::test]
94    async fn test_state_works() {
95        let state = test_state().await;
96        assert_eq!(state.config.app.name, "test-app");
97    }
98
99    #[tokio::test]
100    async fn test_app_works() {
101        let (_app, state) = test_app().await;
102        assert_eq!(state.config.app.name, "test-app");
103    }
104
105    #[tokio::test]
106    async fn seed_and_find_session() {
107        let state = test_state().await;
108        let sid = seed_session(&state.db, "did:plc:test", "test.user").await;
109        assert!(!sid.is_empty());
110
111        let found = session::find_session(&state.db, &sid).await.unwrap();
112        assert!(found.is_some());
113        let s = found.unwrap();
114        assert_eq!(s.did, "did:plc:test");
115        assert_eq!(s.handle, "test.user");
116    }
117}