//! Raporlar: harcama kırılımı (grup, tüm kategoriler, ödeme türü, ay) ve filtreler. mod common; use axum::http::StatusCode; use common::{Session, TestApp}; use serde_json::{Value, json}; async fn id_by_name(app: &TestApp, s: &Session, path: &str, name: &str) -> String { let (_, list) = app.get(path, s).await; list.as_array() .unwrap() .iter() .find(|c| c["name"] == name) .unwrap()["id"] .as_str() .unwrap() .to_string() } async fn spend( app: &TestApp, s: &Session, pid: &str, date: &str, amount: i64, cat: &str, pm: Option<&str>, ) -> Value { let mut body = json!({ "date": date, "amount": amount, "category_id": cat }); if let Some(pm) = pm { body["payment_method_id"] = json!(pm); } let (st, e) = app.post(&format!("/periods/{pid}/entries"), s, body).await; assert_eq!(st, StatusCode::CREATED, "{e}"); e } async fn report(app: &TestApp, s: &Session, query: &str) -> Value { let (st, b) = app.get(&format!("/reports/breakdown{query}"), s).await; assert_eq!(st, StatusCode::OK, "{query} → {b}"); b } fn totals(list: &Value, key: &str) -> Vec<(String, i64)> { list.as_array() .unwrap() .iter() .map(|r| { ( r[key].as_str().unwrap_or("-").to_string(), r["total"].as_i64().unwrap(), ) }) .collect() } #[tokio::test] async fn breakdown_with_filters() { let app = TestApp::new().await; let s = app.register("Test", None).await; let market = id_by_name(&app, &s, "/categories", "Market").await; let giyim = id_by_name(&app, &s, "/categories", "Giyim").await; let kitap = id_by_name(&app, &s, "/categories", "Kitap").await; let nakit = id_by_name(&app, &s, "/payment-methods", "Nakit").await; let kart = id_by_name(&app, &s, "/payment-methods", "Kredi Kartı").await; let (_, oct) = app .post( "/periods", &s, json!({ "start_date": "2026-10-01", "end_date": "2026-11-01" }), ) .await; let oct = oct["id"].as_str().unwrap().to_string(); spend(&app, &s, &oct, "2026-10-05", 100_000, &market, Some(&nakit)).await; spend(&app, &s, &oct, "2026-10-20", 50_000, &giyim, None).await; app.post(&format!("/periods/{oct}/close"), &s, json!({})) .await; let (_, nov) = app .post( "/periods", &s, json!({ "start_date": "2026-11-01", "end_date": "2026-12-01" }), ) .await; let nov = nov["id"].as_str().unwrap().to_string(); spend(&app, &s, &nov, "2026-11-02", 30_000, &market, Some(&kart)).await; spend(&app, &s, &nov, "2026-11-03", 20_000, &kitap, Some(&nakit)).await; spend(&app, &s, &nov, "2026-11-04", 10_000, &market, Some(&nakit)).await; let gone = spend(&app, &s, &nov, "2026-11-05", 99_000, &kitap, None).await; app.delete(&format!("/entries/{}", gone["id"].as_str().unwrap()), &s) .await; // Filtresiz: tüm zamanlar let r = report(&app, &s, "").await; assert_eq!(r["total"], 210_000); assert_eq!(r["count"], 5, "silinen sayılmaz"); assert_eq!( totals(&r["groups"], "group"), [ ("needs".into(), 140_000), ("wants".into(), 50_000), ("culture".into(), 20_000), ("unexpected".into(), 0) ] ); assert_eq!( totals(&r["categories"], "category_id"), [ (market.clone(), 140_000), (giyim.clone(), 50_000), (kitap.clone(), 20_000) ] ); assert_eq!(r["categories"][0]["count"], 3); assert_eq!(r["categories"][0]["group"], "needs"); assert_eq!( totals(&r["payment_methods"], "payment_method_id"), [ (nakit.clone(), 130_000), ("-".into(), 50_000), (kart.clone(), 30_000) ], "ödeme türü girilmemiş → null" ); assert_eq!( totals(&r["months"], "month"), [("2026-10".into(), 150_000), ("2026-11".into(), 60_000)] ); // Dönem assert_eq!( report(&app, &s, &format!("?period_id={nov}")).await["total"], 60_000 ); // Tarih aralığı iki dönemi keser (ikisi de dahil) let r = report(&app, &s, "?from=2026-10-15&to=2026-11-03").await; assert_eq!( (r["total"].as_i64(), r["count"].as_i64()), (Some(100_000), Some(3)) ); // Grup, kategori, ödeme türü (+ girilmemiş) assert_eq!(report(&app, &s, "?group=needs").await["total"], 140_000); assert_eq!( report(&app, &s, &format!("?category_id={kitap}")).await["total"], 20_000 ); assert_eq!( report(&app, &s, &format!("?payment_method_id={nakit}")).await["total"], 130_000 ); assert_eq!( report(&app, &s, "?payment_method_id=none").await["total"], 50_000 ); // Birleşik let r = report( &app, &s, &format!("?period_id={nov}&group=needs&payment_method_id={nakit}"), ) .await; assert_eq!( (r["total"].as_i64(), r["count"].as_i64()), (Some(10_000), Some(1)) ); // Hatalı filtreler for q in [ "?from=2026-11-10&to=2026-11-01", "?from=2026-02-30", "?group=xyz", "?category_id=abc", ] { let (st, b) = app.get(&format!("/reports/breakdown{q}"), &s).await; assert_eq!(st, StatusCode::BAD_REQUEST, "{q} → {b}"); } // Başka hane: kendi verisi (boş); bu hanenin dönemi → 404 let other = app.register("Diger", None).await; let r = report(&app, &other, "").await; assert_eq!(r["total"], 0); assert_eq!(r["groups"].as_array().unwrap().len(), 4); assert_eq!(r["categories"], json!([])); let (st, _) = app .get(&format!("/reports/breakdown?period_id={nov}"), &other) .await; assert_eq!(st, StatusCode::NOT_FOUND); app.cleanup().await; }