//! Hane izolasyonu testleri: bir hane, başka hanenin verisini göremez ve değiştiremez. //! (KAKEIBO_PROMPT.md §11 — "başka hanenin verisine erişim imkânsız olmalı") mod common; use axum::http::{Method, StatusCode}; use common::TestApp; use serde_json::{Value, json}; fn ids(list: &Value) -> Vec { list.as_array() .unwrap() .iter() .map(|i| i["id"].as_str().unwrap().to_string()) .collect() } #[tokio::test] async fn households_cannot_see_or_touch_each_other() { let app = TestApp::new().await; let a = app.register("Ayse", None).await; let b = app.register("Burak", None).await; assert_ne!(a.household_id, b.household_id); // A'ya özel kategori ve ödeme türü let (st, cat) = app .post( "/categories", &a, json!({ "group": "wants", "name": "A'nın gizli kategorisi" }), ) .await; assert_eq!(st, StatusCode::CREATED); let cat_id = cat["id"].as_str().unwrap(); let (_, pm) = app .post("/payment-methods", &a, json!({ "name": "A'nın kartı" })) .await; let pm_id = pm["id"].as_str().unwrap(); let (_, invite) = app.post("/household/invites", &a, json!({})).await; let invite_id = invite["id"].as_str().unwrap(); // --- Okuma: B, A'nın kayıtlarını görmez --- let (_, b_cats) = app.get("/categories?include_inactive=true", &b).await; assert!(!ids(&b_cats).contains(&cat_id.to_string())); let (_, b_pms) = app.get("/payment-methods?include_inactive=true", &b).await; assert!(!ids(&b_pms).contains(&pm_id.to_string())); let (_, b_hh) = app.get("/household", &b).await; assert_eq!(b_hh["id"], b.household_id.as_str()); assert_eq!(b_hh["members"].as_array().unwrap().len(), 1); let (_, b_invites) = app.get("/household/invites", &b).await; assert_eq!(b_invites, json!([])); // --- Yazma: B, A'nın kayıtlarını değiştiremez (404: varlığı bile sızmaz) --- let (st, _) = app .patch( &format!("/categories/{cat_id}"), &b, json!({ "name": "ele geçirildi" }), ) .await; assert_eq!(st, StatusCode::NOT_FOUND); let (st, _) = app .patch( &format!("/payment-methods/{pm_id}"), &b, json!({ "is_active": false }), ) .await; assert_eq!(st, StatusCode::NOT_FOUND); let (st, _) = app .delete(&format!("/household/invites/{invite_id}"), &b) .await; assert_eq!(st, StatusCode::NOT_FOUND); // B, kendi sıralama listesine A'nın kaydını sokamaz let mut b_wants: Vec = b_cats .as_array() .unwrap() .iter() .filter(|c| c["group"] == "wants") .map(|c| c["id"].as_str().unwrap().to_string()) .collect(); b_wants.push(cat_id.to_string()); let (st, _) = app .put( "/categories/order", &b, json!({ "group": "wants", "ids": b_wants }), ) .await; assert_eq!(st, StatusCode::BAD_REQUEST); // --- A'nın verisi hiç değişmedi --- let (_, a_cats) = app.get("/categories", &a).await; let mine = a_cats .as_array() .unwrap() .iter() .find(|c| c["id"] == cat_id) .unwrap(); assert_eq!(mine["name"], "A'nın gizli kategorisi"); assert_eq!(mine["version"], 1); let (_, a_invites) = app.get("/household/invites", &a).await; assert_eq!(ids(&a_invites), vec![invite_id.to_string()]); app.cleanup().await; } #[tokio::test] async fn member_joins_via_invite_and_shares_household_data() { let app = TestApp::new().await; let owner = app.register("Sahip", None).await; let (_, inv) = app.post("/household/invites", &owner, json!({})).await; let member = app .register("Uye", Some(inv["code"].as_str().unwrap())) .await; assert_eq!(member.household_id, owner.household_id); // Üye, sahibin eklediği kategoriyi görür ve düzenleyebilir (ortak defter) let (_, cat) = app .post( "/categories", &owner, json!({ "group": "needs", "name": "Ortak" }), ) .await; let (st, _) = app .patch( &format!("/categories/{}", cat["id"].as_str().unwrap()), &member, json!({ "emoji": "🤝" }), ) .await; assert_eq!(st, StatusCode::OK); // Ama davet oluşturamaz let (st, _) = app.post("/household/invites", &member, json!({})).await; assert_eq!(st, StatusCode::FORBIDDEN); app.cleanup().await; } #[tokio::test] async fn protected_endpoints_require_session() { let app = TestApp::new().await; let endpoints = [ (Method::GET, "/auth/me"), (Method::GET, "/household"), (Method::GET, "/household/invites"), (Method::POST, "/household/invites"), (Method::GET, "/categories"), (Method::POST, "/categories"), (Method::PUT, "/categories/order"), (Method::GET, "/payment-methods"), (Method::POST, "/payment-methods"), ]; for (method, path) in endpoints { // Oturumsuz ve sahte cookie ile for cookie in [None, Some("kakeibo_session=sahte.token.degeri")] { let (st, _, _) = app .send(method.clone(), path, cookie, Some(json!({}))) .await; assert_eq!(st, StatusCode::UNAUTHORIZED, "{method} {path} korumasız!"); } } app.cleanup().await; }