Files
kakeibo/backend/tests/savings_link.rs
T
suphicakir 48554e0ff6 feat(birikim): dönem kapanışındaki birikimi varlıklara ekleme
- Kapatma sonucunda onayla "Birikime ekle" (TL alımı, dönemin son günü)
- Birikimler'de "Bekleyen birikimler" listesi
- Yeniden kapatmada bağlı kayıt yeni tutara güncellenir (0 → silinir), TL çıkışları aşılırsa reddedilir
- Panel "Toplam birikim" artık portföyün güncel değeri

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

224 lines
7.2 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.
//! Dönem kapanışı → birikim: onayla TL varlığına ekleme, bekleyenler, yeniden kapatmada
//! bağlı kaydın güncellenmesi / silinmesi, birikimden çıkış varsa reddedilmesi.
mod common;
use axum::http::StatusCode;
use common::{Session, TestApp};
use serde_json::{Value, json};
/// Ekim: gelir 10.000, harcama 3.000 → gerçekleşen 7.000; `to_savings` ile kapatılır
async fn october(app: &TestApp, s: &Session, to_savings: i64) -> String {
let (_, p) = app
.post(
"/periods",
s,
json!({ "start_date": "2026-10-01", "end_date": "2026-11-01" }),
)
.await;
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;
let (_, cats) = app.get("/categories", s).await;
let cat = cats[0]["id"].as_str().unwrap();
app.post(
&format!("/periods/{id}/entries"),
s,
json!({ "date": "2026-10-10", "amount": 300_000, "category_id": cat }),
)
.await;
close(app, s, &id, to_savings).await;
id
}
async fn close(app: &TestApp, s: &Session, id: &str, to_savings: i64) -> Value {
let (st, b) = app
.post(
&format!("/periods/{id}/close"),
s,
json!({ "to_savings": to_savings }),
)
.await;
assert_eq!(st, StatusCode::OK, "{b}");
b
}
async fn reopen(app: &TestApp, s: &Session, id: &str) {
let (st, b) = app
.post(&format!("/periods/{id}/reopen"), s, json!({}))
.await;
assert_eq!(st, StatusCode::OK, "{b}");
}
async fn cash(app: &TestApp, s: &Session) -> Value {
let (_, list) = app.get("/assets", s).await;
list.as_array()
.unwrap()
.iter()
.find(|a| a["code"] == "TRY")
.unwrap()
.clone()
}
async fn cash_txs(app: &TestApp, s: &Session) -> Vec<Value> {
let id = cash(app, s).await["id"].as_str().unwrap().to_string();
let (_, list) = app.get(&format!("/assets/{id}/transactions"), s).await;
list.as_array().unwrap().clone()
}
#[tokio::test]
async fn add_closing_savings_once() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
let oct = october(&app, &s, 500_000).await;
let (_, pending) = app.get("/savings/pending", &s).await;
assert_eq!(
pending,
json!([{ "period_id": oct, "name": "Ekim 2026", "last_date": "2026-10-31", "to_savings": 500_000 }])
);
let (st, t) = app
.post(&format!("/periods/{oct}/to-savings"), &s, json!({}))
.await;
assert_eq!(st, StatusCode::CREATED, "{t}");
assert_eq!(t["type"], "buy");
assert_eq!(t["total"], 500_000);
assert_eq!(t["date"], "2026-10-31");
assert_eq!(t["note"], "Ekim 2026 kapanışı");
assert_eq!(t["source_period_id"], oct.as_str());
assert_eq!(cash(&app, &s).await["cost"], 500_000);
let (st, b) = app
.post(&format!("/periods/{oct}/to-savings"), &s, json!({}))
.await;
assert_eq!(st, StatusCode::CONFLICT, "{b}");
let (_, pending) = app.get("/savings/pending", &s).await;
assert_eq!(pending, json!([]));
// Açık dönem eklenemez; başka hane göremez
let (_, nov) = app
.post(
"/periods",
&s,
json!({ "start_date": "2026-11-01", "end_date": "2026-12-01" }),
)
.await;
let (st, _) = app
.post(
&format!("/periods/{}/to-savings", nov["id"].as_str().unwrap()),
&s,
json!({}),
)
.await;
assert_eq!(st, StatusCode::BAD_REQUEST);
let other = app.register("Diger", None).await;
let (st, _) = app
.post(&format!("/periods/{oct}/to-savings"), &other, json!({}))
.await;
assert_eq!(st, StatusCode::NOT_FOUND);
let (_, op) = app.get("/savings/pending", &other).await;
assert_eq!(op, json!([]));
// Silinen kayıt → dönem yeniden "bekleyen" olur
let (st, _) = app
.delete(
&format!("/asset-transactions/{}", t["id"].as_str().unwrap()),
&s,
)
.await;
assert_eq!(st, StatusCode::NO_CONTENT);
let (_, pending) = app.get("/savings/pending", &s).await;
assert_eq!(pending.as_array().unwrap().len(), 1);
app.cleanup().await;
}
#[tokio::test]
async fn reclosing_updates_or_removes_linked_savings() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
let oct = october(&app, &s, 500_000).await;
app.post(&format!("/periods/{oct}/to-savings"), &s, json!({}))
.await;
// Yeni tutarla yeniden kapat → kayıt güncellenir
reopen(&app, &s, &oct).await;
let b = close(&app, &s, &oct, 400_000).await;
assert_eq!(b["savings_linked"], true);
let txs = cash_txs(&app, &s).await;
assert_eq!(txs.len(), 1);
assert_eq!(txs[0]["total"], 400_000);
assert_eq!(txs[0]["version"], 2);
assert_eq!(cash(&app, &s).await["cost"], 400_000);
// Birikim 0 → kayıt silinir; bekleyen de yok
reopen(&app, &s, &oct).await;
let b = close(&app, &s, &oct, 0).await;
assert_eq!(b["savings_linked"], false);
assert!(cash_txs(&app, &s).await.is_empty());
let (_, pending) = app.get("/savings/pending", &s).await;
assert_eq!(pending, json!([]));
// Kayıt yokken yeniden kapat → otomatik eklenmez, bekleyenlere düşer
reopen(&app, &s, &oct).await;
let b = close(&app, &s, &oct, 600_000).await;
assert_eq!(b["savings_linked"], false);
assert!(cash_txs(&app, &s).await.is_empty());
let (_, pending) = app.get("/savings/pending", &s).await;
assert_eq!(pending[0]["to_savings"], 600_000);
app.cleanup().await;
}
#[tokio::test]
async fn reclosing_is_refused_when_savings_were_spent() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
let oct = october(&app, &s, 500_000).await;
app.post(&format!("/periods/{oct}/to-savings"), &s, json!({}))
.await;
// Birikimden 4.500 TL çıkış
let cash_id = cash(&app, &s).await["id"].as_str().unwrap().to_string();
let (st, _) = app
.post(
&format!("/assets/{cash_id}/transactions"),
&s,
json!({ "type": "sell", "date": "2026-11-05", "total": 450_000 }),
)
.await;
assert_eq!(st, StatusCode::CREATED);
reopen(&app, &s, &oct).await;
let (st, b) = app
.post(
&format!("/periods/{oct}/close"),
&s,
json!({ "to_savings": 300_000 }),
)
.await;
assert_eq!(st, StatusCode::CONFLICT, "{b}");
assert!(
b["error"]["message"]
.as_str()
.unwrap()
.contains("TL çıkışları"),
"{b}"
);
// Hiçbir şey değişmedi: dönem düzeltmede, kayıt 5.000 TL
let (_, p) = app.get(&format!("/periods/{oct}"), &s).await;
assert_eq!(p["status"], "correcting");
let buy = cash_txs(&app, &s)
.await
.into_iter()
.find(|t| t["type"] == "buy")
.unwrap();
assert_eq!(buy["total"], 500_000);
// Yeterli tutarla kapanır
let b = close(&app, &s, &oct, 450_000).await;
assert_eq!(b["savings_linked"], true);
app.cleanup().await;
}