Files
kakeibo/backend/tests/reports.rs
T
suphicakir 87409590a6 feat(raporlar): harcama kırılımı uç noktası ve dönem istatistiklerinde birikim oranı verileri
- GET /reports/breakdown: grup, tüm kategoriler, ödeme türü ve ay kırılımı; dönem/tarih/grup/kategori/ödeme türü filtreleri
- /stats/periods: gelir, sabit gider, devirler, gerçekleşen birikim
- Faz 9 planı kullanıcı kararlarıyla güncellendi (ekstre içe aktarma, JSON geri dönüş noktası, aylık rapor)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 01:48:46 +03:00

192 lines
5.9 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.
//! 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;
}