Hosszú nap! Ezer dolgot csináltál, és kicsit még a fejed is fáj. Húzzál aludni! Szól a népi bölcsesség. Nyugis zene, egy óra kikapcsolás időzítő, leteszed a fejed és abban a pillanatban tiltáson pörög az agyad és rájössz, hogy valószínűleg meg tudod oldani azt a problémát, ami már egy ideje idegesít a programodban. De hát holnap is lesz nap, nem igaz? De mi van, ha elfelejtem? Here we go again!

Nézzük hát, ha összeírom vázlatba, amit nem szeretnék elfelejteni, sikerül-e elaludnom!

A probléma Link to heading

A teszteket úgy szeretném futtatni, hogy a tényleges adatbázisműveleteket ne végezze el a program a tesztek során. Ennek több oka is van. Egyrészt jelentősen lassítaná a teszteket, másrészt a tesztek kimenetele nem lenne mindig következetes, attól függően, hogy mit felejtek el kitakarítani a tesztadatbázisból. Harmadrészt a Jenkins pipeline-okból amúgy sem tudnék normálisan olyan tesztet futtatni, ami tényleges adatbázisműveletet végez. Röviden: van oka bőven.

 1pub async fn create_inner<F, M>(
 2    claims: Claims,
 3    organizational_units_module: Arc<OrganizationalUnitsModule>,
 4    payload: Result<Json<CreateRequestHelper>, JsonRejection>,
 5    repo_factory: F,
 6    migrator_factory: M,
 7) -> Response
 8where
 9    F: Fn() -> Box<dyn OrganizationalUnitsRepository + Send + Sync>,
10    M: Fn() -> Box<dyn DatabaseMigrator + Send + Sync>,
11{
12    match payload {
13        Ok(Json(payload)) => match CreateRequest::try_from(payload) {
14            Ok(user_input) => {
15                let mut repo = repo_factory();
16                let migrator = migrator_factory();
17                match try_create(
18                    &mut *repo,
19                    &*migrator,
20                    claims,
21                    user_input,
22                    organizational_units_module,
23                )
24                    .await
25                {
26                    Ok(resp) => (StatusCode::CREATED, Json(resp)).into_response(),
27                    Err(e) => e.into_response(),
28                }
29            }
30            Err(e) => e.into_response(),
31        },
32        Err(_) => FriendlyError::UserFacing(
33            StatusCode::BAD_REQUEST,
34            "ORGANIZATIONAL_UNITS/HANDLER/CREATE".to_string(),
35            "Invalid JSON".to_string(),
36        )
37            .trace(Level::DEBUG)
38            .into_response(),
39    }
40}
41
42#[debug_handler]
43pub async fn create(
44    AuthenticatedUser(claims): AuthenticatedUser,
45    State(organizational_unit_module): State<Arc<OrganizationalUnitsModule>>,
46    payload: Result<Json<CreateRequestHelper>, JsonRejection>,
47) -> Response {
48    create_inner(
49        claims,
50        organizational_unit_module.clone(),
51        payload,
52        || {
53            Box::new(PoolWrapper::new(
54                organizational_unit_module
55                    .pool_manager
56                    .get_default_tenant_pool(),
57            ))
58        },
59        || Box::new(PgDatabaseMigrator),
60    )
61        .await
62}

A másik probléma Link to heading

Ahhoz, hogy meg tudjam csinálni, hogy ne közvetlenül hívjam az adatbázist, mockolnom kell, ami Rustban nem annyira egyszerű feladat. A típusok, a borrow checker és a többi, de nagyjából ki lett találva. Leszámítva, hogy nem közvetlenül az Axum handler-t hívom meg, hanem készítettem belőle egy belső függvényt, amit tudok tesztelni. Ezzel viszont a tesztek megkerülik a middleware-eket, ezért nagyvonalúan elhiszik, hogy az autorizáció és az autentikáció működik.

És mi jutott eszembe, amitől nem jön álom a szememre? (bocs, nem hagyhattam ki fáradt vagyok!) Link to heading

Mi történne akkor, ha a két zárvány inicializáció nem a handlerben történne meg, hanem az OrganizationalUnitsModule inicializálásánál? Mivel ebből a kontextusból úgysem kell semmi a repository_factory-nak, és a PgDatabaseMigrator amúgy is később kapja a paramétert, amivel dolgozik.

 1#[debug_handler]
 2pub async fn create(
 3    AuthenticatedUser(claims): AuthenticatedUser,
 4    State(organizational_unit_module): State<Arc<OrganizationalUnitsModule>>,
 5    payload: Result<Json<CreateRequestHelper>, JsonRejection>,
 6) -> Response {
 7    create_inner(
 8        claims,
 9        organizational_unit_module.clone(),
10        payload,
11        || { // << Rád nézek, igen!
12            Box::new(PoolWrapper::new(
13                organizational_unit_module
14                    .pool_manager
15                    .get_default_tenant_pool(),
16            ))
17        },
18        || Box::new(PgDatabaseMigrator), // << Meg rád
19    )
20        .await
21}

Szóval az OrganizationalUnitsModule lehetne valami ilyesmi:

1pub struct OrganizationalUnitsModule {
2    pub pool_manager: Arc<dyn PgPoolManagerTrait>,
3    pub config: Arc<AppConfig>,
4    pub repo_factory: Arc<dyn Fn() -> Box<dyn OrganizationalUnitsRepository + Send + Sync> + Send + Sync>,
5    pub migrator_factory: Arc<dyn Fn() -> Box<dyn DatabaseMigrator + Send + Sync> + Send + Sync>,
6}

És, hogy ez működni fog-e az már talán a holnap Dávid problémája!

Jó éjt!

Persze tudni kéne hívni a handlert a tesztekből! Link to heading

Erre talátam már korábban megoldást: tower::ServiceExt-ben van egy oneshot() függvény! :)

Tényleg jó éjt! :))