Files
kakeibo/backend/tests/account.rs
T

268 lines
8.8 KiB
Rust
Raw 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.
//! Hesap ve hane ayarları: profil adı, şifre değiştirme (diğer oturumlar kapanır),
//! hane adı + bütçe varsayılanları, üye çıkarma, yetkiler.
mod common;
use std::time::Duration;
use axum::http::{Method, StatusCode};
use common::{Session, TestApp};
use serde_json::{Value, json};
async fn login(app: &TestApp, email: &str, password: &str) -> (StatusCode, Option<String>) {
let (st, _, cookie) = app
.send(
Method::POST,
"/auth/login",
None,
Some(json!({ "email": email, "password": password, "remember_me": true })),
)
.await;
(st, cookie)
}
async fn me(app: &TestApp, s: &Session) -> (StatusCode, Value) {
app.get("/auth/me", s).await
}
fn with_cookie(s: &Session, cookie: String) -> Session {
Session {
cookie,
household_id: s.household_id.clone(),
}
}
#[tokio::test]
async fn profile_name_and_password_change_closes_other_sessions() {
let app = TestApp::new().await;
let a = app.register("Ayse", None).await;
let (_, m) = me(&app, &a).await;
let email = m["user"]["email"].as_str().unwrap().to_string();
// Ad
let (st, m) = app
.patch("/auth/me", &a, json!({ "name": " Ayşe Ç. " }))
.await;
assert_eq!(
(st, m["user"]["name"].as_str()),
(StatusCode::OK, Some("Ayşe Ç."))
);
let (st, _) = app.patch("/auth/me", &a, json!({ "name": " " })).await;
assert_eq!(st, StatusCode::BAD_REQUEST);
// İkinci cihaz
let (st, other) = login(&app, &email, "test-sifre-123").await;
assert_eq!(st, StatusCode::OK);
let other = with_cookie(&a, other.unwrap());
tokio::time::sleep(Duration::from_millis(1100)).await; // iat saniye hassasiyetinde
// Yanlış mevcut şifre / kısa / aynı şifre
let pw = |cur: &str, new: &str| json!({ "current_password": cur, "new_password": new });
let (st, b) = app
.post(
"/auth/me/password",
&a,
pw("yanlis-sifre", "yeni-sifre-456"),
)
.await;
assert_eq!(st, StatusCode::BAD_REQUEST);
assert_eq!(b["error"]["message"], "Mevcut şifre hatalı");
let (st, _) = app
.post("/auth/me/password", &a, pw("test-sifre-123", "kisa"))
.await;
assert_eq!(st, StatusCode::BAD_REQUEST);
let (st, b) = app
.post(
"/auth/me/password",
&a,
pw("test-sifre-123", "test-sifre-123"),
)
.await;
assert_eq!(st, StatusCode::BAD_REQUEST, "{b}");
// Doğru → bu cihaz yeni çerezle devam, diğer cihaz düşer
let (st, _, cookie) = app
.send(
Method::POST,
"/auth/me/password",
Some(&a.cookie),
Some(pw("test-sifre-123", "yeni-sifre-456")),
)
.await;
assert_eq!(st, StatusCode::NO_CONTENT);
let fresh = with_cookie(&a, cookie.expect("yeni oturum çerezi"));
assert_eq!(me(&app, &fresh).await.0, StatusCode::OK);
assert_eq!(
me(&app, &other).await.0,
StatusCode::UNAUTHORIZED,
"diğer cihaz"
);
assert_eq!(me(&app, &a).await.0, StatusCode::UNAUTHORIZED, "eski çerez");
// Giriş: eski şifre geçmez, yeni geçer
assert_eq!(
login(&app, &email, "test-sifre-123").await.0,
StatusCode::UNAUTHORIZED
);
assert_eq!(
login(&app, &email, "yeni-sifre-456").await.0,
StatusCode::OK
);
app.cleanup().await;
}
#[tokio::test]
async fn wrong_current_password_is_rate_limited() {
let app = TestApp::new().await;
let a = app.register("Ayse", None).await;
let body = json!({ "current_password": "yanlis", "new_password": "yeni-sifre-456" });
for _ in 0..5 {
let (st, _) = app.post("/auth/me/password", &a, body.clone()).await;
assert_eq!(st, StatusCode::BAD_REQUEST);
}
let (st, _) = app
.post(
"/auth/me/password",
&a,
json!({ "current_password": "test-sifre-123", "new_password": "yeni-sifre-456" }),
)
.await;
assert_eq!(st, StatusCode::TOO_MANY_REQUESTS, "doğru şifre de bekler");
app.cleanup().await;
}
#[tokio::test]
async fn household_settings_and_budget_default_split() {
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;
let (_, h) = app.get("/household", &owner).await;
assert_eq!(
h["settings"],
json!({ "budget_split_pct": { "needs": 50, "wants": 30, "culture": 10, "unexpected": 10 }, "budget_suggestion": "default" })
);
let update = json!({
"name": "Çakır Ailesi",
"budget_split_pct": { "needs": 60, "wants": 20, "culture": 10, "unexpected": 10 },
"budget_suggestion": "avg3",
});
let (st, _) = app.patch("/household", &member, update.clone()).await;
assert_eq!(st, StatusCode::FORBIDDEN, "üye değiştiremez");
let (st, h) = app.patch("/household", &owner, update).await;
assert_eq!(st, StatusCode::OK, "{h}");
assert_eq!(h["name"], "Çakır Ailesi");
assert_eq!(h["settings"]["budget_split_pct"]["needs"], 60);
assert_eq!(h["settings"]["budget_suggestion"], "avg3");
let (_, m) = me(&app, &member).await;
assert_eq!(
m["household"]["name"], "Çakır Ailesi",
"üye de yeni adı görür"
);
// Toplam 100 değil / geçersiz yöntem
for bad in [
json!({ "budget_split_pct": { "needs": 60, "wants": 30, "culture": 10, "unexpected": 10 } }),
json!({ "budget_suggestion": "hepsi" }),
json!({ "name": "" }),
] {
let (st, b) = app.patch("/household", &owner, bad).await;
assert_eq!(st, StatusCode::BAD_REQUEST, "{b}");
}
// Varsayılan öneri yeni yüzdeleri kullanır: harcanabilir 100.000 → 60/20/10/10
let (_, p) = app
.post(
"/periods",
&owner,
json!({ "start_date": "2026-11-01", "end_date": "2026-12-01" }),
)
.await;
let pid = p["id"].as_str().unwrap();
app.post(
&format!("/periods/{pid}/plan-items"),
&owner,
json!({ "kind": "income", "name": "Maaş", "amount": 100_000 }),
)
.await;
let (_, sug) = app
.get(
&format!("/periods/{pid}/budget/suggestion?method=default"),
&owner,
)
.await;
assert_eq!(
sug["amounts"],
json!({ "needs": 60_000, "wants": 20_000, "culture": 10_000, "unexpected": 10_000 })
);
app.cleanup().await;
}
#[tokio::test]
async fn owner_removes_member_who_can_be_invited_again() {
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;
let (_, mm) = me(&app, &member).await;
let member_id = mm["user"]["id"].as_str().unwrap().to_string();
let member_email = mm["user"]["email"].as_str().unwrap().to_string();
let (_, om) = me(&app, &owner).await;
let owner_id = om["user"]["id"].as_str().unwrap().to_string();
// Üye kimseyi çıkaramaz; sahip kendini çıkaramaz; başka hane bulunamaz
let (st, _) = app
.delete(&format!("/household/members/{owner_id}"), &member)
.await;
assert_eq!(st, StatusCode::FORBIDDEN);
let (st, _) = app
.delete(&format!("/household/members/{owner_id}"), &owner)
.await;
assert_eq!(st, StatusCode::BAD_REQUEST);
let stranger = app.register("Yabanci", None).await;
let (st, _) = app
.delete(&format!("/household/members/{member_id}"), &stranger)
.await;
assert_eq!(st, StatusCode::NOT_FOUND);
// Çıkar → oturumu anında düşer, listede yok, giriş yapamaz
let (st, _) = app
.delete(&format!("/household/members/{member_id}"), &owner)
.await;
assert_eq!(st, StatusCode::NO_CONTENT);
assert_eq!(me(&app, &member).await.0, StatusCode::UNAUTHORIZED);
let (_, h) = app.get("/household", &owner).await;
assert_eq!(h["members"].as_array().unwrap().len(), 1);
assert_eq!(
login(&app, &member_email, "test-sifre-123").await.0,
StatusCode::UNAUTHORIZED
);
// Aynı e-posta yeni davetle yeniden katılabilir
let (_, inv) = app.post("/household/invites", &owner, json!({})).await;
let (st, b, _) = app
.send(
Method::POST,
"/auth/register",
None,
Some(json!({
"name": "Uye",
"email": member_email,
"password": "test-sifre-123",
"invite_code": inv["code"],
})),
)
.await;
assert_eq!(st, StatusCode::CREATED, "{b}");
app.cleanup().await;
}