-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathtest.rs
More file actions
167 lines (147 loc) · 5.42 KB
/
Copy pathtest.rs
File metadata and controls
167 lines (147 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Infrastructure testing and validation command
//!
//! This module contains the `TestCommandHandler` which validates deployed infrastructure
//! by running various checks to ensure services are properly installed and configured.
//!
//! ## Validation Steps
//!
//! - Cloud-init completion verification
//! - Docker installation validation
//! - Docker Compose installation validation
//!
//! The command provides comprehensive error reporting and logging to help
//! diagnose deployment issues.
use tracing::{info, instrument};
use crate::adapters::ssh::SshConfig;
use crate::application::steps::{
ValidateCloudInitCompletionStep, ValidateDockerComposeInstallationStep,
ValidateDockerInstallationStep,
};
use crate::domain::environment::Environment;
use crate::infrastructure::remote_actions::RemoteActionError;
use crate::shared::command::CommandError;
/// Comprehensive error type for the `TestCommandHandler`
#[derive(Debug, thiserror::Error)]
pub enum TestCommandHandlerError {
#[error("Command execution failed: {0}")]
Command(#[from] CommandError),
#[error("Remote action failed: {0}")]
RemoteAction(#[from] RemoteActionError),
#[error("Environment '{environment_name}' does not have an instance IP set. The environment must be provisioned before running tests.")]
MissingInstanceIp { environment_name: String },
}
/// `TestCommandHandler` orchestrates the complete infrastructure testing and validation workflow
///
/// The `TestCommandHandler` validates that an environment is properly set up with all required
/// infrastructure components.
///
/// ## Validation Steps
///
/// 1. Validate cloud-init completion
/// 2. Validate Docker installation
/// 3. Validate Docker Compose installation
///
/// ## Design Rationale
///
/// This command accepts an `Environment<S>` (any state) in its `execute` method to provide
/// flexibility for testing environments at different stages. This design:
///
/// - Aligns with `ProvisionCommandHandler`, `ConfigureCommandHandler` patterns (accept environment in execute)
/// - Allows testing environments regardless of compile-time state (runtime validation)
/// - Requires the environment to have an instance IP set (checked at runtime)
/// - Enables use in E2E tests where state tracking may not be enforced
pub struct TestCommandHandler;
impl Default for TestCommandHandler {
fn default() -> Self {
Self::new()
}
}
impl TestCommandHandler {
/// Create a new `TestCommandHandler`
#[must_use]
pub const fn new() -> Self {
Self
}
/// Execute the complete testing and validation workflow
///
/// # Arguments
///
/// * `environment` - The environment to test (must have instance IP set)
///
/// # Errors
///
/// Returns an error if:
/// * Environment does not have an instance IP set
/// * Any step in the validation workflow fails:
/// - Cloud-init completion validation fails
/// - Docker installation validation fails
/// - Docker Compose installation validation fails
#[instrument(
name = "test_command",
skip_all,
fields(
command_type = "test",
environment = %environment.name()
)
)]
pub async fn execute<S>(
&self,
environment: &Environment<S>,
) -> Result<(), TestCommandHandlerError> {
info!(
command = "test",
environment = %environment.name(),
instance_ip = ?environment.instance_ip(),
"Starting complete infrastructure testing workflow"
);
let instance_ip = environment.instance_ip().ok_or_else(|| {
TestCommandHandlerError::MissingInstanceIp {
environment_name: environment.name().to_string(),
}
})?;
let ssh_config =
SshConfig::with_default_port(environment.ssh_credentials().clone(), instance_ip);
ValidateCloudInitCompletionStep::new(ssh_config.clone())
.execute()
.await?;
ValidateDockerInstallationStep::new(ssh_config.clone())
.execute()
.await?;
ValidateDockerComposeInstallationStep::new(ssh_config)
.execute()
.await?;
info!(
command = "test",
environment = %environment.name(),
instance_ip = ?environment.instance_ip(),
"Infrastructure testing workflow completed successfully"
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_create_test_command() {
let _command_handler = TestCommandHandler::new();
// Verify the command was created (basic structure test)
// TestCommandHandler is now a zero-sized type that takes environment in execute()
}
#[test]
fn it_should_have_correct_error_type_conversions() {
// Test that all error types can convert to TestCommandHandlerError
let command_error = CommandError::StartupFailed {
command: "test".to_string(),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "test"),
};
let test_error: TestCommandHandlerError = command_error.into();
drop(test_error);
let remote_action_error = RemoteActionError::ValidationFailed {
action_name: "test".to_string(),
message: "test error".to_string(),
};
let test_error: TestCommandHandlerError = remote_action_error.into();
drop(test_error);
}
}