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).
391 lines
9.7 KiB
Rust
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);
|
|
}
|