test: add wiremock dependency and test infrastructure
Co-authored-by: jnsgruk <668505+jnsgruk@users.noreply.github.com>
This commit is contained in:
parent
9c4734172f
commit
52301e64b9
6 changed files with 976 additions and 0 deletions
108
Cargo.lock
generated
108
Cargo.lock
generated
|
|
@ -139,6 +139,16 @@ dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert-json-diff"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.89"
|
version = "0.1.89"
|
||||||
|
|
@ -315,6 +325,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"wiremock",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -488,6 +499,24 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deadpool"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
|
||||||
|
dependencies = [
|
||||||
|
"deadpool-runtime",
|
||||||
|
"lazy_static",
|
||||||
|
"num_cpus",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deadpool-runtime"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
|
|
@ -632,6 +661,21 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
|
@ -676,6 +720,17 @@ version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.110",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
|
@ -694,8 +749,10 @@ version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -800,6 +857,12 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -1366,6 +1429,16 @@ dependencies = [
|
||||||
"libm",
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
|
|
@ -1679,6 +1752,18 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
|
@ -3137,6 +3222,29 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wiremock"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031"
|
||||||
|
dependencies = [
|
||||||
|
"assert-json-diff",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"deadpool",
|
||||||
|
"futures",
|
||||||
|
"http",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,7 @@ thiserror = "1.0"
|
||||||
tokio = { version = "1.38", features = ["rt-multi-thread", "macros", "signal"] }
|
tokio = { version = "1.38", features = ["rt-multi-thread", "macros", "signal"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { version = "1.38", features = ["rt-multi-thread", "macros"] }
|
||||||
|
wiremock = "0.6"
|
||||||
|
|
|
||||||
5
tests/server.rs
Normal file
5
tests/server.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod server {
|
||||||
|
pub mod helpers;
|
||||||
|
pub mod roasters;
|
||||||
|
pub mod roasts;
|
||||||
|
}
|
||||||
71
tests/server/helpers.rs
Normal file
71
tests/server/helpers.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use brewlog::domain::repositories::{RoastRepository, RoasterRepository, TimelineEventRepository};
|
||||||
|
use brewlog::infrastructure::database::Database;
|
||||||
|
use brewlog::infrastructure::repositories::roasters::SqlRoasterRepository;
|
||||||
|
use brewlog::infrastructure::repositories::roasts::SqlRoastRepository;
|
||||||
|
use brewlog::infrastructure::repositories::timeline_events::SqlTimelineEventRepository;
|
||||||
|
use brewlog::server::routes::app_router;
|
||||||
|
use brewlog::server::server::AppState;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
pub struct TestApp {
|
||||||
|
pub address: String,
|
||||||
|
pub roaster_repo: Arc<dyn RoasterRepository>,
|
||||||
|
pub roast_repo: Arc<dyn RoastRepository>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub timeline_repo: Arc<dyn TimelineEventRepository>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestApp {
|
||||||
|
pub fn api_url(&self, path: &str) -> String {
|
||||||
|
format!("{}/api/v1{}", self.address, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn spawn_app() -> TestApp {
|
||||||
|
// Use in-memory SQLite database for testing
|
||||||
|
let database = Database::connect("sqlite::memory:")
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to in-memory database");
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
database.migrate().await.expect("Failed to migrate database");
|
||||||
|
|
||||||
|
// Create repositories
|
||||||
|
let roaster_repo = Arc::new(SqlRoasterRepository::new(database.clone_pool()));
|
||||||
|
let roast_repo = Arc::new(SqlRoastRepository::new(database.clone_pool()));
|
||||||
|
let timeline_repo = Arc::new(SqlTimelineEventRepository::new(database.clone_pool()));
|
||||||
|
|
||||||
|
// Create application state
|
||||||
|
let state = AppState::new(
|
||||||
|
roaster_repo.clone(),
|
||||||
|
roast_repo.clone(),
|
||||||
|
timeline_repo.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create router
|
||||||
|
let app = app_router(state);
|
||||||
|
|
||||||
|
// Bind to a random port
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0")
|
||||||
|
.await
|
||||||
|
.expect("Failed to bind to random port");
|
||||||
|
|
||||||
|
let local_addr = listener.local_addr().expect("Failed to get local address");
|
||||||
|
let address = format!("http://{}", local_addr);
|
||||||
|
|
||||||
|
// Spawn the server in a background task
|
||||||
|
tokio::spawn(async move {
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.await
|
||||||
|
.expect("Server failed to start");
|
||||||
|
});
|
||||||
|
|
||||||
|
TestApp {
|
||||||
|
address,
|
||||||
|
roaster_repo,
|
||||||
|
roast_repo,
|
||||||
|
timeline_repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
375
tests/server/roasters.rs
Normal file
375
tests/server/roasters.rs
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
use crate::server::helpers::spawn_app;
|
||||||
|
use brewlog::domain::roasters::{NewRoaster, Roaster, UpdateRoaster};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creating_a_roaster_returns_a_201_for_valid_data() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "Test Roasters".to_string(),
|
||||||
|
country: "United Kingdom".to_string(),
|
||||||
|
city: Some("London".to_string()),
|
||||||
|
homepage: Some("https://example.com".to_string()),
|
||||||
|
notes: Some("Great coffee".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 201);
|
||||||
|
|
||||||
|
let roaster: Roaster = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roaster.name, "Test Roasters");
|
||||||
|
assert_eq!(roaster.country, "United Kingdom");
|
||||||
|
assert_eq!(roaster.city, Some("London".to_string()));
|
||||||
|
assert_eq!(roaster.homepage, Some("https://example.com".to_string()));
|
||||||
|
assert_eq!(roaster.notes, Some("Great coffee".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creating_a_roaster_persists_the_data() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "Persistent Roasters".to_string(),
|
||||||
|
country: "France".to_string(),
|
||||||
|
city: Some("Paris".to_string()),
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
let roaster: Roaster = response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
// Assert - Fetch the roaster to verify it was persisted
|
||||||
|
let fetched_roaster = app
|
||||||
|
.roaster_repo
|
||||||
|
.get(roaster.id.clone())
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch roaster");
|
||||||
|
|
||||||
|
assert_eq!(fetched_roaster.name, "Persistent Roasters");
|
||||||
|
assert_eq!(fetched_roaster.country, "France");
|
||||||
|
assert_eq!(fetched_roaster.city, Some("Paris".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn getting_a_roaster_returns_a_200_for_valid_id() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "Fetchable Roasters".to_string(),
|
||||||
|
country: "Germany".to_string(),
|
||||||
|
city: Some("Berlin".to_string()),
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roaster");
|
||||||
|
|
||||||
|
let created_roaster: Roaster = create_response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url(&format!("/roasters/{}", created_roaster.id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roaster: Roaster = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roaster.id, created_roaster.id);
|
||||||
|
assert_eq!(roaster.name, "Fetchable Roasters");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn getting_a_nonexistent_roaster_returns_a_404() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url("/roasters/nonexistent-id"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn listing_roasters_returns_a_200_with_empty_list() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url("/roasters"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roasters: Vec<Roaster> = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roasters.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn listing_roasters_returns_a_200_with_multiple_roasters() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Create multiple roasters
|
||||||
|
let roaster1 = NewRoaster {
|
||||||
|
name: "First Roasters".to_string(),
|
||||||
|
country: "UK".to_string(),
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let roaster2 = NewRoaster {
|
||||||
|
name: "Second Roasters".to_string(),
|
||||||
|
country: "USA".to_string(),
|
||||||
|
city: Some("New York".to_string()),
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&roaster1)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create first roaster");
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&roaster2)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create second roaster");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url("/roasters"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roasters: Vec<Roaster> = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roasters.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn updating_a_roaster_returns_a_200_for_valid_data() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "Original Name".to_string(),
|
||||||
|
country: "UK".to_string(),
|
||||||
|
city: Some("Manchester".to_string()),
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roaster");
|
||||||
|
|
||||||
|
let created_roaster: Roaster = create_response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
let update = UpdateRoaster {
|
||||||
|
name: Some("Updated Name".to_string()),
|
||||||
|
country: None,
|
||||||
|
city: Some("Liverpool".to_string()),
|
||||||
|
homepage: Some("https://updated.com".to_string()),
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.put(app.api_url(&format!("/roasters/{}", created_roaster.id)))
|
||||||
|
.json(&update)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let updated_roaster: Roaster = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(updated_roaster.name, "Updated Name");
|
||||||
|
assert_eq!(updated_roaster.country, "UK"); // Should remain unchanged
|
||||||
|
assert_eq!(updated_roaster.city, Some("Liverpool".to_string()));
|
||||||
|
assert_eq!(updated_roaster.homepage, Some("https://updated.com".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn updating_a_roaster_with_no_changes_returns_a_400() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "Test Roaster".to_string(),
|
||||||
|
country: "UK".to_string(),
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roaster");
|
||||||
|
|
||||||
|
let created_roaster: Roaster = create_response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
let update = UpdateRoaster {
|
||||||
|
name: None,
|
||||||
|
country: None,
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.put(app.api_url(&format!("/roasters/{}", created_roaster.id)))
|
||||||
|
.json(&update)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn updating_a_nonexistent_roaster_returns_a_404() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let update = UpdateRoaster {
|
||||||
|
name: Some("New Name".to_string()),
|
||||||
|
country: None,
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.put(app.api_url("/roasters/nonexistent-id"))
|
||||||
|
.json(&update)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_a_roaster_returns_a_204_for_valid_id() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "To Be Deleted".to_string(),
|
||||||
|
country: "UK".to_string(),
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roaster");
|
||||||
|
|
||||||
|
let created_roaster: Roaster = create_response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.delete(app.api_url(&format!("/roasters/{}", created_roaster.id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 204);
|
||||||
|
|
||||||
|
// Verify roaster was deleted
|
||||||
|
let get_response = client
|
||||||
|
.get(app.api_url(&format!("/roasters/{}", created_roaster.id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
assert_eq!(get_response.status(), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_a_nonexistent_roaster_returns_a_404() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.delete(app.api_url("/roasters/nonexistent-id"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 404);
|
||||||
|
}
|
||||||
413
tests/server/roasts.rs
Normal file
413
tests/server/roasts.rs
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
use crate::server::helpers::spawn_app;
|
||||||
|
use brewlog::domain::roasters::NewRoaster;
|
||||||
|
use brewlog::domain::roasts::{NewRoast, Roast, RoastWithRoaster};
|
||||||
|
|
||||||
|
async fn create_test_roaster(app: &crate::server::helpers::TestApp) -> String {
|
||||||
|
let new_roaster = NewRoaster {
|
||||||
|
name: "Test Roasters".to_string(),
|
||||||
|
country: "UK".to_string(),
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roaster");
|
||||||
|
|
||||||
|
let roaster: brewlog::domain::roasters::Roaster = response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.expect("Failed to parse roaster");
|
||||||
|
|
||||||
|
roaster.id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creating_a_roast_returns_a_201_for_valid_data() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let roaster_id = create_test_roaster(&app).await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roast = NewRoast {
|
||||||
|
roaster_id: roaster_id.clone(),
|
||||||
|
name: "Ethiopian Yirgacheffe".to_string(),
|
||||||
|
origin: "Ethiopia".to_string(),
|
||||||
|
region: "Yirgacheffe".to_string(),
|
||||||
|
producer: "Local Cooperative".to_string(),
|
||||||
|
tasting_notes: vec![
|
||||||
|
"Blueberry".to_string(),
|
||||||
|
"Chocolate".to_string(),
|
||||||
|
"Citrus".to_string(),
|
||||||
|
],
|
||||||
|
process: "Washed".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&new_roast)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 201);
|
||||||
|
|
||||||
|
let roast: Roast = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roast.name, "Ethiopian Yirgacheffe");
|
||||||
|
assert_eq!(roast.roaster_id, roaster_id);
|
||||||
|
assert_eq!(roast.origin, Some("Ethiopia".to_string()));
|
||||||
|
assert_eq!(roast.region, Some("Yirgacheffe".to_string()));
|
||||||
|
assert_eq!(roast.producer, Some("Local Cooperative".to_string()));
|
||||||
|
assert_eq!(roast.tasting_notes.len(), 3);
|
||||||
|
assert_eq!(roast.process, Some("Washed".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creating_a_roast_persists_the_data() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let roaster_id = create_test_roaster(&app).await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roast = NewRoast {
|
||||||
|
roaster_id: roaster_id.clone(),
|
||||||
|
name: "Colombian Supremo".to_string(),
|
||||||
|
origin: "Colombia".to_string(),
|
||||||
|
region: "Huila".to_string(),
|
||||||
|
producer: "Farm Co-op".to_string(),
|
||||||
|
tasting_notes: vec!["Caramel".to_string(), "Nuts".to_string()],
|
||||||
|
process: "Natural".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&new_roast)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
let roast: Roast = response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
// Assert - Fetch the roast to verify it was persisted
|
||||||
|
let fetched_roast = app
|
||||||
|
.roast_repo
|
||||||
|
.get(roast.id.clone())
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch roast");
|
||||||
|
|
||||||
|
assert_eq!(fetched_roast.name, "Colombian Supremo");
|
||||||
|
assert_eq!(fetched_roast.roaster_id, roaster_id);
|
||||||
|
assert_eq!(fetched_roast.origin, Some("Colombia".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn creating_a_roast_with_nonexistent_roaster_returns_a_404() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roast = NewRoast {
|
||||||
|
roaster_id: "nonexistent-roaster-id".to_string(),
|
||||||
|
name: "Test Roast".to_string(),
|
||||||
|
origin: "Test".to_string(),
|
||||||
|
region: "Test".to_string(),
|
||||||
|
producer: "Test".to_string(),
|
||||||
|
tasting_notes: vec!["Test".to_string()],
|
||||||
|
process: "Test".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&new_roast)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn getting_a_roast_returns_a_200_for_valid_id() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let roaster_id = create_test_roaster(&app).await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roast = NewRoast {
|
||||||
|
roaster_id: roaster_id.clone(),
|
||||||
|
name: "Kenyan AA".to_string(),
|
||||||
|
origin: "Kenya".to_string(),
|
||||||
|
region: "Nyeri".to_string(),
|
||||||
|
producer: "Estate".to_string(),
|
||||||
|
tasting_notes: vec!["Blackcurrant".to_string()],
|
||||||
|
process: "Washed".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_response = client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&new_roast)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roast");
|
||||||
|
|
||||||
|
let created_roast: Roast = create_response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url(&format!("/roasts/{}", created_roast.id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roast: Roast = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roast.id, created_roast.id);
|
||||||
|
assert_eq!(roast.name, "Kenyan AA");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn getting_a_nonexistent_roast_returns_a_404() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url("/roasts/nonexistent-id"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn listing_roasts_returns_a_200_with_empty_list() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url("/roasts"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roasts: Vec<RoastWithRoaster> = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roasts.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn listing_roasts_returns_a_200_with_multiple_roasts() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let roaster_id = create_test_roaster(&app).await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Create multiple roasts
|
||||||
|
let roast1 = NewRoast {
|
||||||
|
roaster_id: roaster_id.clone(),
|
||||||
|
name: "First Roast".to_string(),
|
||||||
|
origin: "Brazil".to_string(),
|
||||||
|
region: "Santos".to_string(),
|
||||||
|
producer: "Farm A".to_string(),
|
||||||
|
tasting_notes: vec!["Chocolate".to_string()],
|
||||||
|
process: "Natural".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let roast2 = NewRoast {
|
||||||
|
roaster_id: roaster_id.clone(),
|
||||||
|
name: "Second Roast".to_string(),
|
||||||
|
origin: "Guatemala".to_string(),
|
||||||
|
region: "Antigua".to_string(),
|
||||||
|
producer: "Farm B".to_string(),
|
||||||
|
tasting_notes: vec!["Caramel".to_string()],
|
||||||
|
process: "Washed".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&roast1)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create first roast");
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&roast2)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create second roast");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url("/roasts"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roasts: Vec<RoastWithRoaster> = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roasts.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn listing_roasts_by_roaster_returns_a_200_with_filtered_list() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let roaster1_id = create_test_roaster(&app).await;
|
||||||
|
|
||||||
|
// Create a second roaster
|
||||||
|
let new_roaster2 = NewRoaster {
|
||||||
|
name: "Second Roasters".to_string(),
|
||||||
|
country: "USA".to_string(),
|
||||||
|
city: None,
|
||||||
|
homepage: None,
|
||||||
|
notes: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let response = client
|
||||||
|
.post(app.api_url("/roasters"))
|
||||||
|
.json(&new_roaster2)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create second roaster");
|
||||||
|
|
||||||
|
let roaster2: brewlog::domain::roasters::Roaster = response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.expect("Failed to parse roaster");
|
||||||
|
let roaster2_id = roaster2.id;
|
||||||
|
|
||||||
|
// Create roasts for both roasters
|
||||||
|
let roast1 = NewRoast {
|
||||||
|
roaster_id: roaster1_id.clone(),
|
||||||
|
name: "Roaster 1 Roast".to_string(),
|
||||||
|
origin: "Brazil".to_string(),
|
||||||
|
region: "Santos".to_string(),
|
||||||
|
producer: "Farm A".to_string(),
|
||||||
|
tasting_notes: vec!["Chocolate".to_string()],
|
||||||
|
process: "Natural".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let roast2 = NewRoast {
|
||||||
|
roaster_id: roaster2_id.clone(),
|
||||||
|
name: "Roaster 2 Roast".to_string(),
|
||||||
|
origin: "Guatemala".to_string(),
|
||||||
|
region: "Antigua".to_string(),
|
||||||
|
producer: "Farm B".to_string(),
|
||||||
|
tasting_notes: vec!["Caramel".to_string()],
|
||||||
|
process: "Washed".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&roast1)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create first roast");
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&roast2)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create second roast");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.get(app.api_url(&format!("/roasts?roaster_id={}", roaster1_id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 200);
|
||||||
|
|
||||||
|
let roasts: Vec<RoastWithRoaster> = response.json().await.expect("Failed to parse response");
|
||||||
|
assert_eq!(roasts.len(), 1);
|
||||||
|
assert_eq!(roasts[0].roast.roaster_id, roaster1_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_a_roast_returns_a_204_for_valid_id() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let roaster_id = create_test_roaster(&app).await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let new_roast = NewRoast {
|
||||||
|
roaster_id: roaster_id.clone(),
|
||||||
|
name: "To Be Deleted".to_string(),
|
||||||
|
origin: "Test".to_string(),
|
||||||
|
region: "Test".to_string(),
|
||||||
|
producer: "Test".to_string(),
|
||||||
|
tasting_notes: vec!["Test".to_string()],
|
||||||
|
process: "Test".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_response = client
|
||||||
|
.post(app.api_url("/roasts"))
|
||||||
|
.json(&new_roast)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create roast");
|
||||||
|
|
||||||
|
let created_roast: Roast = create_response.json().await.expect("Failed to parse response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.delete(app.api_url(&format!("/roasts/{}", created_roast.id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 204);
|
||||||
|
|
||||||
|
// Verify roast was deleted
|
||||||
|
let get_response = client
|
||||||
|
.get(app.api_url(&format!("/roasts/{}", created_roast.id)))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
assert_eq!(get_response.status(), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_a_nonexistent_roast_returns_a_404() {
|
||||||
|
// Arrange
|
||||||
|
let app = spawn_app().await;
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let response = client
|
||||||
|
.delete(app.api_url("/roasts/nonexistent-id"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to execute request");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(response.status(), 404);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue