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('Комментарий, с запятой'); });