| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- <?php
- namespace Tests\Unit\Services\Import;
- use App\Models\Import;
- use App\Models\Order;
- use App\Models\Product;
- use App\Models\ProductSKU;
- use App\Models\Reclamation;
- use App\Models\ReclamationStatus;
- use App\Models\User;
- use App\Services\ImportReclamationsService;
- use Illuminate\Foundation\Testing\RefreshDatabase;
- use Illuminate\Support\Facades\Storage;
- use PhpOffice\PhpSpreadsheet\Spreadsheet;
- use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
- use Tests\TestCase;
- class ImportReclamationsServiceTest extends TestCase
- {
- use RefreshDatabase;
- protected $seed = true;
- private array $tempFiles = [];
- protected function tearDown(): void
- {
- foreach ($this->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);
- }
- }
|