koprogo_api/application/use_cases/
module_registry_use_cases.rs1use std::sync::Arc;
17
18use uuid::Uuid;
19
20use crate::application::error::AppError;
21use crate::application::ports::ModuleRegistry;
22use crate::application::use_cases::acp_use_cases::{AcpCaller, AcpUseCases};
23use crate::domain::entities::Module;
24
25pub struct ModuleRegistryUseCases {
26 registry: Arc<dyn ModuleRegistry>,
27 acp_use_cases: Arc<AcpUseCases>,
28}
29
30impl ModuleRegistryUseCases {
31 pub fn new(registry: Arc<dyn ModuleRegistry>, acp_use_cases: Arc<AcpUseCases>) -> Self {
32 Self {
33 registry,
34 acp_use_cases,
35 }
36 }
37
38 pub fn reconnaitre(nom: &str) -> Result<Module, AppError> {
45 Module::depuis_nom(nom).ok_or_else(|| AppError::UnknownModule {
46 module: nom.to_string(),
47 })
48 }
49
50 pub async fn list_enabled(
52 &self,
53 caller: &AcpCaller,
54 acp_id: Uuid,
55 ) -> Result<Vec<Module>, AppError> {
56 self.acp_use_cases
57 .assert_can_see_acp(caller, acp_id)
58 .await?;
59 self.registry.list_enabled(acp_id).await
60 }
61
62 pub async fn enable(
64 &self,
65 caller: &AcpCaller,
66 acp_id: Uuid,
67 module: Module,
68 ) -> Result<(), AppError> {
69 self.assert_peut_changer_letat(caller, acp_id).await?;
70 self.registry.enable(acp_id, module).await
71 }
72
73 pub async fn disable(
79 &self,
80 caller: &AcpCaller,
81 acp_id: Uuid,
82 module: Module,
83 ) -> Result<(), AppError> {
84 self.assert_peut_changer_letat(caller, acp_id).await?;
85 if module.est_toujours_actif() {
86 return Err(AppError::ModuleAlwaysOn {
87 module: module.to_string(),
88 });
89 }
90 self.registry.disable(acp_id, module).await
91 }
92
93 pub async fn is_enabled(&self, acp_id: Uuid, module: Module) -> Result<bool, AppError> {
96 if module.est_toujours_actif() {
97 return Ok(true);
98 }
99 self.registry.is_enabled(acp_id, module).await
100 }
101
102 async fn assert_peut_changer_letat(
103 &self,
104 caller: &AcpCaller,
105 acp_id: Uuid,
106 ) -> Result<(), AppError> {
107 self.acp_use_cases
108 .assert_can_see_acp(caller, acp_id)
109 .await?;
110 if !caller.can_mutate() {
111 return Err(AppError::Forbidden(
112 "Seul un administrateur peut activer ou désactiver un module".to_string(),
113 ));
114 }
115 Ok(())
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn negative_un_nom_inconnu_donne_422_et_pas_403() {
125 let err = ModuleRegistryUseCases::reconnaitre("foobar").unwrap_err();
127 assert!(matches!(err, AppError::UnknownModule { ref module } if module == "foobar"));
128 assert_eq!(
129 actix_web::ResponseError::status_code(&err),
130 actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
131 );
132 }
133
134 #[test]
135 fn happy_un_nom_connu_est_reconnu() {
136 assert_eq!(
137 ModuleRegistryUseCases::reconnaitre("accounting").unwrap(),
138 Module::Accounting
139 );
140 }
141
142 #[test]
143 fn negative_eteindre_identity_est_un_403_type_pas_une_chaine() {
144 let err = AppError::ModuleAlwaysOn {
145 module: Module::Identity.to_string(),
146 };
147 assert_eq!(
148 actix_web::ResponseError::status_code(&err),
149 actix_web::http::StatusCode::FORBIDDEN
150 );
151 }
152
153 #[test]
154 fn security_module_disabled_porte_le_nom_du_module() {
155 let err = AppError::ModuleDisabled {
158 module: "accounting".to_string(),
159 };
160 assert_eq!(
161 actix_web::ResponseError::status_code(&err),
162 actix_web::http::StatusCode::FORBIDDEN
163 );
164 assert!(err.to_string().contains("accounting"));
165 }
166}