//! Panel verileri: günlük harcama ve dönem istatistikleri (toplam birikim dahil). mod common; use axum::http::StatusCode; use common::{Session, TestApp}; use serde_json::{Value, json}; async fn open(app: &TestApp, s: &Session, start: &str, end: &str, goal: i64) -> String { let (st, p) = app .post( "/periods", s, json!({ "start_date": start, "end_date": end, "savings_goal": goal }), ) .await; assert_eq!(st, StatusCode::CREATED, "{p}"); p["id"].as_str().unwrap().to_string() } async fn category(app: &TestApp, s: &Session, group: &str) -> String { let (_, cats) = app.get("/categories", s).await; cats.as_array() .unwrap() .iter() .find(|c| c["group"] == group) .unwrap()["id"] .as_str() .unwrap() .to_string() } async fn spend(app: &TestApp, s: &Session, pid: &str, date: &str, amount: i64, cat: &str) -> Value { let (st, e) = app .post( &format!("/periods/{pid}/entries"), s, json!({ "date": date, "amount": amount, "category_id": cat }), ) .await; assert_eq!(st, StatusCode::CREATED, "{e}"); e } async fn income(app: &TestApp, s: &Session, pid: &str, amount: i64) { let (st, b) = app .post( &format!("/periods/{pid}/plan-items"), s, json!({ "kind": "income", "name": "Maaş", "amount": amount }), ) .await; assert_eq!(st, StatusCode::CREATED, "{b}"); } /// Kasım: gelir 10.000, harcama 3.000, 5.000 birikime → 2.000 artı Aralık'a. /// Aralık: gelir 20.000, hedef 1.000 → harcanabilir 21.000. async fn two_periods(app: &TestApp, s: &Session) -> (String, String) { let needs = category(app, s, "needs").await; let nov = open(app, s, "2026-11-01", "2026-12-01", 0).await; income(app, s, &nov, 1_000_000).await; spend(app, s, &nov, "2026-11-30", 300_000, &needs).await; let (st, b) = app .post( &format!("/periods/{nov}/close"), s, json!({ "to_savings": 500_000 }), ) .await; assert_eq!(st, StatusCode::OK, "{b}"); let dec = open(app, s, "2026-12-01", "2027-01-01", 100_000).await; income(app, s, &dec, 2_000_000).await; (nov, dec) } #[tokio::test] async fn daily_spending_by_group() { let app = TestApp::new().await; let s = app.register("Test", None).await; let (_, dec) = two_periods(&app, &s).await; let needs = category(&app, &s, "needs").await; let wants = category(&app, &s, "wants").await; spend(&app, &s, &dec, "2026-12-01", 100_000, &needs).await; spend(&app, &s, &dec, "2026-12-01", 50_000, &wants).await; spend(&app, &s, &dec, "2026-12-07", 20_000, &needs).await; spend(&app, &s, &dec, "2026-12-31", 7_000, &needs).await; let gone = spend(&app, &s, &dec, "2026-12-02", 99_000, &needs).await; let (st, _) = app .delete(&format!("/entries/{}", gone["id"].as_str().unwrap()), &s) .await; assert_eq!(st, StatusCode::NO_CONTENT); let (st, days) = app.get(&format!("/periods/{dec}/daily"), &s).await; assert_eq!(st, StatusCode::OK, "{days}"); let days = days.as_array().unwrap(); assert_eq!(days.len(), 31, "her gün, harcama olmasa da"); assert_eq!(days[0]["date"], "2026-12-01"); assert_eq!( days[0]["spent"], json!({ "needs": 100_000, "wants": 50_000, "culture": 0, "unexpected": 0 }) ); assert_eq!(days[1]["spent"]["needs"], 0, "silinen kayıt sayılmaz"); assert_eq!(days[6]["spent"]["needs"], 20_000); assert_eq!(days[30]["date"], "2026-12-31"); assert_eq!(days[30]["spent"]["needs"], 7_000, "son gün dahil"); // Kasım'ın 30.11 harcaması Aralık'a karışmaz let total: i64 = days .iter() .map(|d| d["spent"]["needs"].as_i64().unwrap()) .sum(); assert_eq!(total, 127_000); // Başka hane göremez let other = app.register("Diger", None).await; let (st, _) = app.get(&format!("/periods/{dec}/daily"), &other).await; assert_eq!(st, StatusCode::NOT_FOUND); app.cleanup().await; } #[tokio::test] async fn period_stats_and_total_savings() { let app = TestApp::new().await; let s = app.register("Test", None).await; let (nov, dec) = two_periods(&app, &s).await; let needs = category(&app, &s, "needs").await; let culture = category(&app, &s, "culture").await; spend(&app, &s, &dec, "2026-12-05", 40_000, &needs).await; spend(&app, &s, &dec, "2026-12-06", 2_500, &culture).await; let (st, b) = app.get("/stats/periods", &s).await; assert_eq!(st, StatusCode::OK, "{b}"); assert_eq!(b["total_saved"], 500_000); let list = b["periods"].as_array().unwrap(); assert_eq!(list.len(), 2); // Eskiden yeniye assert_eq!(list[0]["id"], nov.as_str()); assert_eq!(list[0]["status"], "closed"); assert_eq!(list[0]["spendable"], 1_000_000); assert_eq!(list[0]["spent"], 300_000); assert_eq!( list[0]["spent_by_group"], json!({ "needs": 300_000, "wants": 0, "culture": 0, "unexpected": 0 }) ); assert_eq!(list[0]["to_savings"], 500_000); assert_eq!(list[0]["last_date"], "2026-11-30"); assert_eq!(list[1]["id"], dec.as_str()); assert_eq!( list[1]["spendable"], 2_100_000, "20.000 − 1.000 hedef + 2.000 artı" ); assert_eq!(list[1]["spent"], 42_500); assert_eq!(list[1]["spent_by_group"]["needs"], 40_000); assert_eq!(list[1]["spent_by_group"]["culture"], 2_500); assert_eq!(list[1]["to_savings"], Value::Null); // limit: en yeni N dönem let (_, b) = app.get("/stats/periods?limit=1", &s).await; let list = b["periods"].as_array().unwrap(); assert_eq!(list.len(), 1); assert_eq!(list[0]["id"], dec.as_str()); assert_eq!( b["total_saved"], 500_000, "toplam birikim limitten bağımsız" ); // Başka hane: boş let other = app.register("Diger", None).await; let (_, b) = app.get("/stats/periods", &other).await; assert_eq!(b["total_saved"], 0); assert_eq!(b["periods"], json!([])); app.cleanup().await; }