assertSame( ['month' => 6, 'year' => 2026], fscash_normalize_month_year(6, 2026) ); } public function testNormalizeMonthYearSpringtBeiUnterschreitungInsVorjahr(): void { $this->assertSame( ['month' => 12, 'year' => 2025], fscash_normalize_month_year(0, 2026) ); } public function testNormalizeMonthYearSpringtBeiUeberschreitungInsFolgejahr(): void { $this->assertSame( ['month' => 1, 'year' => 2027], fscash_normalize_month_year(13, 2026) ); } public function testNormalizeMonthYearGrenzwerteBleibenUnveraendert(): void { $this->assertSame(['month' => 1, 'year' => 2026], fscash_normalize_month_year(1, 2026)); $this->assertSame(['month' => 12, 'year' => 2026], fscash_normalize_month_year(12, 2026)); } // --- fscash_user_roles -------------------------------------------- public function testUserRolesErkenntAdminUndAktienUser(): void { $this->assertSame( ['is_admin' => true, 'is_aktien' => true], fscash_user_roles(1, [1, 2], [1, 2]) ); } public function testUserRolesErkenntNormalenUserOhneRechte(): void { $this->assertSame( ['is_admin' => false, 'is_aktien' => false], fscash_user_roles(99, [1, 2], [1, 2]) ); } public function testUserRolesVergleichtStriktTypisiert(): void { // "1" (string) darf ein int 1 in der Liste nicht faelschlich matchen, // da fscash_user_roles ausschliesslich mit int-typisierten IDs arbeitet // und in_array(..., true) strikt vergleicht. $this->assertSame( ['is_admin' => true, 'is_aktien' => false], fscash_user_roles(2, [1, 2], [1]) ); } // --- fscash_aktie_guv ------------------------------------------------ public function testAktieGuvBerechnetGewinn(): void { $result = fscash_aktie_guv(100.0, 10.0, 120.0); $this->assertSame(1000.0, $result['kaufSumme']); $this->assertSame(1200.0, $result['aktuellerWert']); $this->assertSame(200.0, $result['guv']); $this->assertSame('guv-positive', $result['guvClass']); } public function testAktieGuvBerechnetVerlust(): void { $result = fscash_aktie_guv(100.0, 10.0, 80.0); $this->assertSame(-200.0, $result['guv']); $this->assertSame('guv-negative', $result['guvClass']); } public function testAktieGuvBeiGleichbleibendemKursOhneKlasse(): void { $result = fscash_aktie_guv(50.0, 4.0, 50.0); $this->assertSame(0.0, $result['guv']); $this->assertSame('', $result['guvClass']); } public function testAktieGuvOhneKursErgibtNegativenGuvGleichKaufsumme(): void { // kurs = 0 (noch keine Kursaktualisierung erfolgt) $result = fscash_aktie_guv(100.0, 3.0, 0.0); $this->assertSame(300.0, $result['kaufSumme']); $this->assertSame(0.0, $result['aktuellerWert']); $this->assertSame(-300.0, $result['guv']); $this->assertSame('guv-negative', $result['guvClass']); } // --- fscash_select_figi_symbol -------------------------------------- public function testSelectFigiSymbolBevorzugtXetraEintrag(): void { $entries = [ ['ticker' => 'FOO', 'exchCode' => 'US'], ['ticker' => 'BAR', 'exchCode' => 'XETR'], ]; $this->assertSame('BAR.XETR', fscash_select_figi_symbol($entries)); } public function testSelectFigiSymbolNimmtErstenEintragOhneXetra(): void { $entries = [ ['ticker' => 'FOO', 'exchCode' => 'US'], ['ticker' => 'BAZ', 'exchCode' => 'LSE'], ]; $this->assertSame('FOO.US', fscash_select_figi_symbol($entries)); } public function testSelectFigiSymbolOhneExchCode(): void { $entries = [ ['ticker' => 'FOO'], ]; $this->assertSame('FOO', fscash_select_figi_symbol($entries)); } public function testSelectFigiSymbolBeiLeeremTickerLiefertNull(): void { $entries = [ ['ticker' => '', 'exchCode' => 'US'], ]; $this->assertNull(fscash_select_figi_symbol($entries)); } public function testSelectFigiSymbolBeiLeererListeLiefertNull(): void { $this->assertNull(fscash_select_figi_symbol([])); } // --- fscash_resolve_selected_categories ------------------------------ public function testResolveSelectedCategoriesMitAuswahl(): void { $this->assertSame( [2, 3], fscash_resolve_selected_categories([1, 2, 3, 4], [2, 3]) ); } public function testResolveSelectedCategoriesOhneAuswahlLiefertAlle(): void { $this->assertSame( [1, 2, 3, 4], fscash_resolve_selected_categories([1, 2, 3, 4], null) ); } public function testResolveSelectedCategoriesBeiLeeremArrayLiefertAlle(): void { // Ein leeres categories[]-Array (z.B. alle Haken entfernt) fällt // bewusst auf "alle Kategorien" zurück, siehe rep-person.php. $this->assertSame( [1, 2, 3, 4], fscash_resolve_selected_categories([1, 2, 3, 4], []) ); } // --- fscash_resolve_selected_payment_methods -------------------------- public function testResolveSelectedPaymentMethodsMitAuswahl(): void { $this->assertSame( [2, 3], fscash_resolve_selected_payment_methods([1, 2, 3, 4], [2, 3]) ); } public function testResolveSelectedPaymentMethodsOhneAuswahlLiefertAlle(): void { $this->assertSame( [1, 2, 3, 4], fscash_resolve_selected_payment_methods([1, 2, 3, 4], null) ); } public function testResolveSelectedPaymentMethodsBeiLeeremArrayLiefertAlle(): void { $this->assertSame( [1, 2, 3, 4], fscash_resolve_selected_payment_methods([1, 2, 3, 4], []) ); } // --- fscash_extract_quote_price -------------------------------------- public function testExtractQuotePriceLiestPreisAus(): void { $response = ['Global Quote' => ['05. price' => '123.4500']]; $this->assertSame(123.45, fscash_extract_quote_price($response)); } public function testExtractQuotePriceBeiFehlendemFeldLiefertNull(): void { $this->assertNull(fscash_extract_quote_price(['Global Quote' => []])); $this->assertNull(fscash_extract_quote_price([])); } public function testExtractQuotePriceBeiLeeremStringLiefertNull(): void { $this->assertNull(fscash_extract_quote_price(['Global Quote' => ['05. price' => '']])); } // --- fscash_map_symbol_search_results --------------------------------- public function testMapSymbolSearchResultsWandeltAlphaVantageFormatUm(): void { $response = [ 'bestMatches' => [ ['1. symbol' => 'SAP.DE', '2. name' => 'SAP SE', '4. region' => 'Frankfurt'], ], ]; $this->assertSame( [['symbol' => 'SAP.DE', 'name' => 'SAP SE', 'region' => 'Frankfurt']], fscash_map_symbol_search_results($response) ); } public function testMapSymbolSearchResultsOhneTrefferLiefertLeeresArray(): void { $this->assertSame([], fscash_map_symbol_search_results([])); $this->assertSame([], fscash_map_symbol_search_results(['bestMatches' => []])); } // --- fscash_is_valid_symbol_payload ----------------------------------- public function testIsValidSymbolPayloadMitGueltigenWerten(): void { $this->assertTrue(fscash_is_valid_symbol_payload(42, 'SAP.DE')); $this->assertTrue(fscash_is_valid_symbol_payload('42', 'SAP.DE')); } public function testIsValidSymbolPayloadOhneIdOderSymbolIstUngueltig(): void { $this->assertFalse(fscash_is_valid_symbol_payload(null, 'SAP.DE')); $this->assertFalse(fscash_is_valid_symbol_payload(42, null)); $this->assertFalse(fscash_is_valid_symbol_payload(0, 'SAP.DE')); $this->assertFalse(fscash_is_valid_symbol_payload(42, '')); } // --- fscash_format_report_month_csv_row -------------------------------- public function testFormatReportMonthCsvRow(): void { $entry = [ 'id' => 5, 'datum' => '2026-07-01', 'benutzer_name' => 'Alice', 'kategorie_id' => 3, 'kategorie_name' => 'Einkaufen', 'betreff' => 'Wocheneinkauf', 'betrag' => 1234.5, 'zahlungsmethode' => 'Karte', ]; $this->assertSame( ['5', '2026-07-01', 'Alice', '3', 'Einkaufen', 'Wocheneinkauf', '1.234,50', 'Karte'], fscash_format_report_month_csv_row($entry) ); } public function testFormatReportMonthCsvRowMitFehlendenFeldern(): void { $this->assertSame( ['', '', '', '', '', '', '0,00', ''], fscash_format_report_month_csv_row([]) ); } // --- fscash_pending_migrations ------------------------------------------ public function testPendingMigrationsFiltertBereitsAngewendete(): void { $available = ['/m/0002_add_col.sql', '/m/0001_init.sql', '/m/0003_index.sql']; $applied = ['0001_init.sql']; $this->assertSame( ['/m/0002_add_col.sql', '/m/0003_index.sql'], fscash_pending_migrations($available, $applied) ); } public function testPendingMigrationsSortiertNumerischAlsString(): void { $available = ['/m/0010_b.sql', '/m/0002_a.sql']; $this->assertSame( ['/m/0002_a.sql', '/m/0010_b.sql'], fscash_pending_migrations($available, []) ); } public function testPendingMigrationsBeiAllenAngewendetLiefertLeeresArray(): void { $available = ['/m/0001_init.sql']; $applied = ['0001_init.sql']; $this->assertSame([], fscash_pending_migrations($available, $applied)); } // --- fscash_parse_basic_auth_header -------------------------------- public function testParseBasicAuthHeaderMitGueltigenZugangsdaten(): void { $header = 'Basic ' . base64_encode('finanzmanager:geheim123'); $this->assertSame( ['username' => 'finanzmanager', 'password' => 'geheim123'], fscash_parse_basic_auth_header($header) ); } public function testParseBasicAuthHeaderErlaubtDoppelpunktImPasswort(): void { $header = 'Basic ' . base64_encode('user:pass:mit:doppelpunkt'); $this->assertSame( ['username' => 'user', 'password' => 'pass:mit:doppelpunkt'], fscash_parse_basic_auth_header($header) ); } public function testParseBasicAuthHeaderOhneHeaderLiefertNull(): void { $this->assertNull(fscash_parse_basic_auth_header(null)); } public function testParseBasicAuthHeaderOhneBasicSchemaLiefertNull(): void { $this->assertNull(fscash_parse_basic_auth_header('Bearer irgendwas')); } public function testParseBasicAuthHeaderMitUngueltigemBase64LiefertNull(): void { $this->assertNull(fscash_parse_basic_auth_header('Basic ###nicht-base64###')); } public function testParseBasicAuthHeaderOhneDoppelpunktLiefertNull(): void { $this->assertNull(fscash_parse_basic_auth_header('Basic ' . base64_encode('ohnedoppelpunkt'))); } // --- fscash_is_valid_date ------------------------------------------- public function testIsValidDateMitGueltigemDatum(): void { $this->assertTrue(fscash_is_valid_date('2026-08-19')); $this->assertTrue(fscash_is_valid_date('2024-02-29')); } public function testIsValidDateMitUngueltigemDatumOderFormat(): void { $this->assertFalse(fscash_is_valid_date('2026-02-30')); $this->assertFalse(fscash_is_valid_date('2023-02-29')); $this->assertFalse(fscash_is_valid_date('19.08.2026')); $this->assertFalse(fscash_is_valid_date('2026-8-19')); $this->assertFalse(fscash_is_valid_date('')); } // --- fscash_validate_buchungen_query --------------------------------- public function testValidateBuchungenQueryMitPflichtfeldern(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-09', 'bis' => '2026-08-19', 'betrag' => '-12.50', ]); $this->assertNull($result['error']); $this->assertSame('2026-08-09', $result['von']); $this->assertSame('2026-08-19', $result['bis']); $this->assertSame(-12.50, $result['betrag']); $this->assertSame(0.0, $result['toleranz']); $this->assertSame('EUR', $result['waehrung']); $this->assertSame(20, $result['limit']); } public function testValidateBuchungenQueryMitAllenOptionalenParametern(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-09', 'bis' => '2026-08-19', 'betrag' => '-12.50', 'toleranz' => '0.50', 'waehrung' => 'eur', 'limit' => '5', ]); $this->assertNull($result['error']); $this->assertSame(0.50, $result['toleranz']); $this->assertSame('EUR', $result['waehrung']); $this->assertSame(5, $result['limit']); } public function testValidateBuchungenQueryOhneVonLiefertFehler(): void { $result = fscash_validate_buchungen_query(['bis' => '2026-08-19', 'betrag' => '-12.50']); $this->assertSame("Parameter 'von' fehlt.", $result['error']); } public function testValidateBuchungenQueryOhneBisLiefertFehler(): void { $result = fscash_validate_buchungen_query(['von' => '2026-08-09', 'betrag' => '-12.50']); $this->assertSame("Parameter 'bis' fehlt.", $result['error']); } public function testValidateBuchungenQueryMitUngueltigemDatumLiefertFehler(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-32', 'bis' => '2026-08-19', 'betrag' => '-12.50', ]); $this->assertSame("Parameter 'von' ist unbrauchbar.", $result['error']); } public function testValidateBuchungenQueryMitVonNachBisLiefertFehler(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-19', 'bis' => '2026-08-09', 'betrag' => '-12.50', ]); $this->assertSame("Parameter 'von' darf nicht nach 'bis' liegen.", $result['error']); } public function testValidateBuchungenQueryOhneBetragLiefertFehler(): void { $result = fscash_validate_buchungen_query(['von' => '2026-08-09', 'bis' => '2026-08-19']); $this->assertSame("Parameter 'betrag' fehlt.", $result['error']); } public function testValidateBuchungenQueryMitUnbrauchbaremBetragLiefertFehler(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-09', 'bis' => '2026-08-19', 'betrag' => 'zwölffünfzig', ]); $this->assertSame("Parameter 'betrag' ist unbrauchbar.", $result['error']); } public function testValidateBuchungenQueryMitNegativerToleranzLiefertFehler(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-09', 'bis' => '2026-08-19', 'betrag' => '-12.50', 'toleranz' => '-1', ]); $this->assertSame("Parameter 'toleranz' ist unbrauchbar.", $result['error']); } public function testValidateBuchungenQueryMitUngueltigerWaehrungLiefertFehler(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-09', 'bis' => '2026-08-19', 'betrag' => '-12.50', 'waehrung' => 'EURO', ]); $this->assertSame("Parameter 'waehrung' ist unbrauchbar.", $result['error']); } public function testValidateBuchungenQueryMitUngueltigemLimitLiefertFehler(): void { $result = fscash_validate_buchungen_query([ 'von' => '2026-08-09', 'bis' => '2026-08-19', 'betrag' => '-12.50', 'limit' => '0', ]); $this->assertSame("Parameter 'limit' ist unbrauchbar.", $result['error']); } // --- fscash_signed_betrag --------------------------------------------- public function testSignedBetragDrehtVorzeichenVonGespeicherterAusgabe(): void { $this->assertSame(-12.50, fscash_signed_betrag(12.50)); $this->assertSame(0.0, fscash_signed_betrag(0.0)); } // --- fscash_format_buchung --------------------------------------------- public function testFormatBuchungMitAllenFeldern(): void { $row = [ 'id' => 42, 'datum' => '2026-08-14', 'betrag' => 12.50, 'zweck' => 'Wocheneinkauf', 'kategorie' => 'Lebensmittel', 'konto' => 'Portemonnaie', ]; $this->assertSame( [ 'id' => '42', 'datum' => '2026-08-14', 'betrag' => -12.50, 'waehrung' => 'EUR', 'zweck' => 'Wocheneinkauf', 'kategorie' => 'Lebensmittel', 'konto' => 'Portemonnaie', ], fscash_format_buchung($row) ); } public function testFormatBuchungOhneKategorieUndKontoLaesstFelderWeg(): void { $row = [ 'id' => 42, 'datum' => '2026-08-14', 'betrag' => 12.50, 'zweck' => 'Wocheneinkauf', 'kategorie' => null, 'konto' => null, ]; $this->assertSame( [ 'id' => '42', 'datum' => '2026-08-14', 'betrag' => -12.50, 'waehrung' => 'EUR', 'zweck' => 'Wocheneinkauf', ], fscash_format_buchung($row) ); } // --- fscash_api_error --------------------------------------------------- public function testApiErrorBautFehlerobjekt(): void { $this->assertSame(['fehler' => 'Parameter fehlt.'], fscash_api_error('Parameter fehlt.')); } }