Files
kakeibo/backend/tests/monthly_report.rs
T

157 lines
6.1 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.
//! Aylık değerlendirme raporu: kapalı dönem (önceki dönemle karşılaştırma, notlar, devir),
//! ara rapor, hane yalıtımı.
mod common;
use axum::http::StatusCode;
use common::{Session, TestApp};
use serde_json::json;
async fn id_by(app: &TestApp, s: &Session, name: &str) -> String {
let (_, list) = app.get("/categories", s).await;
list.as_array()
.unwrap()
.iter()
.find(|c| c["name"] == name)
.unwrap()["id"]
.as_str()
.unwrap()
.to_string()
}
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}");
let id = p["id"].as_str().unwrap().to_string();
app.post(
&format!("/periods/{id}/plan-items"),
s,
json!({ "kind": "income", "name": "Maaş", "amount": 1_000_000 }),
)
.await;
id
}
async fn spend(app: &TestApp, s: &Session, pid: &str, date: &str, amount: i64, cat: &str) {
let (st, b) = app
.post(
&format!("/periods/{pid}/entries"),
s,
json!({ "date": date, "amount": amount, "category_id": cat }),
)
.await;
assert_eq!(st, StatusCode::CREATED, "{b}");
}
async fn report(app: &TestApp, s: &Session, pid: &str) -> (StatusCode, String, String) {
app.get_text(&format!("/periods/{pid}/report.md"), s).await
}
#[tokio::test]
async fn closed_period_report_with_comparison_notes_and_carry() {
let app = TestApp::new().await;
let s = app.register("Sahip", None).await;
let market = id_by(&app, &s, "Market").await;
let yemek = id_by(&app, &s, "Dışarıda yemek").await;
// Ekim: 10.000 gelir, 6.000 harcama (Market 3.000, yemek 3.000) → kapalı
let oct = open(&app, &s, "2026-10-01", "2026-11-01", 200_000).await;
spend(&app, &s, &oct, "2026-10-05", 300_000, &market).await;
spend(&app, &s, &oct, "2026-10-06", 300_000, &yemek).await;
app.post(&format!("/periods/{oct}/close"), &s, json!({}))
.await;
// Kasım: hedef 3.000; bütçe Zorunlu 4.000 / İstek 1.000; Market 5.000 (+%67), yemek 1.000
let nov = open(&app, &s, "2026-11-01", "2026-12-01", 300_000).await;
app.put(
&format!("/periods/{nov}/budget"),
&s,
json!({ "needs": 400_000, "wants": 100_000, "culture": 0, "unexpected": 0 }),
)
.await;
spend(&app, &s, &nov, "2026-11-17", 500_000, &market).await;
spend(&app, &s, &nov, "2026-11-03", 100_000, &yemek).await;
app.post(
&format!("/periods/{nov}/notes"),
&s,
json!({ "context": "planning", "title": "Bu ayın niyeti", "body_md": "Dışarıda **az** ye." }),
)
.await;
let (_, p) = app.get(&format!("/periods/{nov}"), &s).await;
app.patch(
&format!("/periods/{nov}"),
&s,
json!({ "reflection_md": "Market listesiyle çık.", "version": p["version"] }),
)
.await;
// Açıkken: ara rapor
let (st, ct, md) = report(&app, &s, &nov).await;
assert_eq!(st, StatusCode::OK, "{md}");
assert!(ct.starts_with("text/markdown"), "{ct}");
assert!(md.starts_with("# Kasım 2026 — Aylık Değerlendirme"), "{md}");
// Testin çalıştığı güne göre Kasım 2026 sürüyor ya da bitmiş olabilir
assert_eq!(md.contains("· Sürüyor ("), md.contains("> **Ara rapor:**"));
let (st, b) = app
.post(
&format!("/periods/{nov}/close"),
&s,
json!({ "to_savings": 0 }),
)
.await;
assert_eq!(st, StatusCode::OK, "{b}");
let (st, _, md) = report(&app, &s, &nov).await;
assert_eq!(st, StatusCode::OK);
println!("{md}");
// Başlık ve karşılaştırma
assert!(md.contains("· Kapandı "));
// Önceki dönemle aynı birikim (4.000) → karşılaştırma cümlesi eklenmez
assert!(md.contains(
"> Birikim hedefin 3.000,00 ₺ idi; 4.000,00 ₺ biriktirdin (%133). Hedefe ulaştın.\n"
));
assert!(md.contains("- İstek bütçenin içinde kaldı.\n"));
assert!(md.contains("- Harcama yapmadığın gün: 28 / 30; günlük ortalama 200,00 ₺."));
assert!(md.contains("haftalık bütçe kullanımı %429."));
assert!(md.contains("| Kakeibo'nun 4 sorusu | Bu dönem | Ekim 2026 |"));
assert!(md.contains("| Ne kadar harcadım? | 6.000,00 ₺ | 6.000,00 ₺ |"));
// Gruplar, kategoriler (ortalamaya göre), haftalar
assert!(md.contains("| 🏠 Zorunlu | 4.000,00 ₺ | 5.000,00 ₺ | -1.000,00 ₺ | ❌ %125 |"));
assert!(
md.contains("| 🛒 Market | 5.000,00 ₺ | 1 | %83 | +%67 (+2.000,00 ₺) |"),
"{md}"
);
assert!(md.contains("| 3. hafta | 16.11 – 22.11 |"));
// Yorumlar
assert!(md.contains("- Zorunlu grubu bütçeyi 1.000,00 ₺ aştı (%125)."));
assert!(md.contains("- Market, ortalamasının %67 üstünde (+2.000,00 ₺); 1 işlem."));
assert!(md.contains("- Dışarıda yemek harcaman ortalamanın %67 altında (-2.000,00 ₺)."));
assert!(md.contains("- Zorunlu bütçede kalsaydı birikimin 5.000,00 ₺ olurdu (+1.000,00 ₺)."));
// Notlar
assert!(md.contains("#### Bu ayın niyeti\n\nDışarıda **az** ye."));
assert!(md.contains("#### Nasıl daha iyi yapabilirim?\n\nMarket listesiyle çık."));
// Öneri: Zorunlu (5.000 + 3.000) / 2 = 4.000
assert!(md.contains("| 🏠 Zorunlu | 4.000,00 ₺ | son 2 dönemin ortalaması |"));
assert!(md.contains("1. Market için haftalık 750,00 ₺ sınır koy"));
// Devir: 4.000 birikim, 0'ı aktarıldı → artı devreder
assert!(md.contains("**Devir:** gelecek döneme 4.000,00 ₺ artı devrediyor"));
assert!(md.contains("## Ek: Tüm kategoriler"));
assert!(!md.contains("{{") && !md.contains("{%"));
// Başka hane göremez; geçersiz kimlik → 404
let other = app.register("Diger", None).await;
let (st, _, _) = report(&app, &other, &nov).await;
assert_eq!(st, StatusCode::NOT_FOUND);
let (st, _, _) = report(&app, &s, "yok").await;
assert_eq!(st, StatusCode::NOT_FOUND);
app.cleanup().await;
}