Based on the late-night notes, a commit was created, which triggered the dominoes. I need to rewrite quite a few things, which will later facilitate writing tests and make them more precise.
What’s changed? Link to heading
The test now directly calls the Axum handler, so I can write tests for the states that occur there. For example: If I call the endpoint with an invalid token, I expect a 401 (Unauthorized) HTTP response, or if I write a wrong URI in the request, I expect a 404 (Not Found) HTTP response. Previously, with tests that directly called the internal function, this was not possible.
In any case, I have a lot to adjust to this new concept, so I already have something to do for today.
Here is how the new test looks now:
1#[cfg(test)]
2mod tests {
3 use super::*;
4 use crate::app::app_state::AppStateBuilder;
5 use crate::app::config::{AppConfig, BasicDatabaseConfig, DatabaseUrlProvider};
6 use crate::app::database::{DatabaseMigrator, MockDatabaseMigrator, MockPgPoolManagerTrait};
7 use crate::app::init::app;
8 use crate::auth::AuthModule;
9 use crate::auth::dto::claims::Claims;
10 use crate::auth::service::Argon2Hasher;
11 use crate::tenants::model::Tenant;
12 use crate::tenants::repository::{MockTenantsRepository, TenantsRepository};
13 use crate::users::UsersModule;
14 use axum::body::Body;
15 use axum::http::Request;
16 use chrono::Local;
17 use sqlx::postgres::PgPoolOptions;
18 use std::ops::Add;
19 use std::time::Duration;
20 use tower::ServiceExt;
21 use uuid::Uuid;
22 use http_body_util::BodyExt;
23 use crate::common::dto::{OkResponse, SimpleMessageResponse};
24
25 #[tokio::test]
26 async fn test_create_managed_success() {
27 let mut pool_manager_mock = MockPgPoolManagerTrait::new();
28 pool_manager_mock
29 .expect_add_tenant_pool()
30 .times(1)
31 .returning(|tenant_id, _| Ok(tenant_id));
32 pool_manager_mock
33 .expect_get_tenant_pool()
34 .times(1)
35 .returning(|_| {
36 let database_config = BasicDatabaseConfig::default();
37 Ok(Some(
38 PgPoolOptions::new()
39 .connect_lazy(&database_config.url())
40 .unwrap(),
41 ))
42 });
43 let pool_manager_mock = Arc::new(pool_manager_mock);
44
45 let repo_factory = Box::new(|| {
46 let mut repo = MockTenantsRepository::new();
47 repo.expect_setup_managed()
48 .times(1)
49 .withf(|_, name, _, _, _| name == "test")
50 .returning(|uuid: Uuid, _, _, _, _| {
51 Ok(Tenant {
52 id: uuid,
53 name: "test".to_string(),
54 db_host: "localhost".to_string(),
55 db_port: 5432,
56 db_name: "database".to_string(),
57 db_user: "user".to_string(),
58 db_password: "password".to_string(),
59 db_max_pool_size: 5,
60 db_ssl_mode: "disable".to_string(),
61 created_at: Local::now(),
62 updated_at: Local::now(),
63 deleted_at: None,
64 })
65 });
66 Box::new(repo) as Box<dyn TenantsRepository + Send + Sync>
67 });
68
69 let migrator_factory = Box::new(|| {
70 let mut migrator = MockDatabaseMigrator::new();
71 migrator
72 .expect_migrate_tenant_db()
73 .times(1)
74 .returning(|_| Ok(()));
75 Box::new(migrator) as Box<dyn DatabaseMigrator + Send + Sync>
76 });
77
78 let config = Arc::new(AppConfig::default());
79
80 let payload = serde_json::to_string(&TenantCreateRequestHelper {
81 name: String::from("test"),
82 is_self_hosted: false,
83 db_host: None,
84 db_port: None,
85 db_name: None,
86 db_user: None,
87 db_password: None,
88 })
89 .unwrap();
90
91 let exp = Local::now().add(Duration::from_secs(100)).timestamp();
92 let iat = Local::now().timestamp();
93 let nbf = Local::now().timestamp();
94
95 let bearer = Claims::new(
96 Uuid::new_v4().to_string(),
97 usize::try_from(exp).unwrap(),
98 usize::try_from(iat).unwrap(),
99 usize::try_from(nbf).unwrap(),
100 config.auth().jwt_issuer().to_string(),
101 config.auth().jwt_audience().to_string(),
102 Uuid::new_v4().to_string(),
103 )
104 .to_token(config.auth().jwt_secret().as_bytes())
105 .unwrap();
106
107 let request = Request::builder()
108 .header("Authorization", format!("Bearer {}", bearer))
109 .header("Content-Type", "application/json")
110 .method("POST")
111 .uri("/tenants/create")
112 .body(Body::from(payload))
113 .unwrap();
114
115 let app_state = Arc::new(
116 AppStateBuilder::new()
117 .users_module(Arc::new(UsersModule {}))
118 .config_module(config.clone())
119 .tenants_module(Arc::new(TenantsModule {
120 pool_manager: pool_manager_mock.clone(),
121 config: config.clone(),
122 repo_factory,
123 migrator_factory,
124 }))
125 .auth_module(Arc::new(AuthModule {
126 pool_manager: pool_manager_mock.clone(),
127 password_hasher: Arc::new(Argon2Hasher),
128 config: config.clone(),
129 }))
130 .build()
131 .unwrap(),
132 );
133 let app = app(app_state).await;
134
135 let response = app.oneshot(request).await.unwrap();
136
137 assert_eq!(response.status(), StatusCode::CREATED);
138
139 let body = response.into_body().collect().await.unwrap().to_bytes();
140
141 let expected_response = serde_json::to_string(&OkResponse::new(SimpleMessageResponse {
142 message: String::from(
143 "Szervezeti egység létrehozása sikeresen megtörtént!",
144 ),
145 })).unwrap();
146
147 assert_eq!(&body[..], expected_response.as_bytes());
148 }
149}