Skip to content

Commit 0fa7964

Browse files
committed
Remove panic!() and unwrap() from docker_init.rs and improve test
coverage for these flows
1 parent 166a830 commit 0fa7964

7 files changed

Lines changed: 877 additions & 38 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ tree_hash_derive.workspace = true
2121
[dev-dependencies]
2222
assert_cmd.workspace = true
2323
predicates.workspace = true
24+
serde_yaml.workspace = true
25+
tempfile.workspace = true
2426

2527
[[bin]]
2628
name = "commit-boost"

bin/commit-boost.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ enum Commands {
4848
async fn main() -> Result<()> {
4949
// Parse the CLI arguments (currently only used for version info, more can be
5050
// added later)
51-
let _cli = Cli::parse();
51+
let cli = Cli::parse();
5252

5353
color_eyre::install()?;
5454

55-
match _cli.command {
55+
match cli.command {
5656
Commands::Pbs => run_pbs_service().await?,
5757
Commands::Signer => run_signer_service().await?,
5858
Commands::Init { config_path, output_path } => run_init(config_path, output_path).await?,

bin/tests/binary.rs

Lines changed: 173 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,201 @@
11
use assert_cmd::{Command, cargo};
2+
use cb_cli::docker_init::{CB_COMPOSE_FILE, CB_ENV_FILE};
3+
4+
// ---------------------------------------------------------------------------
5+
// Fixtures
6+
// ---------------------------------------------------------------------------
7+
8+
const MINIMAL_PBS_TOML: &str = r#"
9+
chain = "Holesky"
10+
[pbs]
11+
docker_image = "ghcr.io/commit-boost/pbs:latest"
12+
"#;
13+
14+
const MINIMAL_WITH_MODULE_TOML: &str = r#"
15+
chain = "Holesky"
16+
[pbs]
17+
docker_image = "ghcr.io/commit-boost/pbs:latest"
18+
19+
[signer.local.loader]
20+
key_path = "/keys/keys.json"
21+
22+
[[modules]]
23+
id = "DA_COMMIT"
24+
type = "commit"
25+
docker_image = "test_da_commit"
26+
"#;
27+
28+
// ---------------------------------------------------------------------------
29+
// Helpers
30+
// ---------------------------------------------------------------------------
31+
32+
/// Returns a `Command` pointed at the `commit-boost` binary under test.
33+
fn cmd() -> Command {
34+
Command::new(cargo::cargo_bin!())
35+
}
36+
37+
/// Writes `contents` to `cb.toml` inside `dir` and returns the path.
38+
fn write_config(dir: &tempfile::TempDir, contents: &str) -> std::path::PathBuf {
39+
let path = dir.path().join("cb.toml");
40+
std::fs::write(&path, contents).expect("write test config");
41+
path
42+
}
43+
44+
/// Returns a `commit-boost init` command configured with the given config and
45+
/// output directory.
46+
fn init_cmd(config: &std::path::Path, output_dir: &std::path::Path) -> Command {
47+
let mut c = cmd();
48+
c.args([
49+
"init",
50+
"--config",
51+
config.to_str().expect("valid config path"),
52+
"--output",
53+
output_dir.to_str().expect("valid output dir"),
54+
]);
55+
c
56+
}
57+
58+
// ---------------------------------------------------------------------------
59+
// Binary smoke tests
60+
// ---------------------------------------------------------------------------
261

362
/// Tests that the binary can be run and returns a version string
463
#[test]
564
fn test_load_example_config() {
6-
let mut cmd = Command::new(cargo::cargo_bin!());
765
let expected_version = format!("Commit-Boost {}\n", commit_boost::VERSION);
8-
cmd.arg("--version").assert().success().stdout(expected_version);
66+
cmd().arg("--version").assert().success().stdout(expected_version);
967
}
1068

1169
/// Tests that the init command can be run and complains about not having
1270
/// --config set
1371
#[test]
1472
fn test_run_init() {
15-
let mut cmd = Command::new(cargo::cargo_bin!());
16-
cmd.args(["init"]).assert().failure().stderr(predicates::str::contains(
73+
cmd().args(["init"]).assert().failure().stderr(predicates::str::contains(
1774
"error: the following required arguments were not provided:\n --config <CONFIG_PATH>",
1875
));
1976
}
2077

2178
/// Tests that PBS runs without CB_CONFIG being set and complains normally
2279
#[test]
2380
fn test_run_pbs_no_config() {
24-
let mut cmd = Command::new(cargo::cargo_bin!());
25-
cmd.args(["pbs"]).assert().failure().stderr(predicates::str::contains("CB_CONFIG is not set"));
81+
cmd()
82+
.args(["pbs"])
83+
.assert()
84+
.failure()
85+
.stderr(predicates::str::contains("CB_CONFIG is not set"));
2686
}
2787

2888
/// Tests that Signer runs without CB_CONFIG being set and complains normally
2989
#[test]
3090
fn test_run_signer_no_config() {
31-
let mut cmd = Command::new(cargo::cargo_bin!());
32-
cmd.args(["signer"])
91+
cmd()
92+
.args(["signer"])
3393
.assert()
3494
.failure()
3595
.stderr(predicates::str::contains("CB_CONFIG is not set"));
3696
}
97+
98+
// ---------------------------------------------------------------------------
99+
// handle_docker_init (via `commit-boost init`) integration tests
100+
// ---------------------------------------------------------------------------
101+
102+
/// Minimal PBS-only config produces a compose file and no .env file.
103+
#[test]
104+
fn test_init_pbs_only_creates_compose_file() {
105+
let dir = tempfile::tempdir().expect("tempdir");
106+
let config = write_config(&dir, MINIMAL_PBS_TOML);
107+
108+
init_cmd(&config, dir.path()).assert().success();
109+
110+
assert!(dir.path().join(CB_COMPOSE_FILE).exists(), "compose file should be created");
111+
assert!(!dir.path().join(CB_ENV_FILE).exists(), "no .env file for PBS-only config");
112+
}
113+
114+
/// PBS-only compose file has the expected service structure.
115+
#[test]
116+
fn test_init_compose_file_pbs_service_structure() {
117+
let dir = tempfile::tempdir().expect("tempdir");
118+
let config = write_config(&dir, MINIMAL_PBS_TOML);
119+
120+
init_cmd(&config, dir.path()).assert().success();
121+
122+
let contents =
123+
std::fs::read_to_string(dir.path().join(CB_COMPOSE_FILE)).expect("read compose file");
124+
let compose: serde_yaml::Value =
125+
serde_yaml::from_str(&contents).expect("compose file is valid YAML");
126+
127+
let pbs = &compose["services"]["cb_pbs"];
128+
assert!(!pbs.is_null(), "cb_pbs service must exist");
129+
assert_eq!(pbs["image"].as_str(), Some("ghcr.io/commit-boost/pbs:latest"), "image");
130+
assert_eq!(pbs["container_name"].as_str(), Some("cb_pbs"), "container_name");
131+
132+
// Config file must be mounted inside the container.
133+
let volumes = pbs["volumes"].as_sequence().expect("volumes is a list");
134+
assert!(
135+
volumes.iter().any(|v| v.as_str().map_or(false, |s| s.ends_with(":/cb-config.toml:ro"))),
136+
"config must be mounted at /cb-config.toml"
137+
);
138+
139+
// Required environment variables must be present.
140+
let env = &pbs["environment"];
141+
assert!(!env["CB_CONFIG"].is_null(), "CB_CONFIG env var must be set");
142+
assert!(!env["CB_PBS_ENDPOINT"].is_null(), "CB_PBS_ENDPOINT env var must be set");
143+
144+
// Port 18550 must be exposed.
145+
let ports = pbs["ports"].as_sequence().expect("ports is a list");
146+
assert!(
147+
ports.iter().any(|p| p.as_str().map_or(false, |s| s.contains("18550"))),
148+
"port 18550 must be exposed"
149+
);
150+
151+
// No signer service and no extra network in a PBS-only config.
152+
assert!(compose["services"]["cb_signer"].is_null(), "cb_signer must not exist");
153+
assert!(compose["networks"].is_null(), "no networks for PBS-only config");
154+
}
155+
156+
/// Config with a commit module produces both a compose file and a .env file.
157+
#[test]
158+
fn test_init_with_module_creates_env_file() {
159+
let dir = tempfile::tempdir().expect("tempdir");
160+
let config = write_config(&dir, MINIMAL_WITH_MODULE_TOML);
161+
162+
init_cmd(&config, dir.path()).assert().success();
163+
164+
assert!(dir.path().join(CB_COMPOSE_FILE).exists(), "compose file should be created");
165+
assert!(dir.path().join(CB_ENV_FILE).exists(), ".env file should be created for modules");
166+
}
167+
168+
/// .env file contains a JWT entry for the module.
169+
#[test]
170+
fn test_init_env_file_contains_module_jwt() {
171+
let dir = tempfile::tempdir().expect("tempdir");
172+
let config = write_config(&dir, MINIMAL_WITH_MODULE_TOML);
173+
174+
init_cmd(&config, dir.path()).assert().success();
175+
176+
let env_contents =
177+
std::fs::read_to_string(dir.path().join(CB_ENV_FILE)).expect("read .env file");
178+
assert!(env_contents.contains("CB_JWT_DA_COMMIT="), ".env must contain module JWT");
179+
}
180+
181+
/// Missing --config argument produces a clear error message.
182+
#[test]
183+
fn test_init_missing_config_flag_fails_with_message() {
184+
cmd().args(["init"]).assert().failure().stderr(predicates::str::contains("--config"));
185+
}
186+
187+
/// Non-existent config file produces an error.
188+
#[test]
189+
fn test_init_nonexistent_config_file_fails() {
190+
let dir = tempfile::tempdir().expect("tempdir");
191+
cmd()
192+
.args([
193+
"init",
194+
"--config",
195+
"/nonexistent/path/cb.toml",
196+
"--output",
197+
dir.path().to_str().expect("valid dir"),
198+
])
199+
.assert()
200+
.failure();
201+
}

crates/cli/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ docker-compose-types.workspace = true
1212
eyre.workspace = true
1313
indexmap.workspace = true
1414
serde_yaml.workspace = true
15+
16+
[dev-dependencies]
17+
tempfile.workspace = true
18+
toml.workspace = true

0 commit comments

Comments
 (0)