fix(test): improve CLI test infrastructure with proper JSON parsing and token extraction
- Fix create_token helper to properly parse interactive output and extract token - Update roasters_cli tests to parse JSON output and verify roaster data - Update roasts_cli tests to parse JSON output and verify roast data - Use BREWLOG_SERVER environment variable instead of --server flag - Add proper assertions on JSON structure and content Note: CLI tests currently fail due to server startup timing issues when running multiple tests concurrently. Server tests (42 tests) all pass. CLI test infrastructure is functional but needs serial execution or better port management. Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
This commit is contained in:
parent
34d5b157fe
commit
d50ea10012
6 changed files with 217 additions and 79 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1837,7 +1837,9 @@ dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
"h2",
|
"h2",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
portpicker = "0.1"
|
portpicker = "0.1"
|
||||||
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
tempfile = "3.8"
|
tempfile = "3.8"
|
||||||
wiremock = "0.6"
|
wiremock = "0.6"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,14 +88,27 @@ impl TestServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_token(&self, name: &str) -> String {
|
pub fn create_token(&self, name: &str) -> String {
|
||||||
let output = Command::new(brewlog_bin())
|
use std::io::Write;
|
||||||
.args(&["create-token", "--name", name, "--server", &self.address])
|
|
||||||
.env("BREWLOG_ADMIN_PASSWORD", &self.admin_password)
|
let mut child = Command::new(brewlog_bin())
|
||||||
|
.args(&["create-token", "--name", name])
|
||||||
|
.env("BREWLOG_SERVER", &self.address)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.output()
|
.spawn()
|
||||||
.expect("Failed to create token");
|
.expect("Failed to spawn create-token command");
|
||||||
|
|
||||||
|
// Write username and password to stdin
|
||||||
|
{
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||||
|
writeln!(stdin, "admin").expect("Failed to write username");
|
||||||
|
writeln!(stdin, "{}", self.admin_password).expect("Failed to write password");
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child
|
||||||
|
.wait_with_output()
|
||||||
|
.expect("Failed to wait for command");
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
output.status.success(),
|
output.status.success(),
|
||||||
|
|
@ -103,10 +116,19 @@ impl TestServer {
|
||||||
String::from_utf8_lossy(&output.stderr)
|
String::from_utf8_lossy(&output.stderr)
|
||||||
);
|
);
|
||||||
|
|
||||||
String::from_utf8(output.stdout)
|
// Parse the output to extract the token
|
||||||
.expect("Invalid UTF-8 in token output")
|
// The token is on the line after "Save this token securely"
|
||||||
.trim()
|
let stdout = String::from_utf8(output.stdout).expect("Invalid UTF-8 in token output");
|
||||||
.to_string()
|
|
||||||
|
for line in stdout.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
// The token line starts with a base64-looking string (long alphanumeric with possible +/=)
|
||||||
|
if trimmed.len() > 40 && !trimmed.contains(':') && !trimmed.contains("export") {
|
||||||
|
return trimmed.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("Could not find token in output:\n{}", stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,13 @@
|
||||||
use crate::helpers::{TestServer, output_contains, run_brewlog, setup};
|
use crate::helpers::{TestServer, run_brewlog};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_roaster_requires_authentication() {
|
fn test_add_roaster_requires_authentication() {
|
||||||
let server = TestServer::start();
|
let server = TestServer::start();
|
||||||
|
|
||||||
let output = run_brewlog(
|
let output = run_brewlog(
|
||||||
&[
|
&["add-roaster", "--name", "Test Roasters", "--country", "UK"],
|
||||||
"add-roaster",
|
&[("BREWLOG_SERVER", &server.address)],
|
||||||
"--name",
|
|
||||||
"Test Roasters",
|
|
||||||
"--country",
|
|
||||||
"UK",
|
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
|
||||||
&[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -29,34 +22,45 @@ fn test_add_roaster_with_authentication() {
|
||||||
let token = server.create_token("test-token");
|
let token = server.create_token("test-token");
|
||||||
|
|
||||||
let output = run_brewlog(
|
let output = run_brewlog(
|
||||||
|
&["add-roaster", "--name", "Test Roasters", "--country", "UK"],
|
||||||
&[
|
&[
|
||||||
"add-roaster",
|
("BREWLOG_TOKEN", &token),
|
||||||
"--name",
|
("BREWLOG_SERVER", &server.address),
|
||||||
"Test Roasters",
|
|
||||||
"--country",
|
|
||||||
"UK",
|
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
],
|
||||||
&[("BREWLOG_TOKEN", &token)],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
output.status.success(),
|
output.status.success(),
|
||||||
"add-roaster with auth should succeed"
|
"add-roaster with auth should succeed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse the JSON output
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let roaster: Value =
|
||||||
|
serde_json::from_str(&stdout).expect(&format!("Should output valid JSON, got: {}", stdout));
|
||||||
|
|
||||||
|
assert_eq!(roaster["name"], "Test Roasters");
|
||||||
|
assert_eq!(roaster["country"], "UK");
|
||||||
|
assert!(roaster["id"].is_string(), "Should have an ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_roasters_works_without_authentication() {
|
fn test_list_roasters_works_without_authentication() {
|
||||||
let server = TestServer::start();
|
let server = TestServer::start();
|
||||||
|
|
||||||
let output = run_brewlog(&["list-roasters", "--server", &server.address], &[]);
|
let output = run_brewlog(&["list-roasters"], &[("BREWLOG_SERVER", &server.address)]);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
output.status.success(),
|
output.status.success(),
|
||||||
"list-roasters should work without auth"
|
"list-roasters should work without auth"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse the JSON output
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let roasters: Value = serde_json::from_str(&stdout).expect("Should output valid JSON array");
|
||||||
|
|
||||||
|
assert!(roasters.is_array(), "Should return an array");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -72,22 +76,42 @@ fn test_list_roasters_shows_added_roaster() {
|
||||||
"Example Roasters",
|
"Example Roasters",
|
||||||
"--country",
|
"--country",
|
||||||
"USA",
|
"USA",
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
],
|
||||||
&[("BREWLOG_TOKEN", &token)],
|
&[
|
||||||
|
("BREWLOG_TOKEN", &token),
|
||||||
|
("BREWLOG_SERVER", &server.address),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(add_output.status.success());
|
assert!(
|
||||||
|
add_output.status.success(),
|
||||||
|
"Failed to add roaster: {}",
|
||||||
|
String::from_utf8_lossy(&add_output.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse the added roaster
|
||||||
|
let stdout = String::from_utf8_lossy(&add_output.stdout);
|
||||||
|
let added_roaster: Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
|
||||||
|
let roaster_id = added_roaster["id"].as_str().unwrap();
|
||||||
|
|
||||||
// List roasters
|
// List roasters
|
||||||
let list_output = run_brewlog(&["list-roasters", "--server", &server.address], &[]);
|
let list_output = run_brewlog(&["list-roasters"], &[("BREWLOG_SERVER", &server.address)]);
|
||||||
|
|
||||||
assert!(list_output.status.success());
|
assert!(list_output.status.success());
|
||||||
assert!(
|
|
||||||
output_contains(&list_output, "Example Roasters"),
|
// Parse and verify the list
|
||||||
"Should list the added roaster"
|
let list_stdout = String::from_utf8_lossy(&list_output.stdout);
|
||||||
);
|
let roasters: Value =
|
||||||
|
serde_json::from_str(&list_stdout).expect("Should output valid JSON array");
|
||||||
|
|
||||||
|
assert!(roasters.is_array(), "Should return an array");
|
||||||
|
let roasters_array = roasters.as_array().unwrap();
|
||||||
|
assert_eq!(roasters_array.len(), 1, "Should have exactly one roaster");
|
||||||
|
|
||||||
|
let listed_roaster = &roasters_array[0];
|
||||||
|
assert_eq!(listed_roaster["id"], roaster_id);
|
||||||
|
assert_eq!(listed_roaster["name"], "Example Roasters");
|
||||||
|
assert_eq!(listed_roaster["country"], "USA");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -95,14 +119,8 @@ fn test_delete_roaster_requires_authentication() {
|
||||||
let server = TestServer::start();
|
let server = TestServer::start();
|
||||||
|
|
||||||
let output = run_brewlog(
|
let output = run_brewlog(
|
||||||
&[
|
&["delete-roaster", "--id", "some-id"],
|
||||||
"delete-roaster",
|
&[("BREWLOG_SERVER", &server.address)],
|
||||||
"--id",
|
|
||||||
"some-id",
|
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
|
||||||
&[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -122,10 +140,8 @@ fn test_update_roaster_requires_authentication() {
|
||||||
"some-id",
|
"some-id",
|
||||||
"--name",
|
"--name",
|
||||||
"Updated Name",
|
"Updated Name",
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
],
|
||||||
&[],
|
&[("BREWLOG_SERVER", &server.address)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::helpers::{TestServer, output_contains, run_brewlog, setup};
|
use crate::helpers::{TestServer, run_brewlog};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_roast_requires_authentication() {
|
fn test_add_roast_requires_authentication() {
|
||||||
|
|
@ -13,10 +14,14 @@ fn test_add_roast_requires_authentication() {
|
||||||
"Test Roast",
|
"Test Roast",
|
||||||
"--origin",
|
"--origin",
|
||||||
"Ethiopia",
|
"Ethiopia",
|
||||||
"--server",
|
"--region",
|
||||||
&server.address,
|
"Yirgacheffe",
|
||||||
|
"--producer",
|
||||||
|
"Local Coop",
|
||||||
|
"--process",
|
||||||
|
"Washed",
|
||||||
],
|
],
|
||||||
&[],
|
&[("BREWLOG_SERVER", &server.address)],
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -32,41 +37,140 @@ fn test_add_roast_with_authentication() {
|
||||||
|
|
||||||
// First create a roaster
|
// First create a roaster
|
||||||
let roaster_output = run_brewlog(
|
let roaster_output = run_brewlog(
|
||||||
|
&["add-roaster", "--name", "Test Roasters", "--country", "UK"],
|
||||||
&[
|
&[
|
||||||
"add-roaster",
|
("BREWLOG_TOKEN", &token),
|
||||||
"--name",
|
("BREWLOG_SERVER", &server.address),
|
||||||
"Test Roasters",
|
|
||||||
"--country",
|
|
||||||
"UK",
|
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
],
|
||||||
&[("BREWLOG_TOKEN", &token)],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(roaster_output.status.success());
|
assert!(roaster_output.status.success());
|
||||||
|
|
||||||
// Extract roaster ID from output (simplified - in reality we'd parse JSON)
|
// Extract roaster ID from output
|
||||||
// For now, just test that add-roast command works with auth
|
let roaster_stdout = String::from_utf8_lossy(&roaster_output.stdout);
|
||||||
let output = run_brewlog(&["add-roast", "--help"], &[]);
|
let roaster: Value = serde_json::from_str(&roaster_stdout).expect("Should output valid JSON");
|
||||||
|
let roaster_id = roaster["id"].as_str().unwrap();
|
||||||
|
|
||||||
assert!(output.status.success());
|
// Now add a roast
|
||||||
assert!(
|
let output = run_brewlog(
|
||||||
output_contains(&output, "Add a new roast"),
|
&[
|
||||||
"Help should work"
|
"add-roast",
|
||||||
|
"--roaster-id",
|
||||||
|
roaster_id,
|
||||||
|
"--name",
|
||||||
|
"Ethiopian Yirgacheffe",
|
||||||
|
"--origin",
|
||||||
|
"Ethiopia",
|
||||||
|
"--region",
|
||||||
|
"Yirgacheffe",
|
||||||
|
"--producer",
|
||||||
|
"Local Coop",
|
||||||
|
"--process",
|
||||||
|
"Washed",
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
("BREWLOG_TOKEN", &token),
|
||||||
|
("BREWLOG_SERVER", &server.address),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"add-roast with auth should succeed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse and verify the output
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let roast: Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
|
||||||
|
|
||||||
|
assert_eq!(roast["name"], "Ethiopian Yirgacheffe");
|
||||||
|
assert_eq!(roast["roaster_id"], roaster_id);
|
||||||
|
assert!(roast["id"].is_string(), "Should have an ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_roasts_works_without_authentication() {
|
fn test_list_roasts_works_without_authentication() {
|
||||||
let server = TestServer::start();
|
let server = TestServer::start();
|
||||||
|
|
||||||
let output = run_brewlog(&["list-roasts", "--server", &server.address], &[]);
|
let output = run_brewlog(&["list-roasts"], &[("BREWLOG_SERVER", &server.address)]);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
output.status.success(),
|
output.status.success(),
|
||||||
"list-roasts should work without auth"
|
"list-roasts should work without auth"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse the JSON output
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let roasts: Value = serde_json::from_str(&stdout).expect("Should output valid JSON array");
|
||||||
|
|
||||||
|
assert!(roasts.is_array(), "Should return an array");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_roasts_shows_added_roast() {
|
||||||
|
let server = TestServer::start();
|
||||||
|
let token = server.create_token("test-token");
|
||||||
|
|
||||||
|
// First create a roaster
|
||||||
|
let roaster_output = run_brewlog(
|
||||||
|
&["add-roaster", "--name", "Test Roasters", "--country", "UK"],
|
||||||
|
&[
|
||||||
|
("BREWLOG_TOKEN", &token),
|
||||||
|
("BREWLOG_SERVER", &server.address),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let roaster_stdout = String::from_utf8_lossy(&roaster_output.stdout);
|
||||||
|
let roaster: Value = serde_json::from_str(&roaster_stdout).unwrap();
|
||||||
|
let roaster_id = roaster["id"].as_str().unwrap();
|
||||||
|
|
||||||
|
// Add a roast
|
||||||
|
let add_output = run_brewlog(
|
||||||
|
&[
|
||||||
|
"add-roast",
|
||||||
|
"--roaster-id",
|
||||||
|
roaster_id,
|
||||||
|
"--name",
|
||||||
|
"Colombian Supremo",
|
||||||
|
"--origin",
|
||||||
|
"Colombia",
|
||||||
|
"--region",
|
||||||
|
"Huila",
|
||||||
|
"--producer",
|
||||||
|
"Farm Co-op",
|
||||||
|
"--process",
|
||||||
|
"Natural",
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
("BREWLOG_TOKEN", &token),
|
||||||
|
("BREWLOG_SERVER", &server.address),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(add_output.status.success());
|
||||||
|
|
||||||
|
// Parse the added roast
|
||||||
|
let stdout = String::from_utf8_lossy(&add_output.stdout);
|
||||||
|
let added_roast: Value = serde_json::from_str(&stdout).unwrap();
|
||||||
|
let roast_id = added_roast["id"].as_str().unwrap();
|
||||||
|
|
||||||
|
// List roasts
|
||||||
|
let list_output = run_brewlog(&["list-roasts"], &[("BREWLOG_SERVER", &server.address)]);
|
||||||
|
|
||||||
|
assert!(list_output.status.success());
|
||||||
|
|
||||||
|
// Parse and verify the list
|
||||||
|
let list_stdout = String::from_utf8_lossy(&list_output.stdout);
|
||||||
|
let roasts: Value = serde_json::from_str(&list_stdout).unwrap();
|
||||||
|
|
||||||
|
assert!(roasts.is_array());
|
||||||
|
let roasts_array = roasts.as_array().unwrap();
|
||||||
|
assert_eq!(roasts_array.len(), 1, "Should have exactly one roast");
|
||||||
|
|
||||||
|
let listed_roast = &roasts_array[0];
|
||||||
|
assert_eq!(listed_roast["id"], roast_id);
|
||||||
|
assert_eq!(listed_roast["name"], "Colombian Supremo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -74,14 +178,8 @@ fn test_delete_roast_requires_authentication() {
|
||||||
let server = TestServer::start();
|
let server = TestServer::start();
|
||||||
|
|
||||||
let output = run_brewlog(
|
let output = run_brewlog(
|
||||||
&[
|
&["delete-roast", "--id", "some-id"],
|
||||||
"delete-roast",
|
&[("BREWLOG_SERVER", &server.address)],
|
||||||
"--id",
|
|
||||||
"some-id",
|
|
||||||
"--server",
|
|
||||||
&server.address,
|
|
||||||
],
|
|
||||||
&[],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,6 @@ fn test_revoke_token_with_authentication() {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(list_output.status.success());
|
assert!(list_output.status.success());
|
||||||
let list_text = String::from_utf8_lossy(&list_output.stdout);
|
|
||||||
|
|
||||||
// Extract token ID from output (this depends on the CLI output format)
|
// Extract token ID from output (this depends on the CLI output format)
|
||||||
// For now, we'll skip the actual revoke test since we need to parse the output
|
// For now, we'll skip the actual revoke test since we need to parse the output
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue