Files
portal/app/tests/Unit/Import/CsvLeadsParserTest.php
T
Дмитрий 8f2b82405a feat(import): CsvLeadsParser + DTO ParsedLeadRow/CsvParseResult
Парсер CSV-выгрузки лидов crm.bp-gr.ru (ТЗ §6.2/§6.3): срезает UTF-8 BOM,
разбирает строки через str_getcsv, валидирует телефон (7XXXXXXXXXX) и даты
(Y/m/d H:i:s), срезает префикс B[123]_ из названия проекта. Невалидные
строки не роняют парсинг — собираются в errors[] с абсолютным номером строки.
Тесты: 5/5 (unit, без DB).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:47:15 +03:00

71 lines
2.9 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\Import\CsvLeadsParser;
function csvLeads(string $body, bool $withBom = true): string
{
$header = 'id,Проект,Тег проекта,Телефон,Создано,Напоминание,Комментарий,Состояние,Имя';
return ($withBom ? "\xEF\xBB\xBF" : '').$header."\n".$body;
}
test('парсит валидную строку в ParsedLeadRow', function (): void {
$result = (new CsvLeadsParser)->parse(csvLeads(
'1001,B1_Окна,окна,79161234567,2024/03/15 10:30:00,,Тёплый клиент,Переговоры,Иван'
));
expect($result->rows)->toHaveCount(1)
->and($result->errors)->toBeEmpty();
$row = $result->rows[0];
expect($row->sourceCrmId)->toBe(1001)
->and($row->projectName)->toBe('Окна') // префикс B1_ срезан
->and($row->projectTag)->toBe('окна')
->and($row->phone)->toBe('79161234567')
->and($row->statusRu)->toBe('Переговоры')
->and($row->contactName)->toBe('Иван')
->and($row->reminderAt)->toBeNull()
->and($row->receivedAt->format('Y-m-d H:i:s'))->toBe('2024-03-15 10:30:00');
});
test('срезает BOM и не считает заголовок строкой данных', function (): void {
$result = (new CsvLeadsParser)->parse(csvLeads(
'1,Проект,тег,79990001122,2024/01/01 00:00:00,,,Новые,'
));
expect($result->rows)->toHaveCount(1)
->and($result->rows[0]->sourceCrmId)->toBe(1);
});
test('парсит напоминание когда оно непустое', function (): void {
$result = (new CsvLeadsParser)->parse(csvLeads(
'2,П,т,79990001122,2024/01/01 09:00:00,2024/01/05 12:00:00,,Новые,'
));
expect($result->rows[0]->reminderAt?->format('Y-m-d H:i:s'))->toBe('2024-01-05 12:00:00');
});
test('собирает ошибки невалидного телефона и даты, не роняя парсинг', function (): void {
$result = (new CsvLeadsParser)->parse(csvLeads(
"3,П,т,8916123,2024/01/01 00:00:00,,,Новые,\n".
"4,П,т,79990001122,НЕ-ДАТА,,,Новые,\n".
'5,П,т,79990001133,2024/01/02 00:00:00,,,Новые,'
));
expect($result->rows)->toHaveCount(1) // только строка 5 валидна
->and($result->rows[0]->sourceCrmId)->toBe(5)
->and($result->errors)->toHaveCount(2);
expect($result->errors[0]['line'])->toBe(2); // 1-я data-строка (после header)
});
test('обрабатывает кавычки и запятые внутри поля', function (): void {
$result = (new CsvLeadsParser)->parse(csvLeads(
'6,П,т,79990001122,2024/01/01 00:00:00,,"Комментарий, с запятой",Новые,Пётр'
));
expect($result->rows[0]->comment)->toBe('Комментарий, с запятой');
});