brewlog/tests/cli/brews_cli.rs
Jon Seager b3d697978c
test(brews): add server and CLI integration tests
- Test create, list, get, delete operations
- Test bag deduction and conflict on insufficient coffee
- Test CLI commands with defaults
- Add create_default_gear helper
2026-02-02 19:32:35 +00:00

286 lines
8.5 KiB
Rust

use crate::helpers::{create_roast, create_roaster, create_token, run_brewlog};
fn create_bag(roast_id: &str, token: &str) -> String {
let output = run_brewlog(
&["bag", "add", "--roast-id", roast_id, "--amount", "250"],
&[("BREWLOG_TOKEN", token)],
);
if !output.status.success() {
panic!(
"Failed to create bag: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let stdout = String::from_utf8_lossy(&output.stdout);
let bag: serde_json::Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
bag["id"]
.as_i64()
.expect("bag id should be numeric")
.to_string()
}
fn create_gear(category: &str, make: &str, model: &str, token: &str) -> String {
let output = run_brewlog(
&[
"gear",
"add",
"--category",
category,
"--make",
make,
"--model",
model,
],
&[("BREWLOG_TOKEN", token)],
);
if !output.status.success() {
panic!(
"Failed to create gear: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let stdout = String::from_utf8_lossy(&output.stdout);
let gear: serde_json::Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
gear["id"]
.as_i64()
.expect("gear id should be numeric")
.to_string()
}
#[test]
fn brew_add_creates_brew_via_api() {
let token = create_token("brew-add-test");
let roaster_id = create_roaster("Brew Test Roaster", &token);
let roast_id = create_roast(&roaster_id, "Brew Test Roast", &token);
let bag_id = create_bag(&roast_id, &token);
let grinder_id = create_gear("grinder", "Comandante", "C40", &token);
let brewer_id = create_gear("brewer", "Hario", "V60", &token);
let output = run_brewlog(
&[
"brew",
"add",
"--bag-id",
&bag_id,
"--coffee-weight",
"15.0",
"--grinder-id",
&grinder_id,
"--grind-setting",
"24.0",
"--brewer-id",
&brewer_id,
"--water-volume",
"250",
"--water-temp",
"92.0",
],
&[("BREWLOG_TOKEN", &token)],
);
assert!(
output.status.success(),
"brew add should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let brew: serde_json::Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
assert_eq!(brew["coffee_weight"], 15.0);
assert_eq!(brew["grind_setting"], 24.0);
assert_eq!(brew["water_volume"], 250);
assert_eq!(brew["water_temp"], 92.0);
}
#[test]
fn brew_add_with_defaults_uses_default_values() {
let token = create_token("brew-defaults-test");
let roaster_id = create_roaster("Brew Defaults Roaster", &token);
let roast_id = create_roast(&roaster_id, "Brew Defaults Roast", &token);
let bag_id = create_bag(&roast_id, &token);
let grinder_id = create_gear("grinder", "1Zpresso", "JMax", &token);
let brewer_id = create_gear("brewer", "AeroPress", "Original", &token);
// Only provide required args, let defaults apply
let output = run_brewlog(
&[
"brew",
"add",
"--bag-id",
&bag_id,
"--grinder-id",
&grinder_id,
"--brewer-id",
&brewer_id,
],
&[("BREWLOG_TOKEN", &token)],
);
assert!(
output.status.success(),
"brew add should succeed with defaults: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let brew: serde_json::Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
// Check defaults: 15.0g coffee, 6.0 grind, 250ml water, 91.0°C
assert_eq!(brew["coffee_weight"], 15.0);
assert_eq!(brew["grind_setting"], 6.0);
assert_eq!(brew["water_volume"], 250);
assert_eq!(brew["water_temp"], 91.0);
}
#[test]
fn brew_list_returns_json_array() {
let token = create_token("brew-list-test");
let roaster_id = create_roaster("Brew List Roaster", &token);
let roast_id = create_roast(&roaster_id, "Brew List Roast", &token);
let bag_id = create_bag(&roast_id, &token);
let grinder_id = create_gear("grinder", "Fellow", "Ode", &token);
let brewer_id = create_gear("brewer", "Chemex", "Classic", &token);
// Create a brew first
run_brewlog(
&[
"brew",
"add",
"--bag-id",
&bag_id,
"--grinder-id",
&grinder_id,
"--brewer-id",
&brewer_id,
],
&[("BREWLOG_TOKEN", &token)],
);
// List brews
let output = run_brewlog(&["brew", "list"], &[("BREWLOG_TOKEN", &token)]);
assert!(
output.status.success(),
"brew list should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let brews: serde_json::Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
assert!(brews.is_array(), "brew list should return an array");
assert!(
!brews.as_array().unwrap().is_empty(),
"should have at least one brew"
);
}
#[test]
fn brew_get_returns_brew_details() {
let token = create_token("brew-get-test");
let roaster_id = create_roaster("Brew Get Roaster", &token);
let roast_id = create_roast(&roaster_id, "Brew Get Roast", &token);
let bag_id = create_bag(&roast_id, &token);
let grinder_id = create_gear("grinder", "Baratza", "Encore", &token);
let brewer_id = create_gear("brewer", "Kalita", "Wave", &token);
// Create a brew
let create_output = run_brewlog(
&[
"brew",
"add",
"--bag-id",
&bag_id,
"--grinder-id",
&grinder_id,
"--brewer-id",
&brewer_id,
"--coffee-weight",
"18.5",
],
&[("BREWLOG_TOKEN", &token)],
);
let create_stdout = String::from_utf8_lossy(&create_output.stdout);
let created_brew: serde_json::Value =
serde_json::from_str(&create_stdout).expect("Should output valid JSON");
let brew_id = created_brew["id"].as_i64().unwrap().to_string();
// Get the brew
let output = run_brewlog(
&["brew", "get", "--id", &brew_id],
&[("BREWLOG_TOKEN", &token)],
);
assert!(
output.status.success(),
"brew get should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let brew: serde_json::Value = serde_json::from_str(&stdout).expect("Should output valid JSON");
assert_eq!(brew["coffee_weight"], 18.5);
assert!(
brew["roast_name"].is_string(),
"should include enriched roast_name"
);
}
#[test]
fn brew_delete_removes_brew() {
let token = create_token("brew-delete-test");
let roaster_id = create_roaster("Brew Delete Roaster", &token);
let roast_id = create_roast(&roaster_id, "Brew Delete Roast", &token);
let bag_id = create_bag(&roast_id, &token);
let grinder_id = create_gear("grinder", "Timemore", "C2", &token);
let brewer_id = create_gear("brewer", "Melitta", "Pour Over", &token);
// Create a brew
let create_output = run_brewlog(
&[
"brew",
"add",
"--bag-id",
&bag_id,
"--grinder-id",
&grinder_id,
"--brewer-id",
&brewer_id,
],
&[("BREWLOG_TOKEN", &token)],
);
let create_stdout = String::from_utf8_lossy(&create_output.stdout);
let created_brew: serde_json::Value =
serde_json::from_str(&create_stdout).expect("Should output valid JSON");
let brew_id = created_brew["id"].as_i64().unwrap().to_string();
// Delete the brew
let output = run_brewlog(
&["brew", "delete", "--id", &brew_id],
&[("BREWLOG_TOKEN", &token)],
);
assert!(
output.status.success(),
"brew delete should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
// Verify it's gone
let get_output = run_brewlog(
&["brew", "get", "--id", &brew_id],
&[("BREWLOG_TOKEN", &token)],
);
assert!(
!get_output.status.success(),
"brew get should fail after deletion"
);
}