Skip to content

Commit 13cca4c

Browse files
committed
refactor: [#220] make E2eConfigEnvironment single source of truth for E2E test configuration
- Add E2eConfigEnvironment::new() constructor for direct instantiation - Add E2eConfigEnvironment::to_json_config() to generate JSON from struct values - Implement Default trait for TrackerPorts (instead of custom method) - Refactor build_e2e_test_config() to build struct in-memory without file I/O - Add write_environment_config() helper to separate file writing from struct creation - Move config file writing to run_deployer_workflow() where it's actually needed - Rename generate_environment_config_with_port() to build_e2e_test_config() for clarity Benefits: - E2eConfigEnvironment is now the authoritative source, not the file - Better testability: can work with config in-memory without file I/O - Clearer data flow: build struct → use it → write file when needed - Configuration values come from struct, not hardcoded defaults - Function name clearly indicates E2E scope without misleading port parameter
1 parent 638561e commit 13cca4c

File tree

4 files changed

+188
-31
lines changed

4 files changed

+188
-31
lines changed

src/bin/e2e_config_and_release_tests.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ use torrust_tracker_deployer_lib::testing::e2e::containers::{
7373
RunningProvisionedContainer, StoppedProvisionedContainer,
7474
};
7575
use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::{
76-
generate_environment_config_with_port, run_container_preflight_cleanup,
77-
verify_required_dependencies, E2eTestRunner,
76+
build_e2e_test_config, run_container_preflight_cleanup, verify_required_dependencies,
77+
write_environment_config, E2eTestRunner,
7878
};
7979
use torrust_tracker_deployer_lib::testing::e2e::tasks::container::cleanup_infrastructure::stop_test_infrastructure;
8080
use torrust_tracker_deployer_lib::testing::e2e::tasks::run_configuration_validation::run_configuration_validation;
@@ -213,20 +213,25 @@ async fn run_configure_release_run_tests() -> Result<()> {
213213
// Build SSH credentials
214214
let ssh_credentials = build_test_ssh_credentials();
215215

216-
// Step 1: Generate environment configuration
217-
// This returns configuration with desired ports from environment.json
218-
let config_env = generate_environment_config_with_port(ENVIRONMENT_NAME)?;
216+
// Step 1: Build E2E test configuration in-memory
217+
// This creates the configuration structure without file I/O
218+
let config_env = build_e2e_test_config(ENVIRONMENT_NAME);
219219

220220
// Step 2: Create and start Docker container
221221
// With bridge networking, Docker assigns random mapped ports
222222
// Returns runtime environment with both config and actual mapped ports
223223
let (runtime_env, running_container) = create_and_start_container(&config_env).await?;
224224

225225
// Get SSH socket address from runtime environment (using actual mapped port)
226-
let socket_addr = runtime_env.ssh_socket_addr();
226+
let ssh_socket_address = runtime_env.ssh_socket_addr();
227227

228228
// Step 3: Establish SSH connectivity using the mapped SSH port
229-
establish_ssh_connectivity(socket_addr, &ssh_credentials, Some(&running_container)).await?;
229+
establish_ssh_connectivity(
230+
ssh_socket_address,
231+
&ssh_credentials,
232+
Some(&running_container),
233+
)
234+
.await?;
230235

231236
// Step 4: Run deployer commands (black-box via CLI)
232237
let test_result = run_deployer_workflow(&config_env, &runtime_env, &ssh_credentials).await;
@@ -253,6 +258,9 @@ async fn run_deployer_workflow(
253258
) -> Result<()> {
254259
let test_runner = E2eTestRunner::new(ENVIRONMENT_NAME);
255260

261+
// Write environment configuration to disk (needed by create command)
262+
write_environment_config(config_env)?;
263+
256264
// Create environment (CLI: cargo run -- create environment --env-file <file>)
257265
test_runner.create_environment(&config_env.config_file_path)?;
258266

src/testing/e2e/containers/tracker_ports.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,90 @@ pub struct E2eConfigEnvironment {
2626
}
2727

2828
impl E2eConfigEnvironment {
29+
/// Create E2E config environment directly from values
30+
///
31+
/// This is the primary constructor that builds the configuration in-memory
32+
/// without requiring file I/O. Use this when you want to work with the
33+
/// configuration before writing it to disk.
34+
///
35+
/// # Arguments
36+
/// * `environment_name` - Name of the environment
37+
/// * `config_file_path` - Path where config will be written (if needed)
38+
/// * `ssh_port` - SSH port to use
39+
/// * `tracker_ports` - Tracker port configuration
40+
#[must_use]
41+
pub fn new(
42+
environment_name: String,
43+
config_file_path: PathBuf,
44+
ssh_port: u16,
45+
tracker_ports: TrackerPorts,
46+
) -> Self {
47+
Self {
48+
environment_name,
49+
config_file_path,
50+
ssh_port,
51+
tracker_ports,
52+
}
53+
}
54+
55+
/// Generate JSON configuration string from this E2E environment
56+
///
57+
/// Creates a complete environment configuration JSON using the values
58+
/// from this struct, with absolute paths to SSH keys.
59+
///
60+
/// # Returns
61+
///
62+
/// Returns a JSON string ready to be written to the environment config file.
63+
///
64+
/// # Example
65+
///
66+
/// ```rust,ignore
67+
/// let env_info = E2eConfigEnvironment::new(...);
68+
/// let json = env_info.to_json_config();
69+
/// ```
70+
#[must_use]
71+
pub fn to_json_config(&self) -> String {
72+
// Use compile-time constant to get project root - more reliable than current_dir()
73+
let project_root = env!("CARGO_MANIFEST_DIR");
74+
let private_key_path = format!("{project_root}/fixtures/testing_rsa");
75+
let public_key_path = format!("{project_root}/fixtures/testing_rsa.pub");
76+
77+
// Create configuration JSON with absolute paths and tracker configuration
78+
// This must match the format expected by EnvironmentCreationConfig
79+
serde_json::json!({
80+
"environment": {
81+
"name": &self.environment_name
82+
},
83+
"ssh_credentials": {
84+
"private_key_path": private_key_path,
85+
"public_key_path": public_key_path
86+
},
87+
"provider": {
88+
"provider": "lxd",
89+
"profile_name": format!("torrust-profile-{}", &self.environment_name)
90+
},
91+
"tracker": {
92+
"core": {
93+
"database": {
94+
"driver": "sqlite3",
95+
"database_name": "tracker.db"
96+
},
97+
"private": false
98+
},
99+
"udp_trackers": [
100+
{"bind_address": format!("0.0.0.0:{}", self.tracker_ports.udp_tracker_port)}
101+
],
102+
"http_trackers": [
103+
{"bind_address": format!("0.0.0.0:{}", self.tracker_ports.http_tracker_port)}
104+
],
105+
"http_api": {
106+
"admin_token": "MyAccessToken"
107+
}
108+
}
109+
})
110+
.to_string()
111+
}
112+
29113
/// Create E2E config environment from configuration file
30114
///
31115
/// # Arguments
@@ -162,6 +246,22 @@ pub struct TrackerPorts {
162246
pub udp_tracker_port: u16,
163247
}
164248

249+
impl Default for TrackerPorts {
250+
/// Create tracker ports with default values
251+
///
252+
/// Default ports match the standard test configuration:
253+
/// - HTTP API: 1212
254+
/// - HTTP tracker: 7070
255+
/// - UDP tracker: 6969
256+
fn default() -> Self {
257+
Self {
258+
http_api_port: 1212,
259+
http_tracker_port: 7070,
260+
udp_tracker_port: 6969,
261+
}
262+
}
263+
}
264+
165265
impl TrackerPorts {
166266
/// Extract tracker ports from an environment configuration JSON file
167267
///

src/testing/e2e/tasks/black_box/generate_config.rs

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,20 @@ use crate::testing::e2e::containers::E2eEnvironmentInfo;
4646
/// let config_path = generate_environment_config("e2e-full")?;
4747
/// ```
4848
pub fn generate_environment_config(environment_name: &str) -> Result<PathBuf> {
49-
let env_info = generate_environment_config_with_port(environment_name)?;
49+
let env_info = build_e2e_test_config(environment_name);
50+
write_environment_config(&env_info)?;
5051
Ok(env_info.config_file_path)
5152
}
5253

53-
/// Generates the environment configuration file with absolute SSH key paths.
54+
/// Generates E2E environment configuration in-memory
5455
///
5556
/// Creates a complete E2E environment configuration including tracker ports,
5657
/// SSH credentials, and provider settings. With host networking, the SSH port
5758
/// is defined in the configuration and remains the same inside and outside the container.
5859
///
60+
/// This function builds the configuration structure directly without file I/O.
61+
/// Use `write_environment_config()` to persist the configuration to disk when needed.
62+
///
5963
/// # Arguments
6064
///
6165
/// * `environment_name` - The name of the environment to create
@@ -64,49 +68,94 @@ pub fn generate_environment_config(environment_name: &str) -> Result<PathBuf> {
6468
///
6569
/// Returns `E2eEnvironmentInfo` containing all necessary information for E2E testing:
6670
/// - Environment name
67-
/// - Path to the generated configuration file
68-
/// - SSH port (extracted from tracker configuration)
69-
/// - Tracker ports (extracted from tracker configuration)
71+
/// - Path where config should be written (if needed)
72+
/// - SSH port (22 - default for test containers)
73+
/// - Tracker ports (default test configuration)
7074
///
71-
/// # Errors
75+
/// # Panics
7276
///
73-
/// Returns an error if the configuration file cannot be created.
77+
/// Panics if the current working directory cannot be determined (should never happen in normal operation).
7478
///
7579
/// # Example
7680
///
7781
/// ```rust,ignore
78-
/// use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::generate_environment_config_with_port;
82+
/// use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::build_e2e_test_config;
7983
///
80-
/// let env_info = generate_environment_config_with_port("e2e-config")?;
84+
/// let env_info = build_e2e_test_config("e2e-config");
8185
/// let socket_addr = env_info.ssh_socket_addr();
8286
/// ```
83-
pub fn generate_environment_config_with_port(environment_name: &str) -> Result<E2eEnvironmentInfo> {
84-
use std::fs;
85-
86-
let project_root = std::env::current_dir()
87-
.map_err(|e| anyhow::anyhow!("Failed to get current directory: {e}"))?;
87+
#[must_use]
88+
pub fn build_e2e_test_config(environment_name: &str) -> E2eEnvironmentInfo {
89+
use crate::testing::e2e::containers::TrackerPorts;
8890

89-
let config_json = create_test_environment_config(environment_name);
91+
let project_root = std::env::current_dir().expect("Failed to get current directory");
9092

91-
// Write to envs directory
9293
let config_path = project_root.join(format!("envs/{environment_name}.json"));
9394

95+
// Build E2eConfigEnvironment directly with default test values
96+
let ssh_port = 22; // Default SSH port for test containers
97+
let tracker_ports = TrackerPorts::default();
98+
99+
info!(
100+
environment_name = %environment_name,
101+
ssh_port = %ssh_port,
102+
"Generated E2E environment configuration in-memory"
103+
);
104+
105+
E2eEnvironmentInfo::new(
106+
environment_name.to_string(),
107+
config_path,
108+
ssh_port,
109+
tracker_ports,
110+
)
111+
}
112+
113+
/// Writes E2E environment configuration to disk
114+
///
115+
/// Creates the configuration JSON file with absolute SSH key paths,
116+
/// ensuring the environment can be used by CLI commands.
117+
///
118+
/// # Arguments
119+
///
120+
/// * `config_env` - The E2E configuration to write
121+
///
122+
/// # Errors
123+
///
124+
/// Returns an error if:
125+
/// - Configuration directory cannot be created
126+
/// - Configuration file cannot be written
127+
///
128+
/// # Example
129+
///
130+
/// ```rust,ignore
131+
/// use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::{
132+
/// build_e2e_test_config,
133+
/// write_environment_config,
134+
/// };
135+
///
136+
/// let env_info = build_e2e_test_config("e2e-config");
137+
/// write_environment_config(&env_info)?;
138+
/// ```
139+
pub fn write_environment_config(config_env: &E2eEnvironmentInfo) -> Result<()> {
140+
use std::fs;
141+
142+
let config_json = config_env.to_json_config();
143+
94144
// Ensure parent directory exists
95-
if let Some(parent) = config_path.parent() {
145+
if let Some(parent) = config_env.config_file_path.parent() {
96146
fs::create_dir_all(parent)
97147
.map_err(|e| anyhow::anyhow!("Failed to create config directory: {e}"))?;
98148
}
99149

100-
fs::write(&config_path, config_json)
150+
fs::write(&config_env.config_file_path, config_json)
101151
.map_err(|e| anyhow::anyhow!("Failed to write config file: {e}"))?;
102152

103153
info!(
104-
config_path = %config_path.display(),
105-
"Generated environment configuration"
154+
config_path = %config_env.config_file_path.display(),
155+
"Wrote environment configuration to disk"
106156
);
107157

108-
// Create E2eEnvironmentInfo from the generated config
109-
E2eEnvironmentInfo::from_config_file(environment_name.to_string(), config_path, None)
158+
Ok(())
110159
}
111160

112161
/// Creates a test environment configuration with absolute SSH key paths

src/testing/e2e/tasks/black_box/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ pub use test_runner::E2eTestRunner;
5151

5252
// Re-export standalone setup functions
5353
pub use generate_config::{
54-
create_test_environment_config, generate_environment_config,
55-
generate_environment_config_with_port,
54+
build_e2e_test_config, create_test_environment_config, generate_environment_config,
55+
write_environment_config,
5656
};
5757
pub use preflight_cleanup::run_container_preflight_cleanup;
5858
pub use preflight_cleanup::run_preflight_cleanup;

0 commit comments

Comments
 (0)