calledPaths[] = [ 'path' => $path, 'query' => $query ]; if ( !isset( $this -> responses[ $path ] ) ) return [ 'http_code' => 200, 'body' => '[]' ]; return $this -> responses[ $path ]; } } function run_fakturownia_api_client_tests() { // AC-1: /invoices.json?income=no jest pytane jako primary source, // pusta odpowiedz /costs.json NIE konczy wczesniej i faktura 486639934 z /invoices.json trafia do wynikow. $client = new FakeFakturowniaApiClient( 'https://example', 'token', 100 ); $client -> responses = [ '/invoices.json' => [ 'http_code' => 200, 'body' => json_encode( [ 'invoices' => [ [ 'id' => 486639934, 'number' => 'FV 100/04/2026', 'kind' => 'vat', 'income' => false, 'issue_date' => '2026-04-05', 'seller_name' => 'Dostawca Sp. z o.o.', 'seller_tax_no' => '5252344567' ] ] ] ) ], '/costs.json' => [ 'http_code' => 200, 'body' => '[]' ], '/expenses.json' => [ 'http_code' => 404, 'body' => '' ] ]; $result = $client -> fetchCostDocuments( '2026-04-01', 1 ); assert_true( count( $result ) === 1, 'Expected faktura 486639934 to be returned from /invoices.json?income=no even when /costs.json is empty.' ); assert_true( (string)$result[0]['id'] === '486639934', 'Expected invoice id 486639934 in merged cost documents.' ); $paths = array_map( function( $c ) { return $c['path']; }, $client -> calledPaths ); assert_true( in_array( '/invoices.json', $paths, true ), 'Expected /invoices.json to be queried for cost documents.' ); assert_true( in_array( '/costs.json', $paths, true ), 'Expected /costs.json to be queried as secondary source.' ); assert_true( in_array( '/expenses.json', $paths, true ), 'Expected /expenses.json to be queried as tertiary source (bug 05-04: empty /costs.json must not short-circuit).' ); // AC-2: period=more + date_from + date_to sa wysylane, by API filtrowalo po issue_date. // Bez tego importer pobieralby wszystkie historyczne faktury i cron by sie zawiesil. $invoicesCall = null; foreach ( $client -> calledPaths as $call ) { if ( $call['path'] === '/invoices.json' ) { $invoicesCall = $call; break; } } assert_true( $invoicesCall !== null, 'Expected at least one call to /invoices.json.' ); assert_true( ( $invoicesCall['query']['period'] ?? '' ) === 'more', 'Expected period=more parameter so Fakturownia filters by date range server-side.' ); assert_true( ( $invoicesCall['query']['date_from'] ?? '' ) === '2026-04-01', 'Expected date_from to match startDate passed to fetchCostDocuments.' ); assert_true( !empty( $invoicesCall['query']['date_to'] ?? '' ), 'Expected date_to to be set (default: today) to complete the range filter.' ); assert_true( ( $invoicesCall['query']['income'] ?? '' ) === 'no', 'Expected income=no filter on /invoices.json for cost documents.' ); // Dedup: ten sam id w /invoices.json i /costs.json trafia raz. $dedupClient = new FakeFakturowniaApiClient( 'https://example', 'token', 100 ); $dedupClient -> responses = [ '/invoices.json' => [ 'http_code' => 200, 'body' => json_encode( [ 'invoices' => [ [ 'id' => 111, 'number' => 'A', 'income' => false ], [ 'id' => 222, 'number' => 'B', 'income' => false ] ] ] ) ], '/costs.json' => [ 'http_code' => 200, 'body' => json_encode( [ 'costs' => [ [ 'id' => 222, 'number' => 'B' ], [ 'id' => 333, 'number' => 'C' ] ] ] ) ] ]; $merged = $dedupClient -> fetchCostDocuments( '2026-04-01', 1 ); $ids = array_map( function( $d ) { return (string)$d['id']; }, $merged ); sort( $ids ); assert_true( $ids === [ '111', '222', '333' ], 'Expected merged cost documents to be deduplicated by id across endpoints.' ); // Gdy wszystkie 3 sciezki zwroca HTTP >= 400, fetchCostDocuments rzuca wyjatek. $errorClient = new FakeFakturowniaApiClient( 'https://example', 'token', 100 ); $errorClient -> responses = [ '/invoices.json' => [ 'http_code' => 500, 'body' => '' ], '/costs.json' => [ 'http_code' => 500, 'body' => '' ], '/expenses.json' => [ 'http_code' => 500, 'body' => '' ] ]; $threw = false; try { $errorClient -> fetchCostDocuments( '2026-04-01', 1 ); } catch ( \Throwable $e ) { $threw = true; } assert_true( $threw, 'Expected fetchCostDocuments to throw when all endpoints fail with HTTP errors.' ); // Gdy wszystkie 3 sciezki zwroca pusta liste z HTTP 200, zwracamy [] (nie wyjatek). $emptyClient = new FakeFakturowniaApiClient( 'https://example', 'token', 100 ); $emptyClient -> responses = [ '/invoices.json' => [ 'http_code' => 200, 'body' => '[]' ], '/costs.json' => [ 'http_code' => 200, 'body' => '[]' ], '/expenses.json' => [ 'http_code' => 200, 'body' => '[]' ] ]; $emptyResult = $emptyClient -> fetchCostDocuments( '2026-04-01', 1 ); assert_true( is_array( $emptyResult ) && count( $emptyResult ) === 0, 'Expected empty array when all endpoints return HTTP 200 with empty lists.' ); }