Files
suphicakir 6eb5440d4d feat(katalog): kategori ve ödeme türü uç noktaları, hane izolasyonu entegrasyon testleri
- /api/categories ve /api/payment-methods: listele, ekle, düzenle, sırala, pasife al
- Sürüm kontrolü ile eşzamanlı düzenleme çakışması (409)
- Backend lib + bin yapısına ayrıldı (entegrasyon testleri için)
- tests/: hane izolasyonu (3) ve katalog (4) entegrasyon testleri
- db:reset artık artık test veritabanlarını da siler
- api.http tamamlandı; Faz 1 tamamlandı

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 23:54:16 +03:00

169 lines
5.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<String> {
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<String> = 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;
}