chore(api): apply rustfmt across modules and tests
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
6c1357ec4d
commit
a4ef6ca49e
@@ -146,7 +146,11 @@ async fn summary_is_owned_and_entries_are_append_only() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), status, "{method} /api/me/stats must not mutate");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
status,
|
||||
"{method} /api/me/stats must not mutate"
|
||||
);
|
||||
}
|
||||
let res = client()
|
||||
.get(app.url("/api/me/stats/entries"))
|
||||
@@ -154,7 +158,11 @@ async fn summary_is_owned_and_entries_are_append_only() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), 404, "the public ledger listing stays out of scope");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
404,
|
||||
"the public ledger listing stays out of scope"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -165,14 +165,8 @@ async fn unauthenticated_requests_are_rejected() {
|
||||
for (method, path) in [
|
||||
("GET", "/api/addresses".to_string()),
|
||||
("POST", "/api/addresses".to_string()),
|
||||
(
|
||||
"PUT",
|
||||
format!("/api/addresses/{}", uuid::Uuid::new_v4()),
|
||||
),
|
||||
(
|
||||
"DELETE",
|
||||
format!("/api/addresses/{}", uuid::Uuid::new_v4()),
|
||||
),
|
||||
("PUT", format!("/api/addresses/{}", uuid::Uuid::new_v4())),
|
||||
("DELETE", format!("/api/addresses/{}", uuid::Uuid::new_v4())),
|
||||
] {
|
||||
let res = client()
|
||||
.request(method.parse().unwrap(), app.url(&path))
|
||||
|
||||
+37
-11
@@ -1,9 +1,9 @@
|
||||
mod common;
|
||||
|
||||
use common::{
|
||||
add_to_cart, category_id_by_slug, checkout, client, create_product_full, create_product_with_sku,
|
||||
create_product_with_sku_in_category, create_shop, login_admin, make_shop_owner, pay,
|
||||
publish_product, register_customer, spawn_app,
|
||||
add_to_cart, category_id_by_slug, checkout, client, create_product_full,
|
||||
create_product_with_sku, create_product_with_sku_in_category, create_shop, login_admin,
|
||||
make_shop_owner, pay, publish_product, register_customer, spawn_app,
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
@@ -61,7 +61,10 @@ async fn publish_lifecycle_and_public_visibility() {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
assert_eq!(res.json::<serde_json::Value>().await.unwrap()["status"], "published");
|
||||
assert_eq!(
|
||||
res.json::<serde_json::Value>().await.unwrap()["status"],
|
||||
"published"
|
||||
);
|
||||
|
||||
let res = client().get(app.url("/api/products")).send().await.unwrap();
|
||||
let list: serde_json::Value = res.json().await.unwrap();
|
||||
@@ -154,7 +157,10 @@ async fn currency_conversion_math() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.json::<serde_json::Value>().await.unwrap()["amount_minor"], 2999);
|
||||
assert_eq!(
|
||||
res.json::<serde_json::Value>().await.unwrap()["amount_minor"],
|
||||
2999
|
||||
);
|
||||
|
||||
// disabled currency rejected
|
||||
let admin = login_admin(&app).await;
|
||||
@@ -226,10 +232,22 @@ async fn category_subtree_listing_and_price_sort() {
|
||||
assert_eq!(res.status(), 200);
|
||||
let body: serde_json::Value = res.json().await.unwrap();
|
||||
let listed = listed_ids(&body);
|
||||
assert!(listed.contains(&leaf), "grandchild product missing from root listing");
|
||||
assert!(listed.contains(&sibling), "child product missing from root listing");
|
||||
assert!(!listed.contains(&unrelated), "product from another root category leaked in");
|
||||
assert_eq!(body["total"], 2, "total must count the subtree, not only the root");
|
||||
assert!(
|
||||
listed.contains(&leaf),
|
||||
"grandchild product missing from root listing"
|
||||
);
|
||||
assert!(
|
||||
listed.contains(&sibling),
|
||||
"child product missing from root listing"
|
||||
);
|
||||
assert!(
|
||||
!listed.contains(&unrelated),
|
||||
"product from another root category leaked in"
|
||||
);
|
||||
assert_eq!(
|
||||
body["total"], 2,
|
||||
"total must count the subtree, not only the root"
|
||||
);
|
||||
|
||||
// A mid-level category covers its own subtree and nothing else.
|
||||
let res = client()
|
||||
@@ -355,7 +373,11 @@ async fn brand_filter_and_real_sales() {
|
||||
.await
|
||||
.unwrap();
|
||||
let body: serde_json::Value = res.json().await.unwrap();
|
||||
assert_eq!(sold(&body, &p_alpha), 0, "pending payment must not count as sold");
|
||||
assert_eq!(
|
||||
sold(&body, &p_alpha),
|
||||
0,
|
||||
"pending payment must not count as sold"
|
||||
);
|
||||
assert_eq!(sold(&body, &p_beta), 0);
|
||||
|
||||
// Paying makes the units count, and the sales sort follows them.
|
||||
@@ -378,7 +400,11 @@ async fn brand_filter_and_real_sales() {
|
||||
.await
|
||||
.unwrap();
|
||||
let body: serde_json::Value = res.json().await.unwrap();
|
||||
assert_eq!(ids(&body)[0], p_alpha, "sales sort puts the sold product first");
|
||||
assert_eq!(
|
||||
ids(&body)[0],
|
||||
p_alpha,
|
||||
"sales sort puts the sold product first"
|
||||
);
|
||||
|
||||
// Comments still have no model, so that sort stays refused.
|
||||
let res = client()
|
||||
|
||||
@@ -168,7 +168,16 @@ pub async fn create_product_with_sku_in_category(
|
||||
stock: i32,
|
||||
category_id: Option<&str>,
|
||||
) -> (String, String) {
|
||||
create_product_full(app, owner_token, slug, price_minor, stock, category_id, None).await
|
||||
create_product_full(
|
||||
app,
|
||||
owner_token,
|
||||
slug,
|
||||
price_minor,
|
||||
stock,
|
||||
category_id,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Same, with a category and a brand so both filters can be exercised.
|
||||
|
||||
@@ -28,7 +28,11 @@ async fn public_content(app: &common::TestApp) -> serde_json::Value {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), 200, "public content read must be unauthenticated");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
200,
|
||||
"public content read must be unauthenticated"
|
||||
);
|
||||
res.json().await.unwrap()
|
||||
}
|
||||
|
||||
@@ -59,7 +63,10 @@ async fn home_content_is_public_and_ordered() {
|
||||
.collect();
|
||||
let mut sorted = positions.clone();
|
||||
sorted.sort_unstable();
|
||||
assert_eq!(positions, sorted, "content must come back in position order");
|
||||
assert_eq!(
|
||||
positions, sorted,
|
||||
"content must come back in position order"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -83,14 +90,20 @@ async fn admin_replace_round_trips_and_reorders() {
|
||||
.map(|b| b["image"].as_str().unwrap().to_string())
|
||||
.collect()
|
||||
};
|
||||
assert_eq!(images(&public_content(&app).await), vec!["/mock/a.svg", "/mock/b.svg"]);
|
||||
assert_eq!(
|
||||
images(&public_content(&app).await),
|
||||
vec!["/mock/a.svg", "/mock/b.svg"]
|
||||
);
|
||||
|
||||
// The submitted order decides the stored order and the positions.
|
||||
let flipped = serde_json::json!([
|
||||
{"image": "/mock/b.svg", "url": "/collective"},
|
||||
{"image": "/mock/a.svg", "url": "/seckill"}
|
||||
]);
|
||||
assert_eq!(replace(&app, &admin, "banners", flipped).await.status(), 200);
|
||||
assert_eq!(
|
||||
replace(&app, &admin, "banners", flipped).await.status(),
|
||||
200
|
||||
);
|
||||
let content = public_content(&app).await;
|
||||
assert_eq!(images(&content), vec!["/mock/b.svg", "/mock/a.svg"]);
|
||||
assert_eq!(content["banners"][0]["position"], 0);
|
||||
@@ -116,7 +129,11 @@ async fn inactive_rows_are_hidden_from_the_public_read() {
|
||||
.iter()
|
||||
.map(|b| b["image"].as_str().unwrap())
|
||||
.collect();
|
||||
assert_eq!(visible, vec!["/mock/on.svg"], "inactive rows must not be public");
|
||||
assert_eq!(
|
||||
visible,
|
||||
vec!["/mock/on.svg"],
|
||||
"inactive rows must not be public"
|
||||
);
|
||||
|
||||
// The admin read keeps it, so a disabled block stays editable.
|
||||
let res = client()
|
||||
@@ -153,7 +170,11 @@ async fn invalid_entry_is_rejected_without_touching_stored_content() {
|
||||
.iter()
|
||||
.map(|b| b["image"].as_str().unwrap())
|
||||
.collect();
|
||||
assert_eq!(images, vec!["/mock/keep.svg"], "a rejected list must change nothing");
|
||||
assert_eq!(
|
||||
images,
|
||||
vec!["/mock/keep.svg"],
|
||||
"a rejected list must change nothing"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -335,12 +335,7 @@ async fn cancelling_a_pending_order_restores_its_coupon() {
|
||||
let shop_id = coupon["shop_id"].as_str().unwrap().to_string();
|
||||
|
||||
add_to_cart(&app, &token, &sku, 2).await;
|
||||
let res = checkout_with(
|
||||
&app,
|
||||
&token,
|
||||
HashMap::from([(shop_id, coupon_id.clone())]),
|
||||
)
|
||||
.await;
|
||||
let res = checkout_with(&app, &token, HashMap::from([(shop_id, coupon_id.clone())])).await;
|
||||
assert_eq!(res.status(), 201);
|
||||
let orders: Vec<serde_json::Value> = res.json().await.unwrap();
|
||||
let order_id = orders[0]["id"].as_str().unwrap().to_string();
|
||||
|
||||
@@ -173,7 +173,10 @@ async fn inactive_window_falls_back_to_normal_price() {
|
||||
let item = &orders[0]["items"][0];
|
||||
assert_eq!(item["unit_price_minor"], 1000, "normal price applies");
|
||||
assert!(item["flash_sale_item_id"].is_null());
|
||||
assert_eq!(activity_lines(&app, orders[0]["id"].as_str().unwrap()).await, 0);
|
||||
assert_eq!(
|
||||
activity_lines(&app, orders[0]["id"].as_str().unwrap()).await,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -182,7 +185,8 @@ async fn cross_shop_sku_and_overlapping_sessions_are_rejected() {
|
||||
let app = spawn_app().await;
|
||||
let admin = login_admin(&app).await;
|
||||
let (owner_a, shop_a, _product_a, sku_a) = setup_sellable(&app, &admin, "fs-a", 1000, 5).await;
|
||||
let (_owner_b, _shop_b, _product_b, sku_b) = setup_sellable(&app, &admin, "fs-b", 1000, 5).await;
|
||||
let (_owner_b, _shop_b, _product_b, sku_b) =
|
||||
setup_sellable(&app, &admin, "fs-b", 1000, 5).await;
|
||||
let (starts, ends) = active_window();
|
||||
|
||||
let session = create_session(&app, &owner_a, &starts, &ends).await;
|
||||
@@ -195,7 +199,11 @@ async fn cross_shop_sku_and_overlapping_sessions_are_rejected() {
|
||||
// A second overlapping session cannot list the same SKU.
|
||||
let session_two = create_session(&app, &owner_a, &starts, &ends).await;
|
||||
let res = add_item(&app, &owner_a, &session_two, &sku_a, 400, 5, 3).await;
|
||||
assert_eq!(res.status(), 409, "overlapping sale for the same SKU is refused");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
409,
|
||||
"overlapping sale for the same SKU is refused"
|
||||
);
|
||||
let _ = shop_a;
|
||||
}
|
||||
|
||||
@@ -263,13 +271,12 @@ async fn reserved_stock_admits_one_activity_price() {
|
||||
assert_eq!(reserved, 0, "the single reserved unit is consumed");
|
||||
assert_eq!(sold, 1);
|
||||
|
||||
let activity: i64 = sqlx::query_scalar(
|
||||
"SELECT count(*) FROM order_items WHERE flash_sale_item_id = $1",
|
||||
)
|
||||
.bind(Uuid::parse_str(&item_id).unwrap())
|
||||
.fetch_one(&app.db)
|
||||
.await
|
||||
.unwrap();
|
||||
let activity: i64 =
|
||||
sqlx::query_scalar("SELECT count(*) FROM order_items WHERE flash_sale_item_id = $1")
|
||||
.bind(Uuid::parse_str(&item_id).unwrap())
|
||||
.fetch_one(&app.db)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(activity, 1, "exactly one line got the activity price");
|
||||
}
|
||||
|
||||
@@ -304,7 +311,10 @@ async fn per_customer_limit_splits_the_line() {
|
||||
.filter(|i| i["flash_sale_item_id"].is_null())
|
||||
.collect();
|
||||
assert_eq!(activity.len(), 1);
|
||||
assert_eq!(activity[0]["qty"], 1, "only the allowance gets the activity price");
|
||||
assert_eq!(
|
||||
activity[0]["qty"], 1,
|
||||
"only the allowance gets the activity price"
|
||||
);
|
||||
assert_eq!(activity[0]["unit_price_minor"], 400);
|
||||
assert_eq!(standard.len(), 1);
|
||||
assert_eq!(standard[0]["qty"], 2);
|
||||
@@ -316,7 +326,10 @@ async fn per_customer_limit_splits_the_line() {
|
||||
let res = checkout_with(&app, &token, HashMap::new()).await;
|
||||
let orders: Vec<serde_json::Value> = res.json().await.unwrap();
|
||||
assert!(orders[0]["items"][0]["flash_sale_item_id"].is_null());
|
||||
assert_eq!(activity_lines(&app, orders[0]["id"].as_str().unwrap()).await, 0);
|
||||
assert_eq!(
|
||||
activity_lines(&app, orders[0]["id"].as_str().unwrap()).await,
|
||||
0
|
||||
);
|
||||
|
||||
let (_, sold) = flash_item_state(&app, &item_id).await;
|
||||
assert_eq!(sold, 1);
|
||||
@@ -330,7 +343,12 @@ async fn coupon_is_rejected_on_a_flash_priced_shop_order() {
|
||||
let (owner, shop, _product, sku) = setup_sellable(&app, &admin, "fs-cp", 1000, 5).await;
|
||||
let (starts, ends) = active_window();
|
||||
let session = create_session(&app, &owner, &starts, &ends).await;
|
||||
assert_eq!(add_item(&app, &owner, &session, &sku, 400, 5, 5).await.status(), 201);
|
||||
assert_eq!(
|
||||
add_item(&app, &owner, &session, &sku, 400, 5, 5)
|
||||
.await
|
||||
.status(),
|
||||
201
|
||||
);
|
||||
|
||||
let template = create_template(&app, &owner, 100, 0).await;
|
||||
let (token, _) = register_customer(&app, "fs-cp").await;
|
||||
@@ -338,7 +356,11 @@ async fn coupon_is_rejected_on_a_flash_priced_shop_order() {
|
||||
|
||||
add_to_cart(&app, &token, &sku, 1).await;
|
||||
let res = checkout_with(&app, &token, HashMap::from([(shop, coupon)])).await;
|
||||
assert_eq!(res.status(), 409, "activity pricing and coupons are exclusive");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
409,
|
||||
"activity pricing and coupons are exclusive"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -375,7 +397,10 @@ async fn cancel_restores_reserved_stock_and_coupon() {
|
||||
|
||||
for order in &orders {
|
||||
let res = client()
|
||||
.post(app.url(&format!("/api/orders/{}/cancel", order["id"].as_str().unwrap())))
|
||||
.post(app.url(&format!(
|
||||
"/api/orders/{}/cancel",
|
||||
order["id"].as_str().unwrap()
|
||||
)))
|
||||
.bearer_auth(&token)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -165,7 +165,8 @@ async fn activity_requires_an_own_sku_and_two_members() {
|
||||
let app = spawn_app().await;
|
||||
let admin = login_admin(&app).await;
|
||||
let (owner_a, _shop_a, _p, sku_a) = setup_sellable(&app, &admin, "gb-valid-a", 1000, 10).await;
|
||||
let (_owner_b, _shop_b, _p2, sku_b) = setup_sellable(&app, &admin, "gb-valid-b", 1000, 10).await;
|
||||
let (_owner_b, _shop_b, _p2, sku_b) =
|
||||
setup_sellable(&app, &admin, "gb-valid-b", 1000, 10).await;
|
||||
|
||||
// Another shop's SKU is refused.
|
||||
let res = create_activity_body(&app, &owner_a, &sku_b, 700, 2, 24).await;
|
||||
@@ -225,7 +226,10 @@ async fn open_then_join_completes_a_group() {
|
||||
.json()
|
||||
.await
|
||||
.unwrap();
|
||||
let found = active.iter().find(|a| a["id"] == activity.as_str()).unwrap();
|
||||
let found = active
|
||||
.iter()
|
||||
.find(|a| a["id"] == activity.as_str())
|
||||
.unwrap();
|
||||
assert_eq!(found["open_groups"][0]["id"], group_id.as_str());
|
||||
assert_eq!(found["open_groups"][0]["paid_member_count"], 0);
|
||||
|
||||
@@ -317,11 +321,13 @@ async fn expired_and_cancelled_groups_are_not_joinable() {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(pay(&app, &token_a, &order_a).await, 200);
|
||||
sqlx::query("UPDATE collective_groups SET expires_at = now() - interval '1 hour' WHERE id = $1")
|
||||
.bind(group_id)
|
||||
.execute(&app.db)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"UPDATE collective_groups SET expires_at = now() - interval '1 hour' WHERE id = $1",
|
||||
)
|
||||
.bind(group_id)
|
||||
.execute(&app.db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// A committed read records the expiry (a failed checkout rolls its own
|
||||
// sweep back, so discovery is what persists it).
|
||||
@@ -473,7 +479,8 @@ async fn activity_rejects_a_sku_with_an_overlapping_flash_sale() {
|
||||
async fn cancelling_an_elapsed_empty_group_records_expired() {
|
||||
let app = spawn_app().await;
|
||||
let admin = login_admin(&app).await;
|
||||
let (owner, _shop, _product, sku) = setup_sellable(&app, &admin, "gb-precedence", 1000, 10).await;
|
||||
let (owner, _shop, _product, sku) =
|
||||
setup_sellable(&app, &admin, "gb-precedence", 1000, 10).await;
|
||||
let activity = create_activity(&app, &owner, &sku, 700, 3, 24).await;
|
||||
|
||||
let (token, _) = register_customer(&app, "gb-precedence").await;
|
||||
@@ -486,11 +493,13 @@ async fn cancelling_an_elapsed_empty_group_records_expired() {
|
||||
.unwrap();
|
||||
|
||||
// The lifetime ends before the opener cancels.
|
||||
sqlx::query("UPDATE collective_groups SET expires_at = now() - interval '1 hour' WHERE id = $1")
|
||||
.bind(group_id)
|
||||
.execute(&app.db)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"UPDATE collective_groups SET expires_at = now() - interval '1 hour' WHERE id = $1",
|
||||
)
|
||||
.bind(group_id)
|
||||
.execute(&app.db)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cancel(&app, &token, &order).await, 200);
|
||||
|
||||
// Both conditions hold; an elapsed group is expired, not relabelled cancelled.
|
||||
|
||||
@@ -37,14 +37,13 @@ async fn register_user(state: &AppState, label: &str) -> Uuid {
|
||||
|
||||
async fn sellable_sku(state: &AppState, slug: &str, price_minor: i64, stock: i32) -> Uuid {
|
||||
let slug = format!("{slug}-{}", &Uuid::new_v4().simple().to_string()[..8]);
|
||||
let shop_id: Uuid = sqlx::query_scalar(
|
||||
"INSERT INTO shops (name, slug) VALUES ($1, $2) RETURNING id",
|
||||
)
|
||||
.bind(serde_json::json!({"en": slug, "zh": slug}))
|
||||
.bind(&slug)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.unwrap();
|
||||
let shop_id: Uuid =
|
||||
sqlx::query_scalar("INSERT INTO shops (name, slug) VALUES ($1, $2) RETURNING id")
|
||||
.bind(serde_json::json!({"en": slug, "zh": slug}))
|
||||
.bind(&slug)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.unwrap();
|
||||
let product_id: Uuid = sqlx::query_scalar(
|
||||
"INSERT INTO products (shop_id, slug, name, status)
|
||||
VALUES ($1, $2, $3, 'published') RETURNING id",
|
||||
@@ -73,9 +72,16 @@ async fn sellable_sku(state: &AppState, slug: &str, price_minor: i64, stock: i32
|
||||
async fn checkout_rejects_empty_cart() {
|
||||
let state = common::spawn_state().await;
|
||||
let user_id = register_user(&state, "empty-cart").await;
|
||||
let err = order::checkout(&state, user_id, address(), "USD".into(), Default::default(), None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let err = order::checkout(
|
||||
&state,
|
||||
user_id,
|
||||
address(),
|
||||
"USD".into(),
|
||||
Default::default(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, ApiError::BadRequest(m) if m.contains("empty")));
|
||||
}
|
||||
|
||||
@@ -107,9 +113,16 @@ async fn checkout_rejects_insufficient_stock() {
|
||||
cart::service::add_item(&state, user_id, sku_id, 2)
|
||||
.await
|
||||
.unwrap();
|
||||
let err = order::checkout(&state, user_id, address(), "USD".into(), Default::default(), None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let err = order::checkout(
|
||||
&state,
|
||||
user_id,
|
||||
address(),
|
||||
"USD".into(),
|
||||
Default::default(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, ApiError::Conflict(m) if m.contains("insufficient stock")));
|
||||
}
|
||||
|
||||
@@ -126,9 +139,16 @@ async fn checkout_splits_per_shop() {
|
||||
cart::service::add_item(&state, user_id, sku_b, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
let orders = order::checkout(&state, user_id, address(), "USD".into(), Default::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let orders = order::checkout(
|
||||
&state,
|
||||
user_id,
|
||||
address(),
|
||||
"USD".into(),
|
||||
Default::default(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(orders.len(), 2);
|
||||
let totals: Vec<i64> = orders.iter().map(|o| o.order.total_minor).collect();
|
||||
assert!(totals.contains(&2000) && totals.contains(&2000));
|
||||
@@ -143,9 +163,16 @@ async fn pay_and_cancel_require_pending_payment() {
|
||||
cart::service::add_item(&state, user_id, sku_id, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
let orders = order::checkout(&state, user_id, address(), "USD".into(), Default::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let orders = order::checkout(
|
||||
&state,
|
||||
user_id,
|
||||
address(),
|
||||
"USD".into(),
|
||||
Default::default(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let id = orders[0].order.id;
|
||||
order::service::pay(&state, user_id, id).await.unwrap();
|
||||
let err = order::service::pay(&state, user_id, id).await.unwrap_err();
|
||||
|
||||
@@ -45,10 +45,15 @@ async fn checkout_splits_orders_per_shop_and_clears_cart() {
|
||||
assert!(shop_ids.contains(&shop_a) && shop_ids.contains(&shop_b));
|
||||
for item in items {
|
||||
assert!(
|
||||
item["shop_name"]["en"].as_str().is_some_and(|s| !s.is_empty()),
|
||||
item["shop_name"]["en"]
|
||||
.as_str()
|
||||
.is_some_and(|s| !s.is_empty()),
|
||||
"line must carry a bilingual shop name"
|
||||
);
|
||||
assert_eq!(item["stock"], 5, "stock is the SKU's, before checkout decrements it");
|
||||
assert_eq!(
|
||||
item["stock"], 5,
|
||||
"stock is the SKU's, before checkout decrements it"
|
||||
);
|
||||
}
|
||||
|
||||
let orders = checkout(&app, &buyer).await;
|
||||
@@ -71,10 +76,13 @@ async fn checkout_splits_orders_per_shop_and_clears_cart() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.json::<serde_json::Value>().await.unwrap()["items"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.len(), 0);
|
||||
assert_eq!(
|
||||
res.json::<serde_json::Value>().await.unwrap()["items"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -193,7 +201,10 @@ async fn fulfillment_flow_partial_then_complete() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.json::<serde_json::Value>().await.unwrap()["status"], "fulfilling");
|
||||
assert_eq!(
|
||||
res.json::<serde_json::Value>().await.unwrap()["status"],
|
||||
"fulfilling"
|
||||
);
|
||||
|
||||
// second shipment covers remainder → shipped after mark
|
||||
let res = client()
|
||||
@@ -222,7 +233,10 @@ async fn fulfillment_flow_partial_then_complete() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.json::<serde_json::Value>().await.unwrap()["status"], "shipped");
|
||||
assert_eq!(
|
||||
res.json::<serde_json::Value>().await.unwrap()["status"],
|
||||
"shipped"
|
||||
);
|
||||
|
||||
// confirm both deliveries → completed
|
||||
for sid in [&shipment1_id, &shipment2_id] {
|
||||
@@ -240,7 +254,10 @@ async fn fulfillment_flow_partial_then_complete() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.json::<serde_json::Value>().await.unwrap()["status"], "completed");
|
||||
assert_eq!(
|
||||
res.json::<serde_json::Value>().await.unwrap()["status"],
|
||||
"completed"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -58,12 +58,7 @@ async fn credit_points(state: &AppState, user_id: Uuid, amount: i64) {
|
||||
tx.commit().await.unwrap();
|
||||
}
|
||||
|
||||
async fn redeem(
|
||||
app: &TestApp,
|
||||
token: &str,
|
||||
product_id: &str,
|
||||
qty: i32,
|
||||
) -> reqwest::Response {
|
||||
async fn redeem(app: &TestApp, token: &str, product_id: &str, qty: i32) -> reqwest::Response {
|
||||
client()
|
||||
.post(app.url("/api/points/redemptions"))
|
||||
.bearer_auth(token)
|
||||
@@ -295,11 +290,7 @@ async fn fulfillment_transitions_are_validated() {
|
||||
let (token, user_id) = register_customer(&app, "pm-flow").await;
|
||||
credit_points(&app.state, uuid(&user_id), 2_000).await;
|
||||
|
||||
let first: serde_json::Value = redeem(&app, &token, &id, 1)
|
||||
.await
|
||||
.json()
|
||||
.await
|
||||
.unwrap();
|
||||
let first: serde_json::Value = redeem(&app, &token, &id, 1).await.json().await.unwrap();
|
||||
let first_id = first["id"].as_str().unwrap().to_string();
|
||||
|
||||
let res = client()
|
||||
@@ -329,11 +320,7 @@ async fn fulfillment_transitions_are_validated() {
|
||||
assert_eq!(res.status(), 409);
|
||||
|
||||
// A pending order can be cancelled once.
|
||||
let second: serde_json::Value = redeem(&app, &token, &id, 1)
|
||||
.await
|
||||
.json()
|
||||
.await
|
||||
.unwrap();
|
||||
let second: serde_json::Value = redeem(&app, &token, &id, 1).await.json().await.unwrap();
|
||||
let second_id = second["id"].as_str().unwrap().to_string();
|
||||
let res = client()
|
||||
.post(app.url(&format!("/api/admin/points/orders/{second_id}/cancel")))
|
||||
|
||||
+31
-12
@@ -13,11 +13,7 @@ async fn list_shops(app: &common::TestApp) -> serde_json::Value {
|
||||
}
|
||||
|
||||
fn find<'a>(shops: &'a serde_json::Value, id: &str) -> Option<&'a serde_json::Value> {
|
||||
shops
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|s| s["id"] == id)
|
||||
shops.as_array().unwrap().iter().find(|s| s["id"] == id)
|
||||
}
|
||||
|
||||
async fn set_profile(
|
||||
@@ -45,7 +41,10 @@ async fn shop_without_a_profile_is_still_listed() {
|
||||
let shops = list_shops(&app).await;
|
||||
let shop = find(&shops, &shop_id).expect("a shop with no profile must still be listed");
|
||||
assert!(shop["name"]["en"].is_string());
|
||||
assert!(shop["logo"].is_null(), "nothing may be invented for a missing profile");
|
||||
assert!(
|
||||
shop["logo"].is_null(),
|
||||
"nothing may be invented for a missing profile"
|
||||
);
|
||||
assert!(shop["score_rating"].is_null());
|
||||
}
|
||||
|
||||
@@ -97,7 +96,10 @@ async fn suspended_or_unknown_shops_are_not_public() {
|
||||
let shop_id = create_shop(&app, &admin, "shop-suspended").await;
|
||||
|
||||
let shops = list_shops(&app).await;
|
||||
let slug = find(&shops, &shop_id).unwrap()["slug"].as_str().unwrap().to_string();
|
||||
let slug = find(&shops, &shop_id).unwrap()["slug"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
client()
|
||||
.put(app.url(&format!("/api/admin/shops/{shop_id}/status")))
|
||||
@@ -107,7 +109,10 @@ async fn suspended_or_unknown_shops_are_not_public() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(find(&list_shops(&app).await, &shop_id).is_none(), "suspended shops are hidden");
|
||||
assert!(
|
||||
find(&list_shops(&app).await, &shop_id).is_none(),
|
||||
"suspended shops are hidden"
|
||||
);
|
||||
let res = client()
|
||||
.get(app.url(&format!("/api/shops/{slug}")))
|
||||
.send()
|
||||
@@ -120,7 +125,11 @@ async fn suspended_or_unknown_shops_are_not_public() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), 404, "an unknown slug is a 404, not an empty profile");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
404,
|
||||
"an unknown slug is a 404, not an empty profile"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -134,7 +143,10 @@ async fn incomplete_bilingual_text_is_refused_without_writing() {
|
||||
"company": "Kept Co.",
|
||||
"notice": {"en": "Original notice", "zh": "原始公告"}
|
||||
});
|
||||
assert_eq!(set_profile(&app, &admin, &shop_id, good).await.status(), 200);
|
||||
assert_eq!(
|
||||
set_profile(&app, &admin, &shop_id, good).await.status(),
|
||||
200
|
||||
);
|
||||
|
||||
let bad = serde_json::json!({
|
||||
"company": "Changed Co.",
|
||||
@@ -144,7 +156,10 @@ async fn incomplete_bilingual_text_is_refused_without_writing() {
|
||||
assert_eq!(res.status(), 400, "a label missing zh must be refused");
|
||||
|
||||
let shop = find(&list_shops(&app).await, &shop_id).unwrap().clone();
|
||||
assert_eq!(shop["company"], "Kept Co.", "the rejected write changed nothing");
|
||||
assert_eq!(
|
||||
shop["company"], "Kept Co.",
|
||||
"the rejected write changed nothing"
|
||||
);
|
||||
assert_eq!(shop["notice"]["zh"], "原始公告");
|
||||
}
|
||||
|
||||
@@ -160,6 +175,10 @@ async fn profile_writes_require_a_platform_admin() {
|
||||
let body = serde_json::json!({ "company": "Nope" });
|
||||
for token in [&owner, &customer] {
|
||||
let res = set_profile(&app, token, &shop_id, body.clone()).await;
|
||||
assert_eq!(res.status(), 403, "only platform admins may write a profile");
|
||||
assert_eq!(
|
||||
res.status(),
|
||||
403,
|
||||
"only platform admins may write a profile"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user