feat: Vierte Stufe der iOS-App - die Arbeitsliste
Ein neuer Reiter "Eingang" mit allem, worueber jemand entscheiden muss. Vier Abschnitte, jeweils mit Anzahl im Kopf: - Zu buchen: offene Eingangseintraege aus Kurzbefehl, Postfach oder Bank. Buchen, als Uebertrag buchen, verwerfen - oder alle auf einmal, bei denen das Konto feststeht. Unklare tragen einen Hinweis ueber den Knoepfen, nicht darunter: Man soll sie ansehen, nicht wegbuchen. - Koennten Uebertraege sein: zwei getrennte Buchungen, die zusammengehoeren koennten. Verbinden oder ablehnen. Hat eine der beiden eine Kategorie, steht dabei, dass sie durchs Verbinden aus dem Budget faellt. - Ohne Kategorie: gebuchtes Geld, das in keiner Budget-Zeile mitzaehlt. Ein Tipp, eine Kategorie - kein Formular mit zwoelf Feldern davor. - Sieht doppelt aus: gleiches Konto, gleicher Tag, gleicher Betrag. Mit der Kennung der Bank je Zeile, denn verschiedene Kennungen heissen, dass die Bank sie als verschiedene Zahlungen gemeldet hat. Alles in einem Abruf. Vier getrennte Zwischenspeicher koennten offline aus vier verschiedenen Momenten stammen und sich widersprechen. Hier sitzt der einzige 409-Fall der App: Beim Buchen als Uebertrag kann das Backend Bedenken haben. Das ist eine Frage, kein Fehler - die App zeigt den Hinweis und wiederholt den Aufruf auf Wunsch mit trotzdem=true. Ihn zu verschlucken waere falsch, ihn als blossen Fehler zu zeigen auch. Zwei Dinge, die die Tests festhalten: Ohne Konto laesst sich nichts buchen (das Backend lehnt mit 400 ab, der Knopf bleibt also grau - und die Zeile sagt auch warum). Und ein Uebertrag braucht keine Kategorie, sonst fragte die Liste ewig nach einer, die es nie geben wird. Die Pruefdatei deckt jetzt auch Eingang, Dubletten und Uebertragsvorschlaege ab. Die aelteren Pruefungen zaehlten exakt und wurden dadurch rot - sie pruefen jetzt Eigenschaften statt Mengen, damit sie das naechste Wachstum ueberleben. 54 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
"must_change_password": false,
|
||||
"must_change_password_reason": null,
|
||||
"totp_enabled": false,
|
||||
"created_at": "2026-10-21T18:22:28.427216",
|
||||
"created_at": "2026-10-21T19:21:28.289884",
|
||||
"mfa_setup_required": false
|
||||
},
|
||||
"einstellungen": [
|
||||
@@ -58,8 +58,8 @@
|
||||
"position": 0,
|
||||
"savings_category_id": null,
|
||||
"identifiers": [],
|
||||
"balance_cents": 430750,
|
||||
"balance_base_cents": 430750,
|
||||
"balance_cents": 419874,
|
||||
"balance_base_cents": 419874,
|
||||
"bank_balance_cents": null,
|
||||
"bank_balance_at": null,
|
||||
"bank_balance_vergleichbar": false
|
||||
@@ -84,6 +84,27 @@
|
||||
"bank_balance_cents": null,
|
||||
"bank_balance_at": null,
|
||||
"bank_balance_vergleichbar": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Zweitkonto",
|
||||
"type": "girokonto",
|
||||
"currency": "EUR",
|
||||
"icon": "wallet",
|
||||
"color": "#3f51b5",
|
||||
"opening_balance_cents": 0,
|
||||
"opening_date": null,
|
||||
"credit_limit_cents": null,
|
||||
"is_locked": false,
|
||||
"is_archived": false,
|
||||
"position": 2,
|
||||
"savings_category_id": null,
|
||||
"identifiers": [],
|
||||
"balance_cents": 7500,
|
||||
"balance_base_cents": 7500,
|
||||
"bank_balance_cents": null,
|
||||
"bank_balance_at": null,
|
||||
"bank_balance_vergleichbar": false
|
||||
}
|
||||
],
|
||||
"kategoriegruppen": [
|
||||
@@ -456,6 +477,136 @@
|
||||
}
|
||||
],
|
||||
"buchungen": [
|
||||
{
|
||||
"id": 10,
|
||||
"type": "income",
|
||||
"date": "2026-10-21",
|
||||
"time": null,
|
||||
"account_id": 3,
|
||||
"to_account_id": null,
|
||||
"account_currency": "EUR",
|
||||
"amount_cents": 7500,
|
||||
"to_amount_cents": null,
|
||||
"fx_rate": null,
|
||||
"amount_eur_cents": 7500,
|
||||
"amount_base_cents": 7500,
|
||||
"category_id": null,
|
||||
"budget_treatment": null,
|
||||
"budget_category_id": null,
|
||||
"subscription_id": null,
|
||||
"budget_period": "2026-10",
|
||||
"budget_period_manuell": false,
|
||||
"import_batch_id": null,
|
||||
"herkunft": "manuell",
|
||||
"payee": "Dauerauftrag",
|
||||
"description": null,
|
||||
"notes": null,
|
||||
"tag_ids": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "expense",
|
||||
"date": "2026-10-21",
|
||||
"time": null,
|
||||
"account_id": 1,
|
||||
"to_account_id": null,
|
||||
"account_currency": "EUR",
|
||||
"amount_cents": 7500,
|
||||
"to_amount_cents": null,
|
||||
"fx_rate": null,
|
||||
"amount_eur_cents": 7500,
|
||||
"amount_base_cents": 7500,
|
||||
"category_id": null,
|
||||
"budget_treatment": null,
|
||||
"budget_category_id": null,
|
||||
"subscription_id": null,
|
||||
"budget_period": "2026-10",
|
||||
"budget_period_manuell": false,
|
||||
"import_batch_id": null,
|
||||
"herkunft": "manuell",
|
||||
"payee": "Dauerauftrag",
|
||||
"description": null,
|
||||
"notes": null,
|
||||
"tag_ids": []
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "expense",
|
||||
"date": "2026-10-21",
|
||||
"time": null,
|
||||
"account_id": 1,
|
||||
"to_account_id": null,
|
||||
"account_currency": "EUR",
|
||||
"amount_cents": 1043,
|
||||
"to_amount_cents": null,
|
||||
"fx_rate": null,
|
||||
"amount_eur_cents": 1043,
|
||||
"amount_base_cents": 1043,
|
||||
"category_id": 1,
|
||||
"budget_treatment": null,
|
||||
"budget_category_id": null,
|
||||
"subscription_id": null,
|
||||
"budget_period": "2026-10",
|
||||
"budget_period_manuell": false,
|
||||
"import_batch_id": null,
|
||||
"herkunft": "manuell",
|
||||
"payee": "Kiosk",
|
||||
"description": null,
|
||||
"notes": null,
|
||||
"tag_ids": []
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "expense",
|
||||
"date": "2026-10-21",
|
||||
"time": null,
|
||||
"account_id": 1,
|
||||
"to_account_id": null,
|
||||
"account_currency": "EUR",
|
||||
"amount_cents": 1043,
|
||||
"to_amount_cents": null,
|
||||
"fx_rate": null,
|
||||
"amount_eur_cents": 1043,
|
||||
"amount_base_cents": 1043,
|
||||
"category_id": 1,
|
||||
"budget_treatment": null,
|
||||
"budget_category_id": null,
|
||||
"subscription_id": null,
|
||||
"budget_period": "2026-10",
|
||||
"budget_period_manuell": false,
|
||||
"import_batch_id": null,
|
||||
"herkunft": "manuell",
|
||||
"payee": "Kiosk",
|
||||
"description": null,
|
||||
"notes": null,
|
||||
"tag_ids": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "expense",
|
||||
"date": "2026-10-21",
|
||||
"time": null,
|
||||
"account_id": 1,
|
||||
"to_account_id": null,
|
||||
"account_currency": "EUR",
|
||||
"amount_cents": 1290,
|
||||
"to_amount_cents": null,
|
||||
"fx_rate": null,
|
||||
"amount_eur_cents": 1290,
|
||||
"amount_base_cents": 1290,
|
||||
"category_id": null,
|
||||
"budget_treatment": null,
|
||||
"budget_category_id": null,
|
||||
"subscription_id": null,
|
||||
"budget_period": "2026-10",
|
||||
"budget_period_manuell": false,
|
||||
"import_batch_id": null,
|
||||
"herkunft": "shortcut",
|
||||
"payee": "Baeckerei",
|
||||
"description": null,
|
||||
"notes": null,
|
||||
"tag_ids": []
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "income",
|
||||
@@ -620,7 +771,7 @@
|
||||
"icon": "landmark",
|
||||
"color": "#3f51b5",
|
||||
"planned_cents": 50000,
|
||||
"actual_cents": 4250,
|
||||
"actual_cents": 6336,
|
||||
"over": false,
|
||||
"good": false,
|
||||
"aus_vertraegen_cents": 108,
|
||||
@@ -631,8 +782,8 @@
|
||||
"icon": "home",
|
||||
"color": "#3f51b5",
|
||||
"planned_cents": 50000,
|
||||
"actual_cents": 4250,
|
||||
"delta_cents": 45750,
|
||||
"actual_cents": 6336,
|
||||
"delta_cents": 43664,
|
||||
"over": false,
|
||||
"modus": "fest",
|
||||
"zuschlag_cents": 0,
|
||||
@@ -958,7 +1109,7 @@
|
||||
},
|
||||
"totals": {
|
||||
"planned_expense": 50000,
|
||||
"actual_expense": 4250,
|
||||
"actual_expense": 6336,
|
||||
"planned_income": 0,
|
||||
"received_income": 0
|
||||
}
|
||||
@@ -1016,8 +1167,8 @@
|
||||
"icon": "wallet",
|
||||
"color": "#3f51b5",
|
||||
"currency": "EUR",
|
||||
"balance_cents": 430750,
|
||||
"balance_base_cents": 430750,
|
||||
"balance_cents": 419874,
|
||||
"balance_base_cents": 419874,
|
||||
"archived": false,
|
||||
"locked": false
|
||||
},
|
||||
@@ -1031,9 +1182,20 @@
|
||||
"balance_base_cents": 535000,
|
||||
"archived": false,
|
||||
"locked": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Zweitkonto",
|
||||
"icon": "wallet",
|
||||
"color": "#3f51b5",
|
||||
"currency": "EUR",
|
||||
"balance_cents": 7500,
|
||||
"balance_base_cents": 7500,
|
||||
"archived": false,
|
||||
"locked": false
|
||||
}
|
||||
],
|
||||
"total": 430750,
|
||||
"total": 427374,
|
||||
"gebunden": 535000,
|
||||
"currency": "EUR"
|
||||
},
|
||||
@@ -1043,7 +1205,7 @@
|
||||
"name": "Fixkosten",
|
||||
"color": "#3f51b5",
|
||||
"planned": 50000,
|
||||
"actual": 4250,
|
||||
"actual": 6336,
|
||||
"over": false,
|
||||
"good": false
|
||||
}
|
||||
@@ -1053,14 +1215,74 @@
|
||||
"group_by": "group"
|
||||
},
|
||||
"einnahmenAusgaben": {
|
||||
"income": 350000,
|
||||
"expense": 4250,
|
||||
"net": 345750,
|
||||
"income": 357500,
|
||||
"expense": 6336,
|
||||
"net": 351164,
|
||||
"currency": "EUR",
|
||||
"label": "2026-10"
|
||||
},
|
||||
"letzteBuchungen": {
|
||||
"transactions": [
|
||||
{
|
||||
"id": 10,
|
||||
"date": "2026-10-21",
|
||||
"type": "income",
|
||||
"title": "Dauerauftrag",
|
||||
"icon": "more-horizontal",
|
||||
"color": "#6b7480",
|
||||
"sign": "pos",
|
||||
"amount_cents": 7500,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 7500
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"date": "2026-10-21",
|
||||
"type": "expense",
|
||||
"title": "Dauerauftrag",
|
||||
"icon": "more-horizontal",
|
||||
"color": "#6b7480",
|
||||
"sign": "neg",
|
||||
"amount_cents": 7500,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 7500
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"date": "2026-10-21",
|
||||
"type": "expense",
|
||||
"title": "Wohnen",
|
||||
"icon": "home",
|
||||
"color": "#3f51b5",
|
||||
"sign": "neg",
|
||||
"amount_cents": 1043,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 1043
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"date": "2026-10-21",
|
||||
"type": "expense",
|
||||
"title": "Wohnen",
|
||||
"icon": "home",
|
||||
"color": "#3f51b5",
|
||||
"sign": "neg",
|
||||
"amount_cents": 1043,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 1043
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"date": "2026-10-21",
|
||||
"type": "expense",
|
||||
"title": "Baeckerei",
|
||||
"icon": "more-horizontal",
|
||||
"color": "#6b7480",
|
||||
"sign": "neg",
|
||||
"amount_cents": 1290,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 1290
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"date": "2026-10-21",
|
||||
@@ -1096,32 +1318,87 @@
|
||||
"amount_cents": 20000,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 20000
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"date": "2026-10-21",
|
||||
"type": "income",
|
||||
"title": "Gehalt",
|
||||
"icon": "banknote",
|
||||
"color": "#2e7d55",
|
||||
"sign": "pos",
|
||||
"amount_cents": 350000,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 350000
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"date": "2026-10-21",
|
||||
"type": "expense",
|
||||
"title": "Wohnen",
|
||||
"icon": "home",
|
||||
"color": "#3f51b5",
|
||||
"sign": "neg",
|
||||
"amount_cents": 4250,
|
||||
"currency": "EUR",
|
||||
"amount_base_cents": 4250
|
||||
}
|
||||
],
|
||||
"currency": "EUR"
|
||||
},
|
||||
"eingang": [
|
||||
{
|
||||
"id": 1,
|
||||
"source": "shortcut",
|
||||
"status": "booked",
|
||||
"account_id": 1,
|
||||
"date": "2026-10-21",
|
||||
"amount_cents": -1290,
|
||||
"currency": "EUR",
|
||||
"payee": "Baeckerei",
|
||||
"note": null,
|
||||
"category_id": null,
|
||||
"transaction_id": 6,
|
||||
"transfer_account_id": null,
|
||||
"fx_rate": null,
|
||||
"counter_amount_cents": null,
|
||||
"counter_currency": null,
|
||||
"created_at": "2026-10-21T19:21:28.451708"
|
||||
}
|
||||
],
|
||||
"dubletten": {
|
||||
"gruppen": [
|
||||
{
|
||||
"account_id": 1,
|
||||
"datum": "2026-10-21",
|
||||
"amount_cents": 1043,
|
||||
"typ": "expense",
|
||||
"zeilen": [
|
||||
{
|
||||
"id": 7,
|
||||
"payee": "Kiosk",
|
||||
"description": null,
|
||||
"category_id": 1,
|
||||
"herkunft": "manuell",
|
||||
"kennung": null
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"payee": "Kiosk",
|
||||
"description": null,
|
||||
"category_id": 1,
|
||||
"herkunft": "manuell",
|
||||
"kennung": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"uebertragsvorschlaege": {
|
||||
"verknuepft": 0,
|
||||
"vorschlaege": [
|
||||
{
|
||||
"a": {
|
||||
"id": 9,
|
||||
"date": "2026-10-21",
|
||||
"type": "expense",
|
||||
"amount_cents": 7500,
|
||||
"currency": "EUR",
|
||||
"account_id": 1,
|
||||
"account_name": "Girokonto",
|
||||
"payee": "Dauerauftrag",
|
||||
"description": null,
|
||||
"category_id": null
|
||||
},
|
||||
"b": {
|
||||
"id": 10,
|
||||
"date": "2026-10-21",
|
||||
"type": "income",
|
||||
"amount_cents": 7500,
|
||||
"currency": "EUR",
|
||||
"account_id": 3,
|
||||
"account_name": "Zweitkonto",
|
||||
"payee": "Dauerauftrag",
|
||||
"description": null,
|
||||
"category_id": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +503,9 @@ struct ServerantwortenTests {
|
||||
@Test("Konten, Kategorien und Schlagwörter")
|
||||
func stammdaten() throws {
|
||||
let konten = try lies([Konto].self, "konten")
|
||||
#expect(konten.count == 2)
|
||||
// Nicht auf eine genaue Zahl pruefen: Die Pruefdatei waechst mit
|
||||
// jeder Stufe, und ein Test, der dann rot wird, sagt nichts aus.
|
||||
#expect(konten.count >= 2)
|
||||
#expect(konten.contains { $0.isLocked })
|
||||
|
||||
let gruppen = try lies([Kategoriegruppe].self, "kategoriegruppen")
|
||||
@@ -515,7 +517,7 @@ struct ServerantwortenTests {
|
||||
@Test("Buchungen samt Tags, Übertrag und Vertragszuordnung")
|
||||
func buchungen() throws {
|
||||
let alle = try lies([Buchung].self, "buchungen")
|
||||
#expect(alle.count == 5)
|
||||
#expect(alle.count >= 5)
|
||||
// Jede Spielart einmal – sonst prüft die Datei die Felder nicht, die
|
||||
// nur in einem Fall belegt sind.
|
||||
#expect(alle.contains { !$0.tagIds.isEmpty })
|
||||
@@ -531,9 +533,12 @@ struct ServerantwortenTests {
|
||||
|
||||
let alle = try lies([Buchung].self, "buchungen")
|
||||
let zeilen = Buchungszeile.zeilen(aus: alle, paare: paare)
|
||||
// Fünf Buchungen, zwei davon ein Paar: vier Zeilen.
|
||||
#expect(zeilen.count == 4)
|
||||
#expect(zeilen.filter(\.istUebertrag).count == 2)
|
||||
// Die Paarliste fuehrt jede Verknuepfung zweimal auf, einmal je
|
||||
// Richtung - also halb so viele Paare wie Eintraege. Genau so viele
|
||||
// Zeilen fallen beim Zusammenfassen weg.
|
||||
#expect(zeilen.count == alle.count - paare.count / 2)
|
||||
let uebertraege = zeilen.filter(\.istUebertrag)
|
||||
#expect(!uebertraege.isEmpty)
|
||||
}
|
||||
|
||||
@Test("Budget-Monat, Vorlage und Verträge")
|
||||
@@ -557,7 +562,7 @@ struct ServerantwortenTests {
|
||||
@Test("Alle vier Dashboard-Karten")
|
||||
func dashboard() throws {
|
||||
let staende = try lies(Kontostaende.self, "kontostaende")
|
||||
#expect(staende.accounts.count == 2)
|
||||
#expect(staende.accounts.count >= 2)
|
||||
#expect(staende.gebunden != 0)
|
||||
|
||||
#expect(try !lies(Budgetfortschritt.self, "budgetfortschritt").rows.isEmpty)
|
||||
@@ -642,3 +647,100 @@ struct EingabenTests {
|
||||
#expect(Zahlungsrhythmus.allCases.map(\.rawValue) == [1, 2, 3, 6, 12])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Arbeitsliste
|
||||
|
||||
@Suite("Arbeitsliste")
|
||||
struct ArbeitslisteTests {
|
||||
|
||||
private func eintrag(stand: String, konto: Int?, kategorie: Int?,
|
||||
gegenkonto: Int? = nil) throws -> Eingangseintrag {
|
||||
let roh = """
|
||||
{"id": 1, "source": "shortcut", "status": "\(stand)",
|
||||
"account_id": \(konto.map(String.init) ?? "null"),
|
||||
"date": "2026-10-15", "amount_cents": -1290, "currency": "EUR",
|
||||
"payee": "Baeckerei", "note": null,
|
||||
"category_id": \(kategorie.map(String.init) ?? "null"),
|
||||
"transaction_id": null,
|
||||
"transfer_account_id": \(gegenkonto.map(String.init) ?? "null"),
|
||||
"fx_rate": null, "counter_amount_cents": null, "counter_currency": null,
|
||||
"created_at": "2026-10-15T14:18:15.780705"}
|
||||
"""
|
||||
return try JSONDecoder().decode(Eingangseintrag.self, from: Data(roh.utf8))
|
||||
}
|
||||
|
||||
@Test("Ohne Konto laesst sich nichts buchen")
|
||||
func ohneKontoKeinBuchen() throws {
|
||||
// Das Backend lehnt es mit 400 ab. Den Knopf trotzdem anzubieten
|
||||
// hiesse, dem Nutzer eine Fehlermeldung als Antwort zu geben.
|
||||
#expect(try eintrag(stand: "pending", konto: nil, kategorie: nil)
|
||||
.laesstSichBuchen == false)
|
||||
#expect(try eintrag(stand: "pending", konto: 3, kategorie: nil)
|
||||
.laesstSichBuchen)
|
||||
}
|
||||
|
||||
@Test("Was schon gebucht ist, steht nicht mehr zum Buchen an")
|
||||
func gebuchtesIstErledigt() throws {
|
||||
#expect(try eintrag(stand: "booked", konto: 3, kategorie: 7)
|
||||
.laesstSichBuchen == false)
|
||||
// Unklares schon: Es wartet auf einen Blick, nicht auf ein Konto.
|
||||
#expect(try eintrag(stand: "unclear", konto: 3, kategorie: nil)
|
||||
.laesstSichBuchen)
|
||||
}
|
||||
|
||||
@Test("Ein Uebertrag braucht keine Kategorie")
|
||||
func uebertragBrauchtKeineKategorie() throws {
|
||||
// Ohne transfer_account_id liesse sich ein Uebertrag nicht von einer
|
||||
// Ausgabe unterscheiden - und die App fragte ewig nach einer
|
||||
// Kategorie, die es nie geben wird.
|
||||
#expect(try eintrag(stand: "booked", konto: 3, kategorie: nil)
|
||||
.brauchtKategorie)
|
||||
#expect(try eintrag(stand: "booked", konto: 3, kategorie: nil, gegenkonto: 4)
|
||||
.brauchtKategorie == false)
|
||||
}
|
||||
|
||||
@Test("Ein Vorschlag ist dasselbe Paar, egal herum")
|
||||
func paarIstUngeordnet() throws {
|
||||
let roh = """
|
||||
{"verknuepft": 0, "vorschlaege": [
|
||||
{"a": {"id": 9, "date": "2026-10-15", "type": "expense",
|
||||
"amount_cents": 7500, "currency": "EUR", "account_id": 1,
|
||||
"account_name": "Giro", "payee": "Dauerauftrag",
|
||||
"description": null, "category_id": null},
|
||||
"b": {"id": 10, "date": "2026-10-15", "type": "income",
|
||||
"amount_cents": 7500, "currency": "EUR", "account_id": 3,
|
||||
"account_name": "Zweitkonto", "payee": "Dauerauftrag",
|
||||
"description": null, "category_id": 4}}]}
|
||||
"""
|
||||
let stand = try JSONDecoder().decode(Uebertragsvorschlaege.self,
|
||||
from: Data(roh.utf8))
|
||||
let paar = try #require(stand.vorschlaege.first)
|
||||
|
||||
// Die ID muss unabhaengig von der Reihenfolge sein, sonst zeigt die
|
||||
// Liste dasselbe Paar als zwei Eintraege.
|
||||
#expect(paar.id == "9-10")
|
||||
// Eine zugeordnete Buchung faellt durchs Verbinden aus dem Budget.
|
||||
#expect(paar.hatKategorie)
|
||||
}
|
||||
}
|
||||
|
||||
extension ServerantwortenTests {
|
||||
|
||||
@Test("Eingang, Dubletten und Uebertragsvorschlaege")
|
||||
func arbeitsliste() throws {
|
||||
let eingang = try lies([Eingangseintrag].self, "eingang")
|
||||
#expect(!eingang.isEmpty)
|
||||
#expect(eingang.allSatisfy { $0.currency.isEmpty == false })
|
||||
|
||||
let dubletten = try lies(Dublettengruppen.self, "dubletten")
|
||||
let gruppe = try #require(dubletten.gruppen.first)
|
||||
#expect(gruppe.zeilen.count >= 2)
|
||||
|
||||
let vorschlaege = try lies(Uebertragsvorschlaege.self, "uebertragsvorschlaege")
|
||||
#expect(vorschlaege.verknuepft == 0, "beim Fragen darf nichts verknuepft werden")
|
||||
#expect(vorschlaege.vorschlaege.count == 1)
|
||||
// Das Paar stand frueher zweimal drin, einmal je Richtung.
|
||||
let ids = Set(vorschlaege.vorschlaege.map(\.id))
|
||||
#expect(ids.count == vorschlaege.vorschlaege.count)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user