torrust_tracker_deployer_lib/presentation/cli/controllers/test/
handler.rs1use std::cell::RefCell;
7use std::sync::Arc;
8
9use parking_lot::ReentrantMutex;
10
11use crate::application::command_handlers::test::result::TestResult;
12use crate::application::command_handlers::TestCommandHandler;
13use crate::domain::environment::name::EnvironmentName;
14use crate::domain::environment::repository::EnvironmentRepository;
15use crate::presentation::cli::input::cli::OutputFormat;
16use crate::presentation::cli::views::commands::test::{JsonView, TestResultData, TextView};
17use crate::presentation::cli::views::progress::ProgressReporter;
18use crate::presentation::cli::views::Render;
19use crate::presentation::cli::views::UserOutput;
20
21use super::errors::TestSubcommandError;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25enum TestStep {
26 ValidateEnvironment,
27 CreateCommandHandler,
28 TestInfrastructure,
29}
30
31impl TestStep {
32 const ALL: &'static [Self] = &[
34 Self::ValidateEnvironment,
35 Self::CreateCommandHandler,
36 Self::TestInfrastructure,
37 ];
38
39 const fn count() -> usize {
41 Self::ALL.len()
42 }
43
44 fn description(self) -> &'static str {
46 match self {
47 Self::ValidateEnvironment => "Validating environment",
48 Self::CreateCommandHandler => "Creating command handler",
49 Self::TestInfrastructure => "Testing infrastructure",
50 }
51 }
52}
53
54pub struct TestCommandController {
76 repository: Arc<dyn EnvironmentRepository>,
77 progress: ProgressReporter,
78}
79
80impl TestCommandController {
81 #[allow(clippy::needless_pass_by_value)] pub fn new(
90 repository: Arc<dyn EnvironmentRepository + Send + Sync>,
91 user_output: Arc<ReentrantMutex<RefCell<UserOutput>>>,
92 ) -> Self {
93 let progress = ProgressReporter::new(user_output, TestStep::count());
94
95 Self {
96 repository,
97 progress,
98 }
99 }
100
101 pub async fn execute(
117 &mut self,
118 environment_name: &str,
119 output_format: OutputFormat,
120 ) -> Result<(), TestSubcommandError> {
121 let env_name = self.validate_environment_name(environment_name)?;
123
124 let handler = self.create_command_handler()?;
126
127 let result = self.fixture_infrastructure(&handler, &env_name).await?;
129
130 self.complete_workflow(environment_name, &result, output_format)?;
132
133 Ok(())
134 }
135
136 fn validate_environment_name(
142 &mut self,
143 name: &str,
144 ) -> Result<EnvironmentName, TestSubcommandError> {
145 self.progress
146 .start_step(TestStep::ValidateEnvironment.description())?;
147
148 let env_name = EnvironmentName::new(name.to_string()).map_err(|source| {
149 TestSubcommandError::InvalidEnvironmentName {
150 name: name.to_string(),
151 source,
152 }
153 })?;
154
155 self.progress
156 .complete_step(Some(&format!("Environment name validated: {name}")))?;
157
158 Ok(env_name)
159 }
160
161 fn create_command_handler(&mut self) -> Result<TestCommandHandler, TestSubcommandError> {
167 self.progress
168 .start_step(TestStep::CreateCommandHandler.description())?;
169
170 let handler = TestCommandHandler::new(self.repository.clone());
171 self.progress.complete_step(None)?;
172
173 Ok(handler)
174 }
175
176 async fn fixture_infrastructure(
186 &mut self,
187 handler: &TestCommandHandler,
188 env_name: &EnvironmentName,
189 ) -> Result<TestResult, TestSubcommandError> {
190 self.progress
191 .start_step(TestStep::TestInfrastructure.description())?;
192
193 let result = handler.execute(env_name).await.map_err(|source| {
194 TestSubcommandError::ValidationFailed {
195 name: env_name.to_string(),
196 source: Box::new(source),
197 }
198 })?;
199
200 for warning in &result.dns_warnings {
202 self.progress
203 .output()
204 .lock()
205 .borrow_mut()
206 .warn(&format!("DNS check: {warning}"));
207 }
208
209 let step_message = if result.has_dns_warnings() {
210 "Infrastructure tests passed (with DNS warnings)"
211 } else {
212 "Infrastructure tests passed"
213 };
214
215 self.progress.complete_step(Some(step_message))?;
216
217 Ok(result)
218 }
219
220 fn complete_workflow(
226 &mut self,
227 environment_name: &str,
228 result: &TestResult,
229 output_format: OutputFormat,
230 ) -> Result<(), TestSubcommandError> {
231 let data = TestResultData::new(environment_name, result);
232
233 let output = match output_format {
234 OutputFormat::Text => TextView::render(&data)?,
235 OutputFormat::Json => JsonView::render(&data)?,
236 };
237
238 self.progress.result(&output)?;
239
240 Ok(())
241 }
242}