Files
portal/app/tests/Unit/Supplier/SupplierPortalClientCsvTest.php
T
Дмитрий cb86065588 feat(supplier): Plan 4 Task 7 — SupplierCsvParser (streaming) + SupplierPortalClient::downloadLeadsCsv
SupplierCsvParser — pure streaming-generator CSV parser:
- BOM (UTF-8 EF BB BF) + CRLF normalization
- Malformed rows (< 6 columns) skipped + Log::warning
- 5 unit tests: empty / 1 row / 1000 rows / malformed / BOM+CRLF

SupplierPortalClient::downloadLeadsCsv(CarbonInterface, CarbonInterface):
- GET /admin/report/index?type=49 через существующий request() helper
- Наследует auth/retry семантику (401 → RefreshSession, 5xx → Transient, 4xx → Client)
- 3 unit tests через Http::fake: 200 / 401 retry / 500

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:50:11 +03:00

79 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
use App\Exceptions\Supplier\SupplierTransientException;
use App\Jobs\Supplier\RefreshSupplierSessionJob;
use App\Services\Supplier\SupplierPortalClient;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
uses(TestCase::class);
beforeEach(function () {
Cache::store('redis')->put('supplier:session', [
'phpsessid' => 'test-session', 'csrf' => 'test-csrf-token',
], now()->addHour());
config(['services.supplier.portal_url' => 'https://crm.bp-gr.ru']);
});
it('GET /admin/report/index?type=49 returns CSV body on 200', function () {
Http::fake([
'crm.bp-gr.ru/admin/report/index*' => Http::response(
"vid;project;tag;phone;phones;time\n1234;B1_example.com;;79991234567;79991234567;1715432400\n",
200,
['Content-Type' => 'text/csv'],
),
]);
$client = new SupplierPortalClient(app(HttpFactory::class));
$body = $client->downloadLeadsCsv(
Carbon::parse('2024-05-11 00:00:00'),
Carbon::parse('2024-05-12 00:00:00'),
);
expect($body)->toContain('1234;B1_example.com');
});
it('401 → triggers session refresh → retry → 200', function () {
Http::fakeSequence('crm.bp-gr.ru/admin/report/index*')
->push('Unauthorized', 401)
->push("vid;...\n", 200);
// RefreshSupplierSessionJob — мокаем
app()->bind(RefreshSupplierSessionJob::class, function () {
return new class
{
public function handle(): void
{
Cache::store('redis')->put('supplier:session', [
'phpsessid' => 'refreshed', 'csrf' => 'refreshed-csrf',
], now()->addHour());
}
};
});
$client = new SupplierPortalClient(app(HttpFactory::class));
$body = $client->downloadLeadsCsv(
Carbon::parse('2024-05-11'),
Carbon::parse('2024-05-12'),
);
expect($body)->toContain('vid');
});
it('500 → SupplierTransientException', function () {
Http::fake(['crm.bp-gr.ru/*' => Http::response('Internal Server Error', 500)]);
$client = new SupplierPortalClient(app(HttpFactory::class));
expect(fn () => $client->downloadLeadsCsv(
Carbon::parse('2024-05-11'),
Carbon::parse('2024-05-12'),
))->toThrow(SupplierTransientException::class);
});