59d3dd06b6
test(supplier): SupplierCsvParserTest под 3-колоночный формат отчёта
Unit-тест ожидал устаревший 6-колоночный формат
vid;project;tag;phone;phones;time, тогда как SupplierCsvParser
переписан эпиком CSV-канала (T2, 18.05.2026) под 3-колоночный
Name;Tag;Phone — yields {project,tag,phone}, vid/time отсутствуют.
Тестовый долг вскрыт полной регрессией: 3 кейса падали
(«array has no key vid»). Тесты приведены к актуальному контракту
парапера. Pest SupplierCsvParserTest 5/5, full-suite 937/934/0/3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
82 lines
2.3 KiB
PHP
82 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Supplier\SupplierCsvParser;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class);
|
|
|
|
// Контракт парсера (эпик CSV-канал T2, 18.05.2026): отчёт «Запрос номеров»
|
|
// crm.bp-gr.ru — 3 колонки Name;Tag;Phone. vid и время в отчёте отсутствуют.
|
|
// SupplierCsvParser::parse() yields {project, tag, phone}. Spec §4.1.
|
|
|
|
beforeEach(function () {
|
|
$this->parser = new SupplierCsvParser;
|
|
});
|
|
|
|
it('parses empty CSV → yields nothing', function () {
|
|
$rows = iterator_to_array($this->parser->parse(''));
|
|
expect($rows)->toBeEmpty();
|
|
});
|
|
|
|
it('parses 1 row → yields 1 struct with project/tag/phone', function () {
|
|
$csv = "Name;Tag;Phone\n"
|
|
."B1_example.com;mytag;79991234567\n";
|
|
|
|
$rows = iterator_to_array($this->parser->parse($csv));
|
|
|
|
expect($rows)->toHaveCount(1);
|
|
expect($rows[0])->toMatchArray([
|
|
'project' => 'B1_example.com',
|
|
'tag' => 'mytag',
|
|
'phone' => '79991234567',
|
|
]);
|
|
});
|
|
|
|
it('parses 1000 rows without OOM (streaming generator)', function () {
|
|
$lines = ['Name;Tag;Phone'];
|
|
for ($i = 1; $i <= 1000; $i++) {
|
|
$lines[] = "B1_test.com;tag{$i};79991234567";
|
|
}
|
|
$csv = implode("\n", $lines)."\n";
|
|
|
|
$count = 0;
|
|
foreach ($this->parser->parse($csv) as $_) {
|
|
$count++;
|
|
}
|
|
|
|
expect($count)->toBe(1000);
|
|
});
|
|
|
|
it('skips malformed rows with missing columns + logs warning', function () {
|
|
Log::spy();
|
|
|
|
$csv = "Name;Tag;Phone\n"
|
|
."B1_example.com;mytag;79991234567\n"
|
|
."broken-row-only-one-column\n"
|
|
."B1_another.com;tag2;79991234500\n";
|
|
|
|
$rows = iterator_to_array($this->parser->parse($csv));
|
|
|
|
expect($rows)->toHaveCount(2);
|
|
expect($rows[0]['project'])->toBe('B1_example.com');
|
|
expect($rows[1]['project'])->toBe('B1_another.com');
|
|
|
|
Log::shouldHaveReceived('warning')
|
|
->with('supplier_csv_parser.malformed_row', Mockery::any())
|
|
->once();
|
|
});
|
|
|
|
it('handles BOM + CRLF line endings', function () {
|
|
$bom = "\xEF\xBB\xBF";
|
|
$csv = $bom."Name;Tag;Phone\r\n"
|
|
."B1_example.com;mytag;79991234567\r\n";
|
|
|
|
$rows = iterator_to_array($this->parser->parse($csv));
|
|
|
|
expect($rows)->toHaveCount(1);
|
|
expect($rows[0]['project'])->toBe('B1_example.com');
|
|
});
|