brewlog/tests/server/gear_api.rs
Jon Seager c1a5d763e2
test(gear): add comprehensive test suite for gear entity
Implement complete test coverage for the Gear entity across CLI, API,
timeline, and Datastar integration layers. This completes the Gear
entity implementation with 32 dedicated tests ensuring all CRUD
operations, authentication requirements, and reactive UI updates work
correctly.

Test Coverage:
- CLI commands (11 tests): Authentication, CRUD operations, category
  filtering, notes handling
- API endpoints (13 tests): HTTP status codes, validation, auth
  requirements, filtering
- Timeline integration (1 test): Gear creation events appear correctly
- Datastar fragments (7 tests): Fragment rendering vs full page/JSON
  responses for list, create, update, delete

Changes:
- Add tests/cli/gear_cli.rs with CLI integration tests
- Add tests/server/gear_api.rs with API integration tests
- Update tests/server/timeline.rs with gear timeline test
- Update tests/server/datastar.rs with gear fragment rendering tests
- Register test modules in tests/cli/main.rs and tests/server/main.rs
- Clean up unused imports in tests/server/helpers.rs

All 140 tests pass (8 unit + 37 CLI + 95 server integration).
2026-02-02 16:09:02 +00:00

391 lines
9.7 KiB
Rust

use crate::helpers::{spawn_app, spawn_app_with_auth};
use brewlog::domain::gear::{Gear, UpdateGear};
#[tokio::test]
async fn creating_gear_returns_201_for_valid_data() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore",
"notes": "Daily driver"
});
// Act
let response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 201);
let gear: Gear = response.json().await.expect("Failed to parse response");
assert_eq!(gear.make, "Baratza");
assert_eq!(gear.model, "Encore");
assert_eq!(gear.notes, Some("Daily driver".to_string()));
}
#[tokio::test]
async fn creating_gear_without_auth_returns_401() {
// Arrange
let app = spawn_app().await;
let client = reqwest::Client::new();
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore"
});
// Act
let response = client
.post(app.api_url("/gear"))
.json(&new_gear)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn creating_gear_with_invalid_category_returns_400() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
let new_gear = serde_json::json!({
"category": "invalid_category",
"make": "Baratza",
"model": "Encore"
});
// Act
let response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 400);
}
#[tokio::test]
async fn creating_gear_with_empty_make_returns_400() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
let new_gear = serde_json::json!({
"category": "grinder",
"make": "",
"model": "Encore"
});
// Act
let response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 400);
}
#[tokio::test]
async fn creating_gear_with_empty_model_returns_400() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": ""
});
// Act
let response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 400);
}
#[tokio::test]
async fn listing_gear_returns_200_and_correct_data() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
// Create a gear
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore"
});
client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to create gear");
// Act
let response = client
.get(app.api_url("/gear"))
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 200);
let gear_list: Vec<Gear> = response.json().await.expect("Failed to parse response");
assert!(!gear_list.is_empty());
assert!(gear_list.iter().any(|g| g.make == "Baratza"));
}
#[tokio::test]
async fn listing_gear_filtered_by_category() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
// Create grinder
let grinder = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore"
});
client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&grinder)
.send()
.await
.expect("Failed to create grinder");
// Create brewer
let brewer = serde_json::json!({
"category": "brewer",
"make": "Hario",
"model": "V60"
});
client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&brewer)
.send()
.await
.expect("Failed to create brewer");
// Act - filter by grinder category
let response = client
.get(app.api_url("/gear?category=grinder"))
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 200);
let gear_list: Vec<Gear> = response.json().await.expect("Failed to parse response");
assert!(gear_list.iter().all(|g| g.category.as_str() == "grinder"));
assert!(gear_list.iter().any(|g| g.make == "Baratza"));
}
#[tokio::test]
async fn getting_gear_by_id_returns_correct_data() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
// Create gear
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore"
});
let create_response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to create gear");
let created_gear: Gear = create_response.json().await.unwrap();
// Act
let response = client
.get(app.api_url(&format!("/gear/{}", created_gear.id)))
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 200);
let gear: Gear = response.json().await.expect("Failed to parse response");
assert_eq!(gear.id, created_gear.id);
assert_eq!(gear.make, "Baratza");
assert_eq!(gear.model, "Encore");
}
#[tokio::test]
async fn updating_gear_returns_updated_data() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
// Create gear
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore"
});
let create_response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to create gear");
let created_gear: Gear = create_response.json().await.unwrap();
// Act
let update = UpdateGear {
make: Some("Comandante".to_string()),
model: Some("C40".to_string()),
notes: Some("Upgraded grinder".to_string()),
};
let response = client
.put(app.api_url(&format!("/gear/{}", created_gear.id)))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&update)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 200);
let updated_gear: Gear = response.json().await.expect("Failed to parse response");
assert_eq!(updated_gear.id, created_gear.id);
assert_eq!(updated_gear.make, "Comandante");
assert_eq!(updated_gear.model, "C40");
assert_eq!(updated_gear.notes, Some("Upgraded grinder".to_string()));
}
#[tokio::test]
async fn updating_gear_without_auth_returns_401() {
// Arrange
let app = spawn_app().await;
let client = reqwest::Client::new();
let update = UpdateGear {
make: Some("Updated".to_string()),
model: None,
notes: None,
};
// Act
let response = client
.put(app.api_url("/gear/123"))
.json(&update)
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 401);
}
#[tokio::test]
async fn deleting_gear_returns_204() {
// Arrange
let app = spawn_app_with_auth().await;
let client = reqwest::Client::new();
// Create gear
let new_gear = serde_json::json!({
"category": "grinder",
"make": "Baratza",
"model": "Encore"
});
let create_response = client
.post(app.api_url("/gear"))
.bearer_auth(app.auth_token.as_ref().unwrap())
.json(&new_gear)
.send()
.await
.expect("Failed to create gear");
let created_gear: Gear = create_response.json().await.unwrap();
// Act
let response = client
.delete(app.api_url(&format!("/gear/{}", created_gear.id)))
.bearer_auth(app.auth_token.as_ref().unwrap())
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 204);
// Verify gear is actually deleted
let get_response = client
.get(app.api_url(&format!("/gear/{}", created_gear.id)))
.send()
.await
.expect("Failed to execute request");
assert_eq!(get_response.status(), 404);
}
#[tokio::test]
async fn deleting_gear_without_auth_returns_401() {
// Arrange
let app = spawn_app().await;
let client = reqwest::Client::new();
// Act
let response = client
.delete(app.api_url("/gear/123"))
.send()
.await
.expect("Failed to execute request");
// Assert
assert_eq!(response.status(), 401);
}