From 0090c4ba435772f0be1beb753772d2f215570e80 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Mon, 2 Feb 2026 17:02:46 +0000 Subject: [PATCH] refactor(cli): restructure commands from flat to nested subcommands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change CLI structure from `brewlog {verb}-{entity}` to `brewlog {entity} {verb}`: - brewlog add-roaster → brewlog roaster add - brewlog list-bags → brewlog bag list - brewlog create-token → brewlog token create - etc. Each entity module now owns its subcommand enum and dispatch logic, simplifying main.rs and improving discoverability via `brewlog {entity} --help`. - Add RoasterCommands, RoastCommands, BagCommands, GearCommands, TokenCommands enums - Add run() dispatcher to each entity module - Simplify top-level Commands enum to delegate to entity modules - Update all CLI tests and bootstrap script --- scripts/bootstrap-db.sh | 227 ++++++++++++++++--------------- src/main.rs | 54 +++----- src/presentation/cli/bags.rs | 26 +++- src/presentation/cli/gear.rs | 26 +++- src/presentation/cli/mod.rs | 86 +++++------- src/presentation/cli/roasters.rs | 26 +++- src/presentation/cli/roasts.rs | 23 +++- src/presentation/cli/tokens.rs | 20 ++- tests/cli/bags_cli.rs | 49 ++++--- tests/cli/gear_cli.rs | 50 ++++--- tests/cli/helpers.rs | 8 +- tests/cli/roasters_cli.rs | 37 +++-- tests/cli/roasts_cli.rs | 23 ++-- tests/cli/tokens_cli.rs | 22 +-- 14 files changed, 392 insertions(+), 285 deletions(-) diff --git a/scripts/bootstrap-db.sh b/scripts/bootstrap-db.sh index 75a62d2..eba70bd 100755 --- a/scripts/bootstrap-db.sh +++ b/scripts/bootstrap-db.sh @@ -3,7 +3,7 @@ set -euo pipefail cargo build -BREWLOG_TOKEN="$(./target/debug/brewlog create-token --name "bootstrap-token" --username admin --password password | grep -Po "BREWLOG_TOKEN=\K.+$")" +BREWLOG_TOKEN="$(./target/debug/brewlog token create --name "bootstrap-token" --username admin --password password | grep -Po "BREWLOG_TOKEN=\K.+$")" export BREWLOG_TOKEN if [[ -z "$BREWLOG_TOKEN" ]]; then @@ -12,15 +12,15 @@ if [[ -z "$BREWLOG_TOKEN" ]]; then fi # Tim Wendelboe (Norway) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Tim Wendelboe" \ --country "Norway" \ --city "Oslo" \ --homepage "https://timwendelboe.no" \ --notes "World-renowned Nordic micro-roastery dedicated to clarity and sustainability." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \ --name "Ben Saïd Natural" \ --origin "Ethiopia" \ --region "Sidamo" \ @@ -28,8 +28,8 @@ fi --process "Natural" \ --tasting-notes "Bergamot, Apricot, Floral" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Tim Wendelboe") | .id')" \ --name "Finca Tamana Washed" \ --origin "Colombia" \ --region "El Pital, Huila" \ @@ -39,15 +39,15 @@ fi # Coffee Collective (Denmark) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Coffee Collective" \ --country "Denmark" \ --city "Copenhagen" \ --homepage "https://coffeecollective.dk" \ --notes "Pioneers of transparency and sustainability; multi-time Nordic roaster award winners." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \ --name "Daterra Sweet Collection" \ --origin "Brazil" \ --region "Cerrado" \ @@ -55,8 +55,8 @@ fi --process "Pulped Natural" \ --tasting-notes "Hazelnut, Milk Chocolate, Yellow Fruit" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Coffee Collective") | .id')" \ --name "Kieni" \ --origin "Kenya" \ --region "Nyeri" \ @@ -66,15 +66,15 @@ fi # Drop Coffee (Sweden) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Drop Coffee" \ --country "Sweden" \ --city "Stockholm" \ --homepage "https://dropcoffee.com" \ --notes "Award-winning Swedish roastery prized for its elegance and clean Scandinavian style." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \ --name "La Linda" \ --origin "Bolivia" \ --region "Caranavi" \ @@ -82,8 +82,8 @@ fi --process "Washed" \ --tasting-notes "Red Apple, Caramel, Floral" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Drop Coffee") | .id')" \ --name "El Sunzita" \ --origin "El Salvador" \ --region "Ahuachapan" \ @@ -93,15 +93,15 @@ fi # La Cabra (Denmark) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "La Cabra" \ --country "Denmark" \ --city "Aarhus" \ --homepage "https://www.lacabra.dk" \ --notes "Scandinavian minimalist roastery known for clarity and innovative sourcing." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="La Cabra") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="La Cabra") | .id')" \ --name "Halo Beriti" \ --origin "Ethiopia" \ --region "Yirgacheffe" \ @@ -109,8 +109,8 @@ fi --process "Washed" \ --tasting-notes "Jasmine, Lemon, Stone Fruit" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="La Cabra") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="La Cabra") | .id')" \ --name "Cerro Azul" \ --origin "Colombia" \ --region "Valle del Cauca" \ @@ -120,15 +120,15 @@ fi # April Coffee (Denmark) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "April Coffee" \ --country "Denmark" \ --city "Copenhagen" \ --homepage "https://aprilcoffeeroasters.com" \ --notes "Modern approach to Nordic coffee, emphasizing transparency and traceability." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="April Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="April Coffee") | .id')" \ --name "El Salvador Pacamara" \ --origin "El Salvador" \ --region "Santa Ana" \ @@ -136,8 +136,8 @@ fi --process "Honey" \ --tasting-notes "Grapefruit, Sugar Cane, Plum" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="April Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="April Coffee") | .id')" \ --name "Guji Highland" \ --origin "Ethiopia" \ --region "Guji" \ @@ -147,15 +147,15 @@ fi # Assembly Coffee (UK) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Assembly Coffee" \ --country "UK" \ --city "London" \ --homepage "https://assemblycoffee.co.uk" \ --notes "Based in Brixton, Assembly focuses on collaborative sourcing and education." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \ --name "Kochere" \ --origin "Ethiopia" \ --region "Yirgacheffe" \ @@ -163,8 +163,8 @@ fi --process "Washed" \ --tasting-notes "Peach, Lemon, Jasmine" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Assembly Coffee") | .id')" \ --name "La Laja" \ --origin "Mexico" \ --region "Veracruz" \ @@ -174,15 +174,15 @@ fi # Square Mile (UK) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Square Mile Coffee" \ --country "UK" \ --city "London" \ --homepage "https://squaremilecoffee.com" \ --notes "One of London's pioneers; delivers balanced and clear, fruit-forward coffees." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \ --name "Red Brick Espresso" \ --origin "Blend" \ --region "Multiple Origins" \ @@ -190,8 +190,8 @@ fi --process "Washed, Natural" \ --tasting-notes "Berry, Chocolate, Citrus" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Square Mile Coffee") | .id')" \ --name "Kamwangi" \ --origin "Kenya" \ --region "Kirinyaga" \ @@ -201,15 +201,15 @@ fi # Dak Coffee Roasters (Netherlands) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Dak Coffee Roasters" \ --country "Netherlands" \ --city "Amsterdam" \ --homepage "https://www.dakcoffeeroasters.com" \ --notes "Highly experimental Dutch roastery; celebrates vibrant acidity and alternative processing." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \ --name "El Paraiso 92 Anaerobic" \ --origin "Colombia" \ --region "Cauca" \ @@ -217,8 +217,8 @@ fi --process "Thermal Shock Anaerobic" \ --tasting-notes "Passionfruit, Raspberry, Yogurt" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dak Coffee Roasters") | .id')" \ --name "Oreti SL28" \ --origin "Kenya" \ --region "Kirinyaga" \ @@ -228,15 +228,15 @@ fi # Bonanza Coffee (Germany) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Bonanza Coffee" \ --country "Germany" \ --city "Berlin" \ --homepage "https://www.bonanzacoffee.de" \ --notes "Pioneering Berlin roastery focused on brightness, balance, and freshness." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \ --name "Gatomboya" \ --origin "Kenya" \ --region "Nyeri" \ @@ -244,8 +244,8 @@ fi --process "Washed" \ --tasting-notes "Blackcurrant, Lime, Tomato" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bonanza Coffee") | .id')" \ --name "Los Pirineos" \ --origin "El Salvador" \ --region "Usulután" \ @@ -255,15 +255,15 @@ fi # Friedhats (Netherlands) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Friedhats" \ --country "Netherlands" \ --city "Amsterdam" \ --homepage "https://friedhats.com" \ --notes "Quirky branding meets serious, awarded, fruit-forward coffees from Amsterdam." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Friedhats") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Friedhats") | .id')" \ --name "Sidamo Guji" \ --origin "Ethiopia" \ --region "Guji" \ @@ -271,8 +271,8 @@ fi --process "Natural" \ --tasting-notes "Peach, Raspberry, Rosehip" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Friedhats") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Friedhats") | .id')" \ --name "La Esmeralda Geisha" \ --origin "Panama" \ --region "Boquete" \ @@ -282,15 +282,15 @@ fi # Origin Coffee (UK) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Origin Coffee" \ --country "UK" \ --city "Porthleven" \ --homepage "https://origincoffee.co.uk" \ --notes "Specialty roaster with close partnerships at origin; leading UK scene with cutting-edge lots." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \ --name "San Fermin" \ --origin "Colombia" \ --region "Tolima" \ @@ -298,8 +298,8 @@ fi --process "Washed" \ --tasting-notes "Red Grape, Caramel, Blood Orange" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Origin Coffee") | .id')" \ --name "Aricha" \ --origin "Ethiopia" \ --region "Yirgacheffe" \ @@ -309,15 +309,15 @@ fi # Dark Arts Coffee (UK) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Dark Arts Coffee" \ --country "UK" \ --city "London" \ --homepage "https://www.darkartscoffee.co.uk" \ --notes "Playful, disruptive roaster with a cult following and flavor-forward offerings." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \ --name "Death to Decaf" \ --origin "Brazil" \ --region "Minas Gerais" \ @@ -325,8 +325,8 @@ fi --process "Swiss Water Decaf" \ --tasting-notes "Cocoa, Cherry, Almond" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Dark Arts Coffee") | .id')" \ --name "Snoop" \ --origin "Guatemala" \ --region "Huehuetenango" \ @@ -336,15 +336,15 @@ fi # KAWA Coffee (France) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "KAWA Coffee" \ --country "France" \ --city "Paris" \ --homepage "https://www.kawa.coffee" \ --notes "One of Paris’ most exciting specialty roasteries, known for unusual and competition-level lots." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \ --name "Sudan Rume" \ --origin "Colombia" \ --region "Cauca" \ @@ -352,8 +352,8 @@ fi --process "Natural" \ --tasting-notes "Strawberry, Cinnamon, Grape" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="KAWA Coffee") | .id')" \ --name "Arbegona" \ --origin "Ethiopia" \ --region "Sidama" \ @@ -363,15 +363,15 @@ fi # Stow Coffee (Slovenia) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Stow Coffee" \ --country "Slovenia" \ --city "Ljubljana" \ --homepage "https://www.stowcoffee.com" \ --notes "Slovenia’s specialty leader, awarded for pure, brightly acidic profiles and innovation." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \ --name "Santa Barbara" \ --origin "Honduras" \ --region "Santa Barbara" \ @@ -379,8 +379,8 @@ fi --process "Honey" \ --tasting-notes "Red Currant, Honeydew, Cocoa" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Stow Coffee") | .id')" \ --name "Suke Quto" \ --origin "Ethiopia" \ --region "Guji" \ @@ -390,15 +390,15 @@ fi # Bows Coffee (Canada) -./target/debug/brewlog add-roaster \ +./target/debug/brewlog roaster add \ --name "Bows Coffee" \ --country "Canada" \ --city "Victoria" \ --homepage "https://bowscoffee.com" \ --notes "Canadian micro-roaster with focus on clarity, complexity, and ethical sourcing." -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \ --name "La Chumeca" \ --origin "Costa Rica" \ --region "Tarrazú" \ @@ -406,8 +406,8 @@ fi --process "White Honey" \ --tasting-notes "Mandarin, Honeycomb, Almond" -./target/debug/brewlog add-roast \ - --roaster-id "$(./target/debug/brewlog list-roasters | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \ +./target/debug/brewlog roast add \ + --roaster-id "$(./target/debug/brewlog roaster list | jq -r '.[] | select(.name=="Bows Coffee") | .id')" \ --name "Simbi" \ --origin "Rwanda" \ --region "Huye" \ @@ -420,74 +420,74 @@ fi # ============================================================================ # Tim Wendelboe - Ben Saïd Natural (250g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Ben Saïd Natural") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Ben Saïd Natural") | .id')" \ --roast-date "2026-01-15" \ --amount 250 # Tim Wendelboe - Finca Tamana Washed (350g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Finca Tamana Washed") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Finca Tamana Washed") | .id')" \ --roast-date "2026-01-18" \ --amount 350 # Coffee Collective - Daterra Sweet Collection (200g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Daterra Sweet Collection") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Daterra Sweet Collection") | .id')" \ --roast-date "2026-01-10" \ --amount 200 # Drop Coffee - La Linda (500g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="La Linda") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="La Linda") | .id')" \ --roast-date "2026-01-20" \ --amount 500 # La Cabra - Halo Beriti (150g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Halo Beriti") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Halo Beriti") | .id')" \ --roast-date "2026-01-12" \ --amount 150 # April Coffee - Guji Highland (300g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Guji Highland") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Guji Highland") | .id')" \ --roast-date "2026-01-22" \ --amount 300 # Assembly Coffee - Kochere (250g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Kochere") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Kochere") | .id')" \ --roast-date "2026-01-08" \ --amount 250 # Square Mile Coffee - Red Brick Espresso (400g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Red Brick Espresso") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Red Brick Espresso") | .id')" \ --roast-date "2026-01-25" \ --amount 400 # Dak Coffee Roasters - El Paraiso 92 Anaerobic (100g - small competition lot) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="El Paraiso 92 Anaerobic") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="El Paraiso 92 Anaerobic") | .id')" \ --roast-date "2026-01-28" \ --amount 100 # Bonanza Coffee - Gatomboya (175g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Gatomboya") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Gatomboya") | .id')" \ --roast-date "2026-01-05" \ --amount 175 # Stow Coffee - Suke Quto (225g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Suke Quto") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Suke Quto") | .id')" \ --roast-date "2026-01-30" \ --amount 225 # Bows Coffee - Simbi (450g) -./target/debug/brewlog add-bag \ - --roast-id "$(./target/debug/brewlog list-roasts | jq -r '.[] | select(.name=="Simbi") | .id')" \ +./target/debug/brewlog bag add \ + --roast-id "$(./target/debug/brewlog roast list | jq -r '.[] | select(.name=="Simbi") | .id')" \ --roast-date "2026-01-14" \ --amount 450 @@ -496,26 +496,26 @@ fi # ============================================================================ # Finish Gatomboya bag (oldest - Jan 5) -./target/debug/brewlog update-bag \ - --id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Gatomboya") | .id')" \ +./target/debug/brewlog bag update \ + --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Gatomboya") | .id')" \ --closed true \ --finished-at "2026-01-20" # Finish Kochere bag (Jan 8) -./target/debug/brewlog update-bag \ - --id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Kochere") | .id')" \ +./target/debug/brewlog bag update \ + --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Kochere") | .id')" \ --closed true \ --finished-at "2026-01-22" # Finish Daterra Sweet Collection bag (Jan 10) -./target/debug/brewlog update-bag \ - --id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Daterra Sweet Collection") | .id')" \ +./target/debug/brewlog bag update \ + --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Daterra Sweet Collection") | .id')" \ --closed true \ --finished-at "2026-01-25" # Finish Halo Beriti bag (Jan 12) -./target/debug/brewlog update-bag \ - --id "$(./target/debug/brewlog list-bags | jq -r '.[] | select(.roast_name=="Halo Beriti") | .id')" \ +./target/debug/brewlog bag update \ + --id "$(./target/debug/brewlog bag list | jq -r '.[] | select(.roast_name=="Halo Beriti") | .id')" \ --closed true \ --finished-at "2026-01-28" @@ -524,32 +524,33 @@ fi # ============================================================================ # Grinders -./target/debug/brewlog add-gear \ +./target/debug/brewlog gear add \ --category "grinder" \ --make "Comandante" \ --model "C40 MK4" -./target/debug/brewlog add-gear \ +./target/debug/brewlog gear add \ --category "grinder" \ --make "1Zpresso" \ --model "J-Max" # Brewers -./target/debug/brewlog add-gear \ +./target/debug/brewlog gear add \ --category "brewer" \ --make "Hario" \ --model "V60 02" -./target/debug/brewlog add-gear \ +./target/debug/brewlog gear add \ --category "brewer" \ --make "AeroPress" \ --model "Original" -./target/debug/brewlog add-gear \ +./target/debug/brewlog gear add \ --category "brewer" \ --make "Fellow" \ --model "Stagg XF" +echo echo "Bootstrapped database" echo echo "Set token $BREWLOG_TOKEN to use the data added here." \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index bdd9b3d..d262e1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,43 +21,25 @@ async fn main() -> Result<()> { match cli.command { Commands::Serve(cmd) => run_server(cmd).await, - command => { + Commands::Roaster { command } => { let client = BrewlogClient::from_base_url(&cli.api_url)?; - match command { - // Tokens - Commands::CreateToken(cmd) => tokens::create_token(&client, cmd).await, - Commands::ListTokens => tokens::list_tokens(&client).await, - Commands::RevokeToken(cmd) => tokens::revoke_token(&client, cmd).await, - - // Roasters - Commands::AddRoaster(cmd) => roasters::add_roaster(&client, cmd).await, - Commands::ListRoasters => roasters::list_roasters(&client).await, - Commands::GetRoaster(cmd) => roasters::get_roaster(&client, cmd).await, - Commands::UpdateRoaster(cmd) => roasters::update_roaster(&client, cmd).await, - Commands::DeleteRoaster(cmd) => roasters::delete_roaster(&client, cmd).await, - - // Roasts - Commands::AddRoast(cmd) => roasts::add_roast(&client, cmd).await, - Commands::ListRoasts(cmd) => roasts::list_roasts(&client, cmd).await, - Commands::GetRoast(cmd) => roasts::get_roast(&client, cmd).await, - Commands::DeleteRoast(cmd) => roasts::delete_roast(&client, cmd).await, - - // Bags - Commands::AddBag(cmd) => bags::add_bag(&client, cmd).await, - Commands::ListBags(cmd) => bags::list_bags(&client, cmd).await, - Commands::GetBag(cmd) => bags::get_bag(&client, cmd).await, - Commands::UpdateBag(cmd) => bags::update_bag(&client, cmd).await, - Commands::DeleteBag(cmd) => bags::delete_bag(&client, cmd).await, - - // Gear - Commands::AddGear(cmd) => gear::add_gear(&client, cmd).await, - Commands::ListGear(cmd) => gear::list_gear(&client, cmd).await, - Commands::GetGear(cmd) => gear::get_gear(&client, cmd).await, - Commands::UpdateGear(cmd) => gear::update_gear(&client, cmd).await, - Commands::DeleteGear(cmd) => gear::delete_gear(&client, cmd).await, - - Commands::Serve(_) => unreachable!("serve command handled earlier"), - } + roasters::run(&client, command).await + } + Commands::Roast { command } => { + let client = BrewlogClient::from_base_url(&cli.api_url)?; + roasts::run(&client, command).await + } + Commands::Bag { command } => { + let client = BrewlogClient::from_base_url(&cli.api_url)?; + bags::run(&client, command).await + } + Commands::Gear { command } => { + let client = BrewlogClient::from_base_url(&cli.api_url)?; + gear::run(&client, command).await + } + Commands::Token { command } => { + let client = BrewlogClient::from_base_url(&cli.api_url)?; + tokens::run(&client, command).await } } } diff --git a/src/presentation/cli/bags.rs b/src/presentation/cli/bags.rs index cc8cc91..0cab55e 100644 --- a/src/presentation/cli/bags.rs +++ b/src/presentation/cli/bags.rs @@ -1,11 +1,35 @@ use anyhow::Result; -use clap::Args; +use clap::{Args, Subcommand}; use super::macros::{define_delete_command, define_get_command}; use super::print_json; use crate::domain::ids::{BagId, RoastId}; use crate::infrastructure::client::BrewlogClient; +#[derive(Debug, Subcommand)] +pub enum BagCommands { + /// Add a new bag + Add(AddBagCommand), + /// List all bags + List(ListBagsCommand), + /// Get a bag by ID + Get(GetBagCommand), + /// Update a bag + Update(UpdateBagCommand), + /// Delete a bag + Delete(DeleteBagCommand), +} + +pub async fn run(client: &BrewlogClient, cmd: BagCommands) -> Result<()> { + match cmd { + BagCommands::Add(c) => add_bag(client, c).await, + BagCommands::List(c) => list_bags(client, c).await, + BagCommands::Get(c) => get_bag(client, c).await, + BagCommands::Update(c) => update_bag(client, c).await, + BagCommands::Delete(c) => delete_bag(client, c).await, + } +} + #[derive(Debug, Args)] pub struct AddBagCommand { #[arg(long)] diff --git a/src/presentation/cli/gear.rs b/src/presentation/cli/gear.rs index 922b96b..9d2fd0b 100644 --- a/src/presentation/cli/gear.rs +++ b/src/presentation/cli/gear.rs @@ -1,11 +1,35 @@ use anyhow::Result; -use clap::Args; +use clap::{Args, Subcommand}; use super::macros::{define_delete_command, define_get_command}; use super::print_json; use crate::domain::ids::GearId; use crate::infrastructure::client::BrewlogClient; +#[derive(Debug, Subcommand)] +pub enum GearCommands { + /// Add new gear + Add(AddGearCommand), + /// List all gear + List(ListGearCommand), + /// Get gear by ID + Get(GetGearCommand), + /// Update gear + Update(UpdateGearCommand), + /// Delete gear + Delete(DeleteGearCommand), +} + +pub async fn run(client: &BrewlogClient, cmd: GearCommands) -> Result<()> { + match cmd { + GearCommands::Add(c) => add_gear(client, c).await, + GearCommands::List(c) => list_gear(client, c).await, + GearCommands::Get(c) => get_gear(client, c).await, + GearCommands::Update(c) => update_gear(client, c).await, + GearCommands::Delete(c) => delete_gear(client, c).await, + } +} + #[derive(Debug, Args)] pub struct AddGearCommand { #[arg(long)] diff --git a/src/presentation/cli/mod.rs b/src/presentation/cli/mod.rs index ffc01fa..64861d5 100644 --- a/src/presentation/cli/mod.rs +++ b/src/presentation/cli/mod.rs @@ -7,12 +7,12 @@ pub mod tokens; use std::net::SocketAddr; -use bags::{AddBagCommand, DeleteBagCommand, GetBagCommand, ListBagsCommand, UpdateBagCommand}; +use bags::BagCommands; use clap::{Args, Parser, Subcommand}; -use gear::{AddGearCommand, DeleteGearCommand, GetGearCommand, ListGearCommand, UpdateGearCommand}; -use roasters::{AddRoasterCommand, DeleteRoasterCommand, GetRoasterCommand, UpdateRoasterCommand}; -use roasts::{AddRoastCommand, DeleteRoastCommand, GetRoastCommand, ListRoastsCommand}; -use tokens::{CreateTokenCommand, RevokeTokenCommand}; +use gear::GearCommands; +use roasters::RoasterCommands; +use roasts::RoastCommands; +use tokens::TokenCommands; #[derive(Debug, Parser)] #[command(author, version, about = "Track coffee roasts, brews, and cups", long_about = None)] @@ -31,62 +31,38 @@ pub struct Cli { #[derive(Debug, Subcommand)] pub enum Commands { - #[command(name = "serve")] + /// Run the HTTP server Serve(ServeCommand), - // Tokens - #[command(name = "create-token")] - CreateToken(CreateTokenCommand), - #[command(name = "list-tokens")] - ListTokens, - #[command(name = "revoke-token")] - RevokeToken(RevokeTokenCommand), + /// Manage roasters + Roaster { + #[command(subcommand)] + command: RoasterCommands, + }, - // Roasters - #[command(name = "add-roaster")] - AddRoaster(AddRoasterCommand), - #[command(name = "list-roasters")] - ListRoasters, - #[command(name = "get-roaster")] - GetRoaster(GetRoasterCommand), - #[command(name = "update-roaster")] - UpdateRoaster(UpdateRoasterCommand), - #[command(name = "delete-roaster")] - DeleteRoaster(DeleteRoasterCommand), + /// Manage roasts + Roast { + #[command(subcommand)] + command: RoastCommands, + }, - // Roasts - #[command(name = "add-roast")] - AddRoast(AddRoastCommand), - #[command(name = "list-roasts")] - ListRoasts(ListRoastsCommand), - #[command(name = "get-roast")] - GetRoast(GetRoastCommand), - #[command(name = "delete-roast")] - DeleteRoast(DeleteRoastCommand), + /// Manage bags + Bag { + #[command(subcommand)] + command: BagCommands, + }, - // Bags - #[command(name = "add-bag")] - AddBag(AddBagCommand), - #[command(name = "list-bags")] - ListBags(ListBagsCommand), - #[command(name = "get-bag")] - GetBag(GetBagCommand), - #[command(name = "update-bag")] - UpdateBag(UpdateBagCommand), - #[command(name = "delete-bag")] - DeleteBag(DeleteBagCommand), + /// Manage gear + Gear { + #[command(subcommand)] + command: GearCommands, + }, - // Gear - #[command(name = "add-gear")] - AddGear(AddGearCommand), - #[command(name = "list-gear")] - ListGear(ListGearCommand), - #[command(name = "get-gear")] - GetGear(GetGearCommand), - #[command(name = "update-gear")] - UpdateGear(UpdateGearCommand), - #[command(name = "delete-gear")] - DeleteGear(DeleteGearCommand), + /// Manage API tokens + Token { + #[command(subcommand)] + command: TokenCommands, + }, } #[derive(Debug, Args)] diff --git a/src/presentation/cli/roasters.rs b/src/presentation/cli/roasters.rs index bf039be..f0d082c 100644 --- a/src/presentation/cli/roasters.rs +++ b/src/presentation/cli/roasters.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use clap::Args; +use clap::{Args, Subcommand}; use super::macros::{define_delete_command, define_get_command}; use super::print_json; @@ -7,6 +7,30 @@ use crate::domain::ids::RoasterId; use crate::domain::roasters::{NewRoaster, UpdateRoaster}; use crate::infrastructure::client::BrewlogClient; +#[derive(Debug, Subcommand)] +pub enum RoasterCommands { + /// Add a new roaster + Add(AddRoasterCommand), + /// List all roasters + List, + /// Get a roaster by ID + Get(GetRoasterCommand), + /// Update a roaster + Update(UpdateRoasterCommand), + /// Delete a roaster + Delete(DeleteRoasterCommand), +} + +pub async fn run(client: &BrewlogClient, cmd: RoasterCommands) -> Result<()> { + match cmd { + RoasterCommands::Add(c) => add_roaster(client, c).await, + RoasterCommands::List => list_roasters(client).await, + RoasterCommands::Get(c) => get_roaster(client, c).await, + RoasterCommands::Update(c) => update_roaster(client, c).await, + RoasterCommands::Delete(c) => delete_roaster(client, c).await, + } +} + #[derive(Debug, Args)] pub struct AddRoasterCommand { #[arg(long)] diff --git a/src/presentation/cli/roasts.rs b/src/presentation/cli/roasts.rs index 2912e6c..a8390a6 100644 --- a/src/presentation/cli/roasts.rs +++ b/src/presentation/cli/roasts.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use clap::Args; +use clap::{Args, Subcommand}; use super::macros::{define_delete_command, define_get_command}; use super::print_json; @@ -7,6 +7,27 @@ use crate::domain::ids::{RoastId, RoasterId}; use crate::domain::roasts::NewRoast; use crate::infrastructure::client::BrewlogClient; +#[derive(Debug, Subcommand)] +pub enum RoastCommands { + /// Add a new roast + Add(AddRoastCommand), + /// List all roasts + List(ListRoastsCommand), + /// Get a roast by ID + Get(GetRoastCommand), + /// Delete a roast + Delete(DeleteRoastCommand), +} + +pub async fn run(client: &BrewlogClient, cmd: RoastCommands) -> Result<()> { + match cmd { + RoastCommands::Add(c) => add_roast(client, c).await, + RoastCommands::List(c) => list_roasts(client, c).await, + RoastCommands::Get(c) => get_roast(client, c).await, + RoastCommands::Delete(c) => delete_roast(client, c).await, + } +} + #[derive(Debug, Args)] pub struct AddRoastCommand { #[arg(long)] diff --git a/src/presentation/cli/tokens.rs b/src/presentation/cli/tokens.rs index 580df03..b694102 100644 --- a/src/presentation/cli/tokens.rs +++ b/src/presentation/cli/tokens.rs @@ -1,11 +1,29 @@ use anyhow::{Context, Result}; -use clap::Args; +use clap::{Args, Subcommand}; use std::io::{self, Write}; use super::print_json; use crate::domain::ids::TokenId; use crate::infrastructure::client::BrewlogClient; +#[derive(Debug, Subcommand)] +pub enum TokenCommands { + /// Create a new API token + Create(CreateTokenCommand), + /// List all tokens + List, + /// Revoke a token + Revoke(RevokeTokenCommand), +} + +pub async fn run(client: &BrewlogClient, cmd: TokenCommands) -> Result<()> { + match cmd { + TokenCommands::Create(c) => create_token(client, c).await, + TokenCommands::List => list_tokens(client).await, + TokenCommands::Revoke(c) => revoke_token(client, c).await, + } +} + #[derive(Debug, Args)] pub struct CreateTokenCommand { /// A descriptive name for this token diff --git a/tests/cli/bags_cli.rs b/tests/cli/bags_cli.rs index c1cc9ad..5fc6396 100644 --- a/tests/cli/bags_cli.rs +++ b/tests/cli/bags_cli.rs @@ -5,9 +5,12 @@ use serde_json::Value; fn test_add_bag_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["add-bag", "--roast-id", "123", "--amount", "250.0"], &[]); + let output = run_brewlog( + &["bag", "add", "--roast-id", "123", "--amount", "250.0"], + &[], + ); - assert!(!output.status.success(), "add-bag without auth should fail"); + assert!(!output.status.success(), "bag add without auth should fail"); } #[test] @@ -21,7 +24,8 @@ fn test_add_bag_with_authentication() { // Test: Add Bag let output = run_brewlog( &[ - "add-bag", + "bag", + "add", "--roast-id", &roast_id, "--amount", @@ -44,11 +48,14 @@ fn test_add_bag_with_authentication() { fn test_update_bag_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["update-bag", "--id", "123", "--remaining", "100.0"], &[]); + let output = run_brewlog( + &["bag", "update", "--id", "123", "--remaining", "100.0"], + &[], + ); assert!( !output.status.success(), - "update-bag without auth should fail" + "bag update without auth should fail" ); } @@ -61,7 +68,7 @@ fn test_update_bag_with_authentication() { let roast_id = create_roast(&roaster_id, "Bag Update Roast", &token); let bag_output = run_brewlog( - &["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], + &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"], &[("BREWLOG_TOKEN", &token)], ); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); @@ -70,7 +77,8 @@ fn test_update_bag_with_authentication() { // Test: Update Bag let output = run_brewlog( &[ - "update-bag", + "bag", + "update", "--id", &bag_id, "--remaining", @@ -92,7 +100,7 @@ fn test_update_bag_with_authentication() { fn test_list_bags_works_without_authentication() { let _ = server_info(); // Listing without roast_id might return all or empty, but should succeed (200 OK) - let output = run_brewlog(&["list-bags"], &[]); + let output = run_brewlog(&["bag", "list"], &[]); assert!(output.status.success()); } @@ -105,7 +113,7 @@ fn test_list_bags_shows_added_bag() { let roast_id = create_roast(&roaster_id, "Bag List Roast", &token); let bag_output = run_brewlog( - &["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], + &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"], &[("BREWLOG_TOKEN", &token)], ); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); @@ -113,7 +121,7 @@ fn test_list_bags_shows_added_bag() { // Test: List Bags (Authenticated) let output = run_brewlog( - &["list-bags", "--roast-id", &roast_id], + &["bag", "list", "--roast-id", &roast_id], &[("BREWLOG_TOKEN", &token)], ); @@ -133,14 +141,14 @@ fn test_list_bags_without_roast_id_shows_all_bags() { let roast_id = create_roast(&roaster_id, "Bag List All Roast", &token); let bag_output = run_brewlog( - &["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], + &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"], &[("BREWLOG_TOKEN", &token)], ); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); let bag_id = bag["id"].as_i64().unwrap(); // Test: List Bags (Authenticated) - let output = run_brewlog(&["list-bags"], &[("BREWLOG_TOKEN", &token)]); + let output = run_brewlog(&["bag", "list"], &[("BREWLOG_TOKEN", &token)]); assert!(output.status.success()); let bags: Value = serde_json::from_slice(&output.stdout).unwrap(); @@ -159,7 +167,7 @@ fn test_list_bags_shows_open_and_closed_bags() { // Create Open Bag let open_bag_output = run_brewlog( - &["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], + &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"], &[("BREWLOG_TOKEN", &token)], ); let open_bag: Value = serde_json::from_slice(&open_bag_output.stdout).unwrap(); @@ -167,7 +175,7 @@ fn test_list_bags_shows_open_and_closed_bags() { // Create Closed Bag let closed_bag_output = run_brewlog( - &["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], + &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"], &[("BREWLOG_TOKEN", &token)], ); let closed_bag: Value = serde_json::from_slice(&closed_bag_output.stdout).unwrap(); @@ -176,7 +184,8 @@ fn test_list_bags_shows_open_and_closed_bags() { // Close the second bag let _ = run_brewlog( &[ - "update-bag", + "bag", + "update", "--id", &closed_bag_id.to_string(), "--closed", @@ -186,7 +195,7 @@ fn test_list_bags_shows_open_and_closed_bags() { ); // Test: List Bags (Authenticated) - let output = run_brewlog(&["list-bags"], &[("BREWLOG_TOKEN", &token)]); + let output = run_brewlog(&["bag", "list"], &[("BREWLOG_TOKEN", &token)]); assert!(output.status.success()); let bags: Value = serde_json::from_slice(&output.stdout).unwrap(); @@ -210,7 +219,7 @@ fn test_list_bags_shows_open_and_closed_bags() { #[test] fn test_delete_bag_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["delete-bag", "--id", "123"], &[]); + let output = run_brewlog(&["bag", "delete", "--id", "123"], &[]); assert!(!output.status.success()); } @@ -223,7 +232,7 @@ fn test_delete_bag_with_authentication() { let roast_id = create_roast(&roaster_id, "Bag Delete Roast", &token); let bag_output = run_brewlog( - &["add-bag", "--roast-id", &roast_id, "--amount", "250.0"], + &["bag", "add", "--roast-id", &roast_id, "--amount", "250.0"], &[("BREWLOG_TOKEN", &token)], ); let bag: Value = serde_json::from_slice(&bag_output.stdout).unwrap(); @@ -231,12 +240,12 @@ fn test_delete_bag_with_authentication() { // Test: Delete Bag let output = run_brewlog( - &["delete-bag", "--id", &bag_id], + &["bag", "delete", "--id", &bag_id], &[("BREWLOG_TOKEN", &token)], ); assert!(output.status.success()); // Verify deletion - let get_output = run_brewlog(&["get-bag", "--id", &bag_id], &[]); + let get_output = run_brewlog(&["bag", "get", "--id", &bag_id], &[]); assert!(!get_output.status.success()); } diff --git a/tests/cli/gear_cli.rs b/tests/cli/gear_cli.rs index a6d948e..b87f378 100644 --- a/tests/cli/gear_cli.rs +++ b/tests/cli/gear_cli.rs @@ -7,7 +7,8 @@ fn test_add_gear_requires_authentication() { let output = run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -20,7 +21,7 @@ fn test_add_gear_requires_authentication() { assert!( !output.status.success(), - "add-gear without auth should fail" + "gear add without auth should fail" ); } @@ -30,7 +31,8 @@ fn test_add_gear_with_authentication() { let output = run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -54,11 +56,11 @@ fn test_add_gear_with_authentication() { fn test_list_gear_works_without_authentication() { let _ = server_info(); - let output = run_brewlog(&["list-gear"], &[]); + let output = run_brewlog(&["gear", "list"], &[]); assert!( output.status.success(), - "list-gear should work without auth" + "gear list should work without auth" ); } @@ -69,7 +71,8 @@ fn test_list_gear_shows_added_gear() { // Add gear let add_output = run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -82,7 +85,7 @@ fn test_list_gear_shows_added_gear() { assert!(add_output.status.success()); // List gear - let list_output = run_brewlog(&["list-gear"], &[]); + let list_output = run_brewlog(&["gear", "list"], &[]); assert!(list_output.status.success()); let gear_list: Value = serde_json::from_slice(&list_output.stdout).unwrap(); @@ -103,7 +106,8 @@ fn test_list_gear_filtered_by_category() { // Add grinder run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -117,7 +121,8 @@ fn test_list_gear_filtered_by_category() { // Add brewer run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "brewer", "--make", @@ -129,7 +134,7 @@ fn test_list_gear_filtered_by_category() { ); // List only grinders - let output = run_brewlog(&["list-gear", "--category", "grinder"], &[]); + let output = run_brewlog(&["gear", "list", "--category", "grinder"], &[]); assert!(output.status.success()); let gear_list: Value = serde_json::from_slice(&output.stdout).unwrap(); @@ -151,7 +156,8 @@ fn test_get_gear_by_id() { // Add gear let add_output = run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -165,7 +171,7 @@ fn test_get_gear_by_id() { let gear_id = gear["id"].as_i64().unwrap().to_string(); // Get gear by ID - let output = run_brewlog(&["get-gear", "--id", &gear_id], &[]); + let output = run_brewlog(&["gear", "get", "--id", &gear_id], &[]); assert!(output.status.success()); let retrieved_gear: Value = serde_json::from_slice(&output.stdout).unwrap(); @@ -177,11 +183,11 @@ fn test_get_gear_by_id() { fn test_update_gear_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["update-gear", "--id", "123", "--make", "Updated"], &[]); + let output = run_brewlog(&["gear", "update", "--id", "123", "--make", "Updated"], &[]); assert!( !output.status.success(), - "update-gear without auth should fail" + "gear update without auth should fail" ); } @@ -192,7 +198,8 @@ fn test_update_gear_with_authentication() { // Add gear let add_output = run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -207,7 +214,7 @@ fn test_update_gear_with_authentication() { // Update gear let output = run_brewlog( - &["update-gear", "--id", &gear_id, "--model", "Mini II"], + &["gear", "update", "--id", &gear_id, "--model", "Mini II"], &[("BREWLOG_TOKEN", &token)], ); @@ -221,11 +228,11 @@ fn test_update_gear_with_authentication() { fn test_delete_gear_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["delete-gear", "--id", "123"], &[]); + let output = run_brewlog(&["gear", "delete", "--id", "123"], &[]); assert!( !output.status.success(), - "delete-gear without auth should fail" + "gear delete without auth should fail" ); } @@ -236,7 +243,8 @@ fn test_delete_gear_with_authentication() { // Add gear let add_output = run_brewlog( &[ - "add-gear", + "gear", + "add", "--category", "grinder", "--make", @@ -251,12 +259,12 @@ fn test_delete_gear_with_authentication() { // Delete gear let delete_output = run_brewlog( - &["delete-gear", "--id", &gear_id], + &["gear", "delete", "--id", &gear_id], &[("BREWLOG_TOKEN", &token)], ); assert!(delete_output.status.success()); // Verify deletion - get should fail - let get_output = run_brewlog(&["get-gear", "--id", &gear_id], &[]); + let get_output = run_brewlog(&["gear", "get", "--id", &gear_id], &[]); assert!(!get_output.status.success()); } diff --git a/tests/cli/helpers.rs b/tests/cli/helpers.rs index 8671e90..c9e315e 100644 --- a/tests/cli/helpers.rs +++ b/tests/cli/helpers.rs @@ -119,7 +119,8 @@ pub fn create_token(name: &str) -> String { let output = run_brewlog( &[ - "create-token", + "token", + "create", "--name", name, "--username", @@ -165,7 +166,7 @@ pub fn run_brewlog(args: &[&str], env: &[(&str, &str)]) -> std::process::Output /// Helper to create a roaster and return its ID pub fn create_roaster(name: &str, token: &str) -> String { let output = run_brewlog( - &["add-roaster", "--name", name, "--country", "UK"], + &["roaster", "add", "--name", name, "--country", "UK"], &[("BREWLOG_TOKEN", token)], ); @@ -189,7 +190,8 @@ pub fn create_roaster(name: &str, token: &str) -> String { pub fn create_roast(roaster_id: &str, name: &str, token: &str) -> String { let output = run_brewlog( &[ - "add-roast", + "roast", + "add", "--roaster-id", roaster_id, "--name", diff --git a/tests/cli/roasters_cli.rs b/tests/cli/roasters_cli.rs index 19ad5ed..df31105 100644 --- a/tests/cli/roasters_cli.rs +++ b/tests/cli/roasters_cli.rs @@ -6,13 +6,20 @@ fn test_add_roaster_requires_authentication() { let _ = server_info(); // Ensure server is started let output = run_brewlog( - &["add-roaster", "--name", "Test Roasters", "--country", "UK"], + &[ + "roaster", + "add", + "--name", + "Test Roasters", + "--country", + "UK", + ], &[], ); assert!( !output.status.success(), - "add-roaster without auth should fail" + "roaster add without auth should fail" ); } @@ -21,13 +28,20 @@ fn test_add_roaster_with_authentication() { let token = create_token("test-add-roaster"); let output = run_brewlog( - &["add-roaster", "--name", "Test Roasters", "--country", "UK"], + &[ + "roaster", + "add", + "--name", + "Test Roasters", + "--country", + "UK", + ], &[("BREWLOG_TOKEN", &token)], ); assert!( output.status.success(), - "add-roaster with auth should succeed: {}", + "roaster add with auth should succeed: {}", String::from_utf8_lossy(&output.stderr) ); @@ -44,11 +58,11 @@ fn test_add_roaster_with_authentication() { fn test_list_roasters_works_without_authentication() { let _ = server_info(); - let output = run_brewlog(&["list-roasters"], &[]); + let output = run_brewlog(&["roaster", "list"], &[]); assert!( output.status.success(), - "list-roasters should work without auth" + "roaster list should work without auth" ); let stdout = String::from_utf8_lossy(&output.stdout); @@ -65,7 +79,7 @@ fn test_list_roasters_shows_added_roaster() { let roaster_id = create_roaster("Example Roasters", &token); // List roasters - let list_output = run_brewlog(&["list-roasters"], &[]); + let list_output = run_brewlog(&["roaster", "list"], &[]); assert!(list_output.status.success()); @@ -87,11 +101,11 @@ fn test_list_roasters_shows_added_roaster() { fn test_delete_roaster_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["delete-roaster", "--id", "some-id"], &[]); + let output = run_brewlog(&["roaster", "delete", "--id", "some-id"], &[]); assert!( !output.status.success(), - "delete-roaster without auth should fail" + "roaster delete without auth should fail" ); } @@ -101,7 +115,8 @@ fn test_update_roaster_requires_authentication() { let output = run_brewlog( &[ - "update-roaster", + "roaster", + "update", "--id", "some-id", "--name", @@ -112,6 +127,6 @@ fn test_update_roaster_requires_authentication() { assert!( !output.status.success(), - "update-roaster without auth should fail" + "roaster update without auth should fail" ); } diff --git a/tests/cli/roasts_cli.rs b/tests/cli/roasts_cli.rs index 856f9c1..cec0a52 100644 --- a/tests/cli/roasts_cli.rs +++ b/tests/cli/roasts_cli.rs @@ -7,7 +7,8 @@ fn test_add_roast_requires_authentication() { let output = run_brewlog( &[ - "add-roast", + "roast", + "add", "--roaster-id", "some-id", "--name", @@ -26,7 +27,7 @@ fn test_add_roast_requires_authentication() { assert!( !output.status.success(), - "add-roast without auth should fail" + "roast add without auth should fail" ); } @@ -40,7 +41,8 @@ fn test_add_roast_with_authentication() { // Now add a roast let output = run_brewlog( &[ - "add-roast", + "roast", + "add", "--roaster-id", &roaster_id, "--name", @@ -61,7 +63,7 @@ fn test_add_roast_with_authentication() { assert!( output.status.success(), - "add-roast with auth should succeed: {}", + "roast add with auth should succeed: {}", String::from_utf8_lossy(&output.stderr) ); @@ -80,11 +82,11 @@ fn test_add_roast_with_authentication() { fn test_list_roasts_works_without_authentication() { let _ = server_info(); - let output = run_brewlog(&["list-roasts"], &[]); + let output = run_brewlog(&["roast", "list"], &[]); assert!( output.status.success(), - "list-roasts should work without auth" + "roast list should work without auth" ); let stdout = String::from_utf8_lossy(&output.stdout); @@ -103,7 +105,8 @@ fn test_list_roasts_shows_added_roast() { // Add a roast let add_output = run_brewlog( &[ - "add-roast", + "roast", + "add", "--roaster-id", &roaster_id, "--name", @@ -131,7 +134,7 @@ fn test_list_roasts_shows_added_roast() { .expect("roast id should be numeric"); // List roasts - let list_output = run_brewlog(&["list-roasts"], &[]); + let list_output = run_brewlog(&["roast", "list"], &[]); assert!(list_output.status.success()); @@ -157,10 +160,10 @@ fn test_list_roasts_shows_added_roast() { fn test_delete_roast_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["delete-roast", "--id", "some-id"], &[]); + let output = run_brewlog(&["roast", "delete", "--id", "some-id"], &[]); assert!( !output.status.success(), - "delete-roast without auth should fail" + "roast delete without auth should fail" ); } diff --git a/tests/cli/tokens_cli.rs b/tests/cli/tokens_cli.rs index 8d3d5b9..2866f01 100644 --- a/tests/cli/tokens_cli.rs +++ b/tests/cli/tokens_cli.rs @@ -4,11 +4,11 @@ use crate::helpers::{create_token, run_brewlog, server_info}; fn test_list_tokens_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["list-tokens"], &[]); + let output = run_brewlog(&["token", "list"], &[]); assert!( !output.status.success(), - "list-tokens without auth should fail" + "token list without auth should fail" ); } @@ -16,11 +16,11 @@ fn test_list_tokens_requires_authentication() { fn test_list_tokens_with_authentication() { let token = create_token("test-list-tokens"); - let output = run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &token)]); + let output = run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &token)]); assert!( output.status.success(), - "list-tokens with auth should succeed" + "token list with auth should succeed" ); let stdout = String::from_utf8_lossy(&output.stdout); @@ -34,11 +34,11 @@ fn test_list_tokens_with_authentication() { fn test_revoke_token_requires_authentication() { let _ = server_info(); - let output = run_brewlog(&["revoke-token", "--id", "1"], &[]); + let output = run_brewlog(&["token", "revoke", "--id", "1"], &[]); assert!( !output.status.success(), - "revoke-token without auth should fail" + "token revoke without auth should fail" ); } @@ -47,7 +47,7 @@ fn test_revoke_token_with_authentication() { let token = create_token("test-revoke-token"); // List tokens to get the ID - let list_output = run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &token)]); + let list_output = run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &token)]); assert!(list_output.status.success()); let list_stdout = String::from_utf8_lossy(&list_output.stdout); @@ -66,7 +66,7 @@ fn test_revoke_token_with_authentication() { .expect("Token should have ID"); let revoke_output = run_brewlog( - &["revoke-token", "--id", &token_id.to_string()], + &["token", "revoke", "--id", &token_id.to_string()], &[("BREWLOG_TOKEN", &token)], ); @@ -85,7 +85,7 @@ fn test_revoked_token_cannot_be_used() { let admin_token = create_token("test-admin-token"); // List tokens to get the ID of the token we want to revoke - let list_output = run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &admin_token)]); + let list_output = run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &admin_token)]); assert!(list_output.status.success()); let list_stdout = String::from_utf8_lossy(&list_output.stdout); @@ -105,7 +105,7 @@ fn test_revoked_token_cannot_be_used() { // Revoke the token let revoke_output = run_brewlog( - &["revoke-token", "--id", &token_id.to_string()], + &["token", "revoke", "--id", &token_id.to_string()], &[("BREWLOG_TOKEN", &admin_token)], ); assert!( @@ -115,7 +115,7 @@ fn test_revoked_token_cannot_be_used() { // Try to use the revoked token - it should fail let list_with_revoked_output = - run_brewlog(&["list-tokens"], &[("BREWLOG_TOKEN", &token_to_revoke)]); + run_brewlog(&["token", "list"], &[("BREWLOG_TOKEN", &token_to_revoke)]); assert!( !list_with_revoked_output.status.success(),