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! :))