tempFiles as $path) { if (file_exists($path)) { unlink($path); } } parent::tearDown(); } // ------------------------------------------------------------------------- // Helper: create xlsx file with reclamation headers and optional data rows, // upload it to Storage::disk('upload') and return the Import model. // ------------------------------------------------------------------------- private function createImportWithFile(array $dataRows = [], ?array $customHeaders = null): Import { $import = Import::factory()->create([ 'type' => 'reclamations', 'filename' => 'test_reclamations_' . uniqid() . '.xlsx', 'status' => 'new', ]); $tempPath = $this->buildXlsxFile($customHeaders ?? array_keys(ImportReclamationsService::HEADERS), $dataRows); Storage::fake('upload'); Storage::disk('upload')->put($import->filename, file_get_contents($tempPath)); return $import; } private function buildXlsxFile(array $headers, array $dataRows): string { $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // Write headers in row 1 foreach ($headers as $colIdx => $header) { $sheet->setCellValue(chr(65 + $colIdx) . '1', $header); } // Write data rows starting from row 2 foreach ($dataRows as $rowIdx => $row) { foreach ($row as $colIdx => $value) { $sheet->setCellValue(chr(65 + $colIdx) . ($rowIdx + 2), $value); } } $path = sys_get_temp_dir() . '/test_reclamations_' . uniqid() . '.xlsx'; (new XlsxWriter($spreadsheet))->save($path); $this->tempFiles[] = $path; return $path; } // ------------------------------------------------------------------------- // Tests // ------------------------------------------------------------------------- /** * When the file does not exist in the upload disk, prepare() should fail * and handle() must return false. */ public function test_handle_returns_false_when_file_not_found(): void { Storage::fake('upload'); $import = Import::factory()->create([ 'type' => 'reclamations', 'filename' => 'nonexistent_file_' . uniqid() . '.xlsx', 'status' => 'new', ]); $service = new ImportReclamationsService($import); $result = $service->handle(); $this->assertFalse($result); } /** * A file that exists but has wrong column headers must cause prepare() to * throw "Invalid headers", so handle() returns false. */ public function test_handle_returns_false_with_wrong_headers(): void { $import = $this->createImportWithFile([], ['Wrong', 'Headers', 'Here']); $service = new ImportReclamationsService($import); $result = $service->handle(); $this->assertFalse($result); } /** * Correct headers, one data row, but the status name in the row does not * match any row in reclamation_statuses → the row is skipped with * status_not_found, but handle() still returns true (processing completes). */ public function test_handle_returns_false_when_status_not_found(): void { $import = $this->createImportWithFile([ [ 'Округ ЦАО', // Округ 'Тверской', // Район 'ул. Тестовая, 1', // Адрес 'ART-001', // Артикул '1.01.01', // Тип 'RFID-UNKNOWN-999', // RFID 'Нет', // Гарантии 'Покраска', // Что сделано 45000, // Дата создания (excel serial) 45001, // Дата начала работ 45002, // Дата завершения работ 2024, // Год поставки МАФ 'Вандализм', // Причина 'НесуществующийСтатус', // Статус '', // Комментарий ], ]); $service = new ImportReclamationsService($import); // handle() returns true (the import ran), but the row was skipped $result = $service->handle(); $this->assertTrue($result); $this->assertDatabaseMissing('reclamations', ['reason' => 'Вандализм']); } /** * Full happy path: a ProductSKU with a known RFID exists, the status * exists in the DB (seeded), the import creates a Reclamation and attaches * the SKU to it. */ public function test_handle_creates_reclamation_when_sku_found(): void { // ReclamationStatus is seeded via $seed = true (ReclamationStatusSeeder). $status = ReclamationStatus::query()->first(); $this->assertNotNull($status, 'ReclamationStatusSeeder must create at least one status'); $order = Order::factory()->create(['year' => (int) date('Y')]); $product = Product::factory()->create(['year' => (int) date('Y')]); $rfid = 'RFID-TEST-' . uniqid(); $sku = ProductSKU::factory()->create([ 'rfid' => $rfid, 'order_id' => $order->id, 'product_id' => $product->id, 'year' => (int) date('Y'), ]); $import = $this->createImportWithFile([ [ 'Округ ЦАО', // Округ 'Тверской', // Район 'ул. Тестовая, 1', // Адрес 'ART-001', // Артикул '1.01.01', // Тип $rfid, // RFID 'Нет', // Гарантии 'Покраска', // Что сделано 46023, // Дата создания (2026-01-01 в Excel serial) 46024, // Дата начала работ (2026-01-02) 46054, // Дата завершения работ (2026-02-01) (int) date('Y'), // Год поставки МАФ 'Вандализм', // Причина $status->name, // Статус 'Тест комментарий',// Комментарий ], ]); $service = new ImportReclamationsService($import); $result = $service->handle(); $this->assertTrue($result); $this->assertDatabaseHas('reclamations', [ 'order_id' => $order->id, 'status_id' => $status->id, 'reason' => 'Вандализм', ]); } /** * When the RFID in the row does not match any ProductSKU the row is * logged with sku_not_found and skipped; handle() still returns true. */ public function test_handle_skips_row_when_sku_not_found(): void { $status = ReclamationStatus::query()->first(); $this->assertNotNull($status); $import = $this->createImportWithFile([ [ 'Округ ЦАО', 'Тверской', 'ул. Тестовая, 1', 'ART-001', '1.01.01', 'RFID-DOES-NOT-EXIST-' . uniqid(), // unknown RFID 'Нет', 'Покраска', '', '', '', (int) date('Y'), 'Вандализм', $status->name, '', ], ]); $service = new ImportReclamationsService($import); $result = $service->handle(); $this->assertTrue($result); $this->assertDatabaseMissing('reclamations', ['reason' => 'Вандализм']); } /** * A file that contains only the header row (no data rows) should be * processed successfully and return true. */ public function test_handle_returns_true_with_empty_data_rows(): void { $import = $this->createImportWithFile([]); $service = new ImportReclamationsService($import); $result = $service->handle(); $this->assertTrue($result); } }