- Add roast detail page at /roasters/{slug}/roasts/{slug}
- Remove expand/collapse detail rows from all 7 list views
- List rows now navigate directly to entity detail pages
- Replace three-dots action button with chevron-right link
- Add delete buttons to brew and cup detail pages
- Restyle all delete buttons: outlined with red text
- Remove share buttons from all detail pages
- Update timeline card links to point at detail pages
- Make homepage activity cards clickable with hover effect
- Replace all vanilla JS delete/close with Datastar actions
- Extract render_redirect_script helper for Datastar redirects
- Update delete macro with referer-based routing for detail pages
187 lines
7.1 KiB
Rust
187 lines
7.1 KiB
Rust
/// Generates mechanical CRUD tests that are identical across entities.
|
|
/// Entity-specific tests remain hand-written in each file.
|
|
macro_rules! define_crud_tests {
|
|
(
|
|
entity: $entity:ident,
|
|
path: $path:expr,
|
|
list_type: $list_type:ty
|
|
$(, malformed_json: $malformed:expr)?
|
|
$(, missing_fields: $missing:expr)?
|
|
) => {
|
|
paste::paste! {
|
|
#[tokio::test]
|
|
async fn [<getting_a_nonexistent_ $entity _returns_a_404>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.get(app.api_url(&format!("{}/999999", $path)))
|
|
.send()
|
|
.await
|
|
.expect("Failed to execute request");
|
|
|
|
assert_eq!(response.status(), 404);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn [<deleting_a_nonexistent_ $entity _returns_a_404>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.delete(app.api_url(&format!("{}/999999", $path)))
|
|
.bearer_auth(app.auth_token.as_ref().unwrap())
|
|
.send()
|
|
.await
|
|
.expect("Failed to execute request");
|
|
|
|
assert_eq!(response.status(), 404);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn [<listing_ $entity s_returns_a_200_with_empty_list>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.get(app.api_url($path))
|
|
.send()
|
|
.await
|
|
.expect("Failed to execute request");
|
|
|
|
assert_eq!(response.status(), 200);
|
|
|
|
let items: Vec<$list_type> =
|
|
response.json().await.expect("Failed to parse response");
|
|
assert_eq!(items.len(), 0);
|
|
}
|
|
|
|
$(
|
|
#[tokio::test]
|
|
async fn [<creating_a_ $entity _with_malformed_json_returns_a_400>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.post(app.api_url($path))
|
|
.bearer_auth(app.auth_token.as_ref().unwrap())
|
|
.header("content-type", "application/json")
|
|
.body($malformed)
|
|
.send()
|
|
.await
|
|
.expect("Failed to execute request");
|
|
|
|
assert_eq!(response.status(), 400);
|
|
}
|
|
)?
|
|
|
|
$(
|
|
#[tokio::test]
|
|
async fn [<creating_a_ $entity _with_missing_required_fields_returns_a_400>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.post(app.api_url($path))
|
|
.bearer_auth(app.auth_token.as_ref().unwrap())
|
|
.header("content-type", "application/json")
|
|
.body($missing)
|
|
.send()
|
|
.await
|
|
.expect("Failed to execute request");
|
|
|
|
assert_eq!(response.status(), 400);
|
|
}
|
|
)?
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) use define_crud_tests;
|
|
|
|
/// Generates list (with/without datastar header) and delete (with datastar header)
|
|
/// tests for a given entity. The setup function creates an entity and returns its
|
|
/// ID as a String (used for the delete test; ignored for list tests).
|
|
macro_rules! define_datastar_entity_tests {
|
|
(
|
|
entity: $entity:ident,
|
|
type_param: $type_param:expr,
|
|
api_path: $api_path:expr,
|
|
list_element: $list_element:expr,
|
|
selector: $selector:expr,
|
|
setup: $setup:expr
|
|
) => {
|
|
paste::paste! {
|
|
#[tokio::test]
|
|
async fn [<$entity _list_with_datastar_header_returns_fragment>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
$setup(&app).await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.get(format!("{}/data?type={}", app.address, $type_param))
|
|
.header("datastar-request", "true")
|
|
.send()
|
|
.await
|
|
.expect(concat!("failed to fetch ", stringify!($entity)));
|
|
|
|
assert_eq!(response.status(), 200);
|
|
crate::helpers::assert_datastar_headers_with_mode(
|
|
&response,
|
|
"#data-content",
|
|
"inner",
|
|
);
|
|
|
|
let body = response.text().await.expect("failed to read body");
|
|
crate::helpers::assert_html_fragment(&body);
|
|
assert!(
|
|
body.contains($list_element),
|
|
"Fragment should contain the selector element"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn [<$entity _list_without_datastar_header_returns_full_page>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
$setup(&app).await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.get(format!("{}/data?type={}", app.address, $type_param))
|
|
.send()
|
|
.await
|
|
.expect(concat!("failed to fetch ", stringify!($entity)));
|
|
|
|
assert_eq!(response.status(), 200);
|
|
assert!(response.headers().get("datastar-selector").is_none());
|
|
|
|
let body = response.text().await.expect("failed to read body");
|
|
crate::helpers::assert_full_page(&body);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn [<$entity _delete_with_datastar_header_returns_fragment>]() {
|
|
let app = crate::helpers::spawn_app_with_auth().await;
|
|
let entity_id = $setup(&app).await;
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client
|
|
.delete(app.api_url(&format!("{}/{}", $api_path, entity_id)))
|
|
.bearer_auth(app.auth_token.as_ref().unwrap())
|
|
.header("datastar-request", "true")
|
|
.header("referer", format!("{}/data?type={}", app.address, $type_param))
|
|
.send()
|
|
.await
|
|
.expect(concat!("failed to delete ", stringify!($entity)));
|
|
|
|
assert_eq!(response.status(), 200);
|
|
crate::helpers::assert_datastar_headers(&response, $selector);
|
|
|
|
let body = response.text().await.expect("failed to read body");
|
|
crate::helpers::assert_html_fragment(&body);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) use define_datastar_entity_tests;
|