Files
kakeibo/backend/tests/periods.rs
T
suphicakir b883530f76 feat(dönem): dönem açma, açık dönem, düzenleme ve haftalar (backend)
- /api/periods: liste, açık dönem, aç, detay, düzenle
- Kurallar: tek açık dönem, çakışma yok, bitiş hariç, en fazla 62 gün, kapalı dönem salt okunur
- Tarih daraltılırken dışarıda harcama kalırsa engellenir
- Haftalar Pazartesi başlangıçlı, kısmi haftalar ayrı (takvim yardımcıları)
- Otomatik dönem adı: "Aralık 2026" / "15 Ara 2026 – 14 Oca 2027"
- Testler: takvim (4), dönem kuralları (2), entegrasyon (5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 02:56:13 +03:00

245 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 kuralları (KAKEIBO_PROMPT §6).
mod common;
use axum::http::StatusCode;
use common::TestApp;
use mongodb::bson::{Document, doc};
use serde_json::{Value, json};
async fn open_december(app: &TestApp, s: &common::Session) -> Value {
let (st, p) = app
.post(
"/periods",
s,
json!({ "start_date": "2026-12-01", "end_date": "2027-01-01", "savings_goal": 500000 }),
)
.await;
assert_eq!(st, StatusCode::CREATED, "{p}");
p
}
#[tokio::test]
async fn open_period_returns_weeks_and_default_name() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
// Başta açık dönem yok → null
let (st, cur) = app.get("/periods/current", &s).await;
assert_eq!((st, cur), (StatusCode::OK, Value::Null));
let p = open_december(&app, &s).await;
assert_eq!(p["name"], "Aralık 2026");
assert_eq!(p["status"], "open");
assert_eq!(p["days"], 31);
assert_eq!(p["last_date"], "2026-12-31");
assert_eq!(p["savings_goal"], 500000);
let days: Vec<i64> = p["weeks"]
.as_array()
.unwrap()
.iter()
.map(|w| w["days"].as_i64().unwrap())
.collect();
assert_eq!(
days,
vec![6, 7, 7, 7, 4],
"Pazartesi başlangıçlı, kısmi haftalar ayrı"
);
let (_, cur) = app.get("/periods/current", &s).await;
assert_eq!(cur["id"], p["id"]);
app.cleanup().await;
}
#[tokio::test]
async fn only_one_open_period_and_no_overlap() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
let dec = open_december(&app, &s).await;
// İkinci açık dönem → 409
let (st, body) = app
.post(
"/periods",
&s,
json!({ "start_date": "2027-01-01", "end_date": "2027-02-01" }),
)
.await;
assert_eq!(st, StatusCode::CONFLICT);
assert!(
body["error"]["message"]
.as_str()
.unwrap()
.contains("Aralık 2026")
);
// Aralık'ı kapat (Faz 6'da sihirbaz yapacak) → çakışan yeni dönem yine 409
app.db
.collection::<Document>("periods")
.update_one(
doc! { "name": "Aralık 2026" },
doc! { "$set": { "status": "closed" } },
)
.await
.unwrap();
let (st, body) = app
.post(
"/periods",
&s,
json!({ "start_date": "2026-12-20", "end_date": "2027-01-20" }),
)
.await;
assert_eq!(st, StatusCode::CONFLICT, "{body}");
assert!(
body["error"]["message"]
.as_str()
.unwrap()
.contains("çakışıyor")
);
// Bitişik dönem (bitiş hariç olduğu için 1 Ocak serbest) → 201
let (st, jan) = app
.post(
"/periods",
&s,
json!({ "start_date": "2027-01-01", "end_date": "2027-02-01" }),
)
.await;
assert_eq!(st, StatusCode::CREATED, "{jan}");
assert_eq!(jan["name"], "Ocak 2027");
// Liste: en yeni başta
let (_, list) = app.get("/periods", &s).await;
let names: Vec<&str> = list
.as_array()
.unwrap()
.iter()
.map(|p| p["name"].as_str().unwrap())
.collect();
assert_eq!(names, vec!["Ocak 2027", "Aralık 2026"]);
// Kapalı dönem salt okunur
let (st, _) = app
.patch(
&format!("/periods/{}", dec["id"].as_str().unwrap()),
&s,
json!({ "savings_goal": 1 }),
)
.await;
assert_eq!(st, StatusCode::CONFLICT);
app.cleanup().await;
}
#[tokio::test]
async fn validation_errors() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
for (start, end) in [
("2026-12-10", "2026-12-01"), // ters
("2026-12-01", "2026-12-01"), // boş
("2026-02-30", "2026-03-30"), // geçersiz gün
("01.12.2026", "2027-01-01"), // biçim
("2026-12-01", "2027-03-01"), // 90 gün
] {
let (st, _) = app
.post(
"/periods",
&s,
json!({ "start_date": start, "end_date": end }),
)
.await;
assert_eq!(st, StatusCode::BAD_REQUEST, "{start} → {end}");
}
let (st, _) = app
.post(
"/periods",
&s,
json!({ "start_date": "2026-12-01", "end_date": "2027-01-01", "savings_goal": -5 }),
)
.await;
assert_eq!(st, StatusCode::BAD_REQUEST, "negatif birikim");
app.cleanup().await;
}
#[tokio::test]
async fn update_dates_name_goal_and_version() {
let app = TestApp::new().await;
let s = app.register("Test", None).await;
let p = open_december(&app, &s).await;
let path = format!("/periods/{}", p["id"].as_str().unwrap());
// Maaş 15'inde → dönemi kaydır; ad otomatik güncellenmez, istenirse verilir
let (st, upd) = app
.patch(
&path,
&s,
json!({ "start_date": "2026-12-15", "end_date": "2027-01-15", "name": "", "savings_goal": 750000, "version": 1 }),
)
.await;
assert_eq!(st, StatusCode::OK, "{upd}");
assert_eq!(upd["name"], "15 Ara 2026 – 14 Oca 2027");
assert_eq!(upd["savings_goal"], 750000);
assert_eq!(upd["version"], 2);
// Eski sürümle → 409
let (st, _) = app
.patch(&path, &s, json!({ "name": "X", "version": 1 }))
.await;
assert_eq!(st, StatusCode::CONFLICT);
// Aralığın dışında kalacak bir harcama varsa daraltma engellenir
let hid = mongodb::bson::Uuid::parse_str(&s.household_id).unwrap();
let pid = mongodb::bson::Uuid::parse_str(p["id"].as_str().unwrap()).unwrap();
app.db
.collection::<Document>("entries")
.insert_one(doc! { "household_id": hid, "period_id": pid, "date": "2027-01-10", "deleted_at": null })
.await
.unwrap();
let (st, body) = app
.patch(&path, &s, json!({ "end_date": "2027-01-05" }))
.await;
assert_eq!(st, StatusCode::CONFLICT, "{body}");
assert!(
body["error"]["message"]
.as_str()
.unwrap()
.contains("1 harcama")
);
app.cleanup().await;
}
#[tokio::test]
async fn periods_are_isolated_between_households() {
let app = TestApp::new().await;
let a = app.register("A", None).await;
let b = app.register("B", None).await;
let p = open_december(&app, &a).await;
let path = format!("/periods/{}", p["id"].as_str().unwrap());
// B, A'nın dönemini göremez/değiştiremez; kendi açık dönemi yok
assert_eq!(app.get(&path, &b).await.0, StatusCode::NOT_FOUND);
assert_eq!(
app.patch(&path, &b, json!({ "name": "ele geçirildi" }))
.await
.0,
StatusCode::NOT_FOUND
);
assert_eq!(app.get("/periods/current", &b).await.1, Value::Null);
assert_eq!(app.get("/periods", &b).await.1, json!([]));
// A'nın açık dönemi B'nin dönem açmasını engellemez (açık dönem kuralı hane bazlı)
let (st, _) = app
.post(
"/periods",
&b,
json!({ "start_date": "2026-12-01", "end_date": "2027-01-01" }),
)
.await;
assert_eq!(st, StatusCode::CREATED);
app.cleanup().await;
}