tenant = Tenant::factory()->create(); $this->project = Project::factory()->create(['tenant_id' => $this->tenant->id, 'name' => 'Окна Москва']); }); /** ReportJob без сохранения — провайдер читает только tenant_id + parameters. */ function dealsExportJob(int $tenantId): ReportJob { return new ReportJob([ 'tenant_id' => $tenantId, 'type' => 'deals_export', 'parameters' => [ 'format' => 'csv', 'date_from' => Carbon::now()->startOfMonth()->toDateString(), 'date_to' => Carbon::now()->endOfMonth()->toDateString(), ], ]); } test('headers: 8 колонок', function () { expect((new DealsExportProvider)->headers()) ->toBe(['ID', 'Телефон', 'Контакт', 'Статус', 'Проект', 'Менеджер', 'Стоимость (₽)', 'Получено']); }); test('отдаёт СЫРЫЕ данные — нейтрализация формул в форматтерах, не в провайдере (F-CSV)', function () { DB::table('deals')->insert([ 'tenant_id' => $this->tenant->id, 'project_id' => $this->project->id, 'phone' => '+79991234567', 'contact_name' => '=HYPERLINK("http://evil")', 'status' => 'new', 'received_at' => Carbon::now()->startOfMonth()->addDays(5), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]); DB::table('projects')->where('id', $this->project->id)->update(['name' => '@SUM(1+1)']); $rows = (new DealsExportProvider)->rows(dealsExportJob($this->tenant->id)); expect($rows)->toHaveCount(1); // Провайдер НЕ префиксует апострофом — отдаёт как есть (чтобы JSON-формат не // портился). Нейтрализацию делают CsvFormatter/XlsxFormatter (свои тесты). expect($rows[0][2])->toBe('=HYPERLINK("http://evil")'); expect($rows[0][1])->toBe('+79991234567'); expect($rows[0][4])->toBe('@SUM(1+1)'); expect($rows[0][0])->toBeInt(); });