mod common; use common::{client, register_customer, spawn_app}; use serial_test::serial; fn addr_body(recipient: &str, city: &str, is_default: bool) -> serde_json::Value { serde_json::json!({ "recipient": recipient, "phone": "+1 555 0100", "country": "US", "region": "California", "city": city, "line1": "1 Infinite Loop", "postal_code": "95014", "is_default": is_default, }) } async fn create(app: &common::TestApp, token: &str, body: serde_json::Value) -> serde_json::Value { let res = client() .post(app.url("/api/addresses")) .bearer_auth(token) .json(&body) .send() .await .unwrap(); assert_eq!(res.status(), 201, "create failed: {:?}", res.text().await); res.json().await.unwrap() } async fn list(app: &common::TestApp, token: &str) -> Vec { let res = client() .get(app.url("/api/addresses")) .bearer_auth(token) .send() .await .unwrap(); assert_eq!(res.status(), 200); res.json().await.unwrap() } #[tokio::test] #[serial] async fn address_crud_roundtrip() { let app = spawn_app().await; let (token, _user_id) = register_customer(&app, "addr-crud").await; let created = create(&app, &token, addr_body("Alice", "Cupertino", false)).await; let id = created["id"].as_str().unwrap().to_string(); assert_eq!(created["city"], "Cupertino"); // The first address of an account is always the default. assert_eq!(created["is_default"], true); let rows = list(&app, &token).await; assert_eq!(rows.len(), 1); assert_eq!(rows[0]["id"], id); let res = client() .put(app.url(&format!("/api/addresses/{id}"))) .bearer_auth(&token) .json(&addr_body("Alice B", "Sunnyvale", false)) .send() .await .unwrap(); assert_eq!(res.status(), 200); let updated: serde_json::Value = res.json().await.unwrap(); assert_eq!(updated["city"], "Sunnyvale"); assert_eq!(updated["recipient"], "Alice B"); let res = client() .delete(app.url(&format!("/api/addresses/{id}"))) .bearer_auth(&token) .send() .await .unwrap(); assert_eq!(res.status(), 200); let remaining: Vec = res.json().await.unwrap(); assert!(remaining.is_empty()); } #[tokio::test] #[serial] async fn single_default_invariant() { let app = spawn_app().await; let (token, _user_id) = register_customer(&app, "addr-default").await; let first = create(&app, &token, addr_body("A", "Cupertino", true)).await; let first_id = first["id"].as_str().unwrap().to_string(); let second = create(&app, &token, addr_body("B", "Sunnyvale", true)).await; let second_id = second["id"].as_str().unwrap().to_string(); // Creating a new default must clear the previous one. let rows = list(&app, &token).await; let defaults: Vec<_> = rows.iter().filter(|r| r["is_default"] == true).collect(); assert_eq!(defaults.len(), 1); assert_eq!(defaults[0]["id"], second_id); // Setting the first one back as default flips the flag atomically. let res = client() .post(app.url(&format!("/api/addresses/{first_id}/default"))) .bearer_auth(&token) .send() .await .unwrap(); assert_eq!(res.status(), 200); let rows = list(&app, &token).await; let defaults: Vec<_> = rows.iter().filter(|r| r["is_default"] == true).collect(); assert_eq!(defaults.len(), 1); assert_eq!(defaults[0]["id"], first_id); // Deleting the current default promotes the most recent remaining row. let res = client() .delete(app.url(&format!("/api/addresses/{first_id}"))) .bearer_auth(&token) .send() .await .unwrap(); assert_eq!(res.status(), 200); let remaining: Vec = res.json().await.unwrap(); assert_eq!(remaining.len(), 1); assert_eq!(remaining[0]["id"], second_id); assert_eq!(remaining[0]["is_default"], true); } #[tokio::test] #[serial] async fn cross_user_access_is_404() { let app = spawn_app().await; let (token_a, _) = register_customer(&app, "addr-a").await; let (token_b, _) = register_customer(&app, "addr-b").await; let created = create(&app, &token_a, addr_body("A", "Cupertino", true)).await; let id = created["id"].as_str().unwrap().to_string(); let res = client() .put(app.url(&format!("/api/addresses/{id}"))) .bearer_auth(&token_b) .json(&addr_body("Hijack", "Nowhere", true)) .send() .await .unwrap(); assert_eq!(res.status(), 404); let res = client() .delete(app.url(&format!("/api/addresses/{id}"))) .bearer_auth(&token_b) .send() .await .unwrap(); assert_eq!(res.status(), 404); let res = client() .post(app.url(&format!("/api/addresses/{id}/default"))) .bearer_auth(&token_b) .send() .await .unwrap(); assert_eq!(res.status(), 404); } #[tokio::test] #[serial] async fn unauthenticated_requests_are_rejected() { let app = spawn_app().await; 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()), ), ] { let res = client() .request(method.parse().unwrap(), app.url(&path)) .send() .await .unwrap(); assert_eq!(res.status(), 401, "{method} {path} must require auth"); } } #[tokio::test] #[serial] async fn missing_fields_are_400() { let app = spawn_app().await; let (token, _) = register_customer(&app, "addr-invalid").await; let res = client() .post(app.url("/api/addresses")) .bearer_auth(&token) .json(&serde_json::json!({ "recipient": "", "phone": "+1 555 0100", "country": "US", "region": "California", "city": "Cupertino", "line1": "1 Infinite Loop", "postal_code": "95014", })) .send() .await .unwrap(); assert_eq!(res.status(), 400); }