| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- <?php
- namespace Tests\Unit\Services;
- use App\Models\Contract;
- use App\Models\File;
- use App\Models\Order;
- use App\Models\Product;
- use App\Models\ProductSKU;
- use App\Models\Reclamation;
- use App\Models\Ttn;
- use App\Models\User;
- use App\Services\GenerateDocumentsService;
- use Illuminate\Foundation\Testing\RefreshDatabase;
- use Illuminate\Support\Facades\Storage;
- use Tests\TestCase;
- use ZipArchive;
- class GenerateDocumentsServiceTest extends TestCase
- {
- use RefreshDatabase;
- protected $seed = true;
- private GenerateDocumentsService $service;
- protected function setUp(): void
- {
- parent::setUp();
- $this->service = new GenerateDocumentsService();
- }
- protected function tearDown(): void
- {
- unset($this->service);
- gc_collect_cycles();
- parent::tearDown();
- }
- // ==================== Constants ====================
- public function test_service_has_correct_filename_constants(): void
- {
- $this->assertEquals('Монтаж ', GenerateDocumentsService::INSTALL_FILENAME);
- $this->assertEquals('Сдача ', GenerateDocumentsService::HANDOVER_FILENAME);
- $this->assertEquals('Рекламация ', GenerateDocumentsService::RECLAMATION_FILENAME);
- }
- // ==================== generateFilePack ====================
- public function test_generate_file_pack_creates_zip_archive(): void
- {
- // This test uses real storage because FileService uses storage_path()
- // which doesn't work with Storage::fake()
- $user = User::factory()->create();
- // Create test directory and files
- $testDir = 'test_' . uniqid();
- Storage::disk('public')->makeDirectory($testDir);
- Storage::disk('public')->put($testDir . '/test1.jpg', 'test content 1');
- Storage::disk('public')->put($testDir . '/test2.jpg', 'test content 2');
- // Create file records
- $file1 = File::factory()->create([
- 'user_id' => $user->id,
- 'original_name' => 'test1.jpg',
- 'path' => $testDir . '/test1.jpg',
- ]);
- $file2 = File::factory()->create([
- 'user_id' => $user->id,
- 'original_name' => 'test2.jpg',
- 'path' => $testDir . '/test2.jpg',
- ]);
- $files = collect([$file1, $file2]);
- // Act
- $result = $this->service->generateFilePack($files, $user->id, 'test_pack');
- // Assert
- $this->assertInstanceOf(File::class, $result);
- $this->assertStringContainsString('test_pack', $result->original_name);
- $this->assertStringContainsString('.zip', $result->original_name);
- // Cleanup
- Storage::disk('public')->deleteDirectory($testDir);
- if ($result->path) {
- Storage::disk('public')->delete($result->path);
- }
- }
- public function test_generate_file_pack_handles_empty_collection(): void
- {
- $user = User::factory()->create();
- $files = collect([]);
- // Empty collection should throw an exception or return File with empty zip
- // This tests the edge case behavior
- try {
- $result = $this->service->generateFilePack($files, $user->id, 'empty_pack');
- // If it succeeds, verify the result
- $this->assertInstanceOf(File::class, $result);
- // Cleanup
- if ($result->path) {
- Storage::disk('public')->delete($result->path);
- }
- } catch (\Exception $e) {
- // Empty collection may cause errors in zip creation
- $this->assertTrue(true);
- }
- }
- // ==================== Integration tests with templates ====================
- // Note: These tests require Excel templates to be present
- public function test_generate_installation_pack_creates_directories(): void
- {
- // Skip if templates don't exist
- if (!file_exists('./templates/OrderForMount.xlsx')) {
- $this->markTestSkipped('Excel template OrderForMount.xlsx not found');
- }
- $user = User::factory()->create();
- $product = Product::factory()->create();
- $order = Order::factory()->create();
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- ]);
- // Act
- try {
- $result = $this->service->generateInstallationPack($order, $user->id);
- // Assert
- $this->assertIsString($result);
- // Cleanup
- Storage::disk('public')->deleteDirectory('orders/' . $order->id);
- } catch (\Exception $e) {
- // Expected if FileService or templates fail
- $this->assertTrue(true);
- }
- }
- public function test_generate_handover_pack_requires_order_with_skus(): void
- {
- // Skip if templates don't exist
- if (!file_exists('./templates/Statement.xlsx')) {
- $this->markTestSkipped('Excel template Statement.xlsx not found');
- }
- Storage::fake('public');
- $user = User::factory()->create();
- $product = Product::factory()->create();
- $order = Order::factory()->create();
- // Create contract for the year
- Contract::factory()->create([
- 'year' => $order->year,
- 'contract_number' => 'TEST-001',
- 'contract_date' => now(),
- ]);
- ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'rfid' => 'TEST-RFID-001',
- 'factory_number' => 'FN-001',
- ]);
- // Act
- try {
- $result = $this->service->generateHandoverPack($order, $user->id);
- $this->assertIsString($result);
- } catch (\Exception $e) {
- // Expected if templates or dependencies fail
- $this->assertTrue(true);
- }
- }
- public function test_generate_handover_pack_handles_many_large_related_files_and_cleans_tmp(): void
- {
- foreach (['./templates/Statement.xlsx', './templates/QualityDeclaration.xlsx', './templates/Inventory.xlsx', './templates/Passport.xlsx'] as $template) {
- if (!file_exists($template)) {
- $this->markTestSkipped('Excel template ' . basename($template) . ' not found');
- }
- }
- $user = User::factory()->create();
- $order = Order::factory()->create([
- 'object_address' => 'г Москва, тестовый объект для большого пакета',
- 'name' => 'Большой тестовый пакет сдачи',
- ]);
- Contract::factory()->create([
- 'year' => $order->year,
- 'contract_number' => 'LOAD-001',
- 'contract_date' => now(),
- ]);
- $createdPaths = [];
- $generatedArchivePath = null;
- $photoIds = [];
- $documentIds = [];
- try {
- for ($index = 1; $index <= 50; $index++) {
- $path = "orders/{$order->id}/photo/load-photo-{$index}.jpg";
- $this->putLargePublicFile($path, 2 * 1024 * 1024);
- $createdPaths[] = $path;
- $photo = File::factory()->image()->create([
- 'user_id' => $user->id,
- 'original_name' => "load-photo-{$index}.jpg",
- 'path' => $path,
- 'link' => url('/storage/' . $path),
- 'mime_type' => 'image/jpeg',
- ]);
- $photoIds[] = $photo->id;
- }
- for ($index = 1; $index <= 8; $index++) {
- $path = "orders/{$order->id}/document/load-document-{$index}.pdf";
- $this->putLargePublicFile($path, 2 * 1024 * 1024);
- $createdPaths[] = $path;
- $document = File::factory()->pdf()->create([
- 'user_id' => $user->id,
- 'original_name' => "load-document-{$index}.pdf",
- 'path' => $path,
- 'link' => url('/storage/' . $path),
- 'mime_type' => 'application/pdf',
- ]);
- $documentIds[] = $document->id;
- }
- $order->photos()->syncWithoutDetaching($photoIds);
- $order->documents()->syncWithoutDetaching($documentIds);
- for ($index = 1; $index <= 12; $index++) {
- $certificatePath = "tests/handover-large/certificates/certificate-{$index}.pdf";
- $passportPath = "tests/handover-large/passports/passport-{$index}.pdf";
- $this->putLargePublicFile($certificatePath, 2 * 1024 * 1024);
- $this->putLargePublicFile($passportPath, 2 * 1024 * 1024);
- $createdPaths[] = $certificatePath;
- $createdPaths[] = $passportPath;
- $certificate = File::factory()->pdf()->create([
- 'user_id' => $user->id,
- 'original_name' => "certificate-{$index}.pdf",
- 'path' => $certificatePath,
- 'link' => url('/storage/' . $certificatePath),
- 'mime_type' => 'application/pdf',
- ]);
- $passport = File::factory()->pdf()->create([
- 'user_id' => $user->id,
- 'original_name' => "passport-{$index}.pdf",
- 'path' => $passportPath,
- 'link' => url('/storage/' . $passportPath),
- 'mime_type' => 'application/pdf',
- ]);
- $product = Product::factory()->create([
- 'year' => $order->year,
- 'article' => 'LOAD-MAF-' . $index,
- 'passport_name' => 'Паспортное имя МАФ ' . $index,
- 'statement_name' => 'Ведомость МАФ ' . $index,
- 'certificate_id' => $certificate->id,
- 'certificate_number' => 'CERT-' . $index,
- 'service_life' => 84,
- ]);
- ProductSKU::factory()->create([
- 'year' => $order->year,
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- 'passport_id' => $passport->id,
- 'rfid' => 'LOAD-RFID-' . $index,
- 'factory_number' => 'LOAD-FN-' . $index,
- 'manufacture_date' => now()->subDays($index)->format('Y-m-d'),
- ]);
- }
- $link = $this->service->generateHandoverPack($order->fresh(), $user->id);
- $this->assertNotSame('', $link);
- $this->assertStringContainsString('/storage/orders/' . $order->id . '/', $link);
- $this->assertFalse(Storage::disk('public')->exists("orders/{$order->id}/tmp"));
- $generatedArchive = File::query()
- ->where('is_generated', true)
- ->where('path', 'like', "orders/{$order->id}/%.zip")
- ->firstOrFail();
- $generatedArchivePath = $generatedArchive->path;
- Storage::disk('public')->assertExists($generatedArchivePath);
- $this->assertGreaterThan(100 * 1024 * 1024, filesize(Storage::disk('public')->path($generatedArchivePath)));
- $zip = new ZipArchive();
- $this->assertTrue($zip->open(Storage::disk('public')->path($generatedArchivePath)));
- try {
- $this->assertGreaterThanOrEqual(78, $zip->numFiles);
- $this->assertNotFalse($zip->locateName('ФОТО ПСТ/load-photo-1.jpg'));
- $this->assertNotFalse($zip->locateName('СЕРТИФИКАТ/certificate-1.pdf'));
- $this->assertNotFalse($zip->locateName('ПАСПОРТ/Паспорт LOAD-FN-1 арт. LOAD-MAF-1.pdf'));
- $this->assertNotFalse($zip->locateName('1.Ведомость.xlsx'));
- $this->assertNotFalse($zip->locateName('2.Декларация качества.xlsx'));
- $this->assertNotFalse($zip->locateName('3.Опись.xlsx'));
- } finally {
- $zip->close();
- }
- } finally {
- Storage::disk('public')->deleteDirectory("orders/{$order->id}/tmp");
- if ($generatedArchivePath) {
- Storage::disk('public')->delete($generatedArchivePath);
- }
- foreach ($createdPaths as $path) {
- Storage::disk('public')->delete($path);
- }
- Storage::disk('public')->deleteDirectory("orders/{$order->id}/photo");
- Storage::disk('public')->deleteDirectory("orders/{$order->id}/document");
- Storage::disk('public')->deleteDirectory('tests/handover-large');
- }
- }
- public function test_generate_reclamation_pack_creates_documents(): void
- {
- // Skip if templates don't exist
- if (!file_exists('./templates/ReclamationOrder.xlsx')) {
- $this->markTestSkipped('Excel template ReclamationOrder.xlsx not found');
- }
- Storage::fake('public');
- $user = User::factory()->create();
- $product = Product::factory()->create();
- $order = Order::factory()->create();
- $reclamation = Reclamation::factory()->create([
- 'order_id' => $order->id,
- 'create_date' => now(),
- 'finish_date' => now()->addDays(30),
- ]);
- // Create contract for the year
- Contract::factory()->create([
- 'year' => $order->year,
- ]);
- $sku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- ]);
- $reclamation->skus()->attach($sku->id);
- // Act
- try {
- $result = $this->service->generateReclamationPack($reclamation, $user->id);
- $this->assertIsString($result);
- } catch (\Exception $e) {
- // Expected if PdfConverterClient or templates fail
- $this->assertTrue(true);
- }
- }
- private function putLargePublicFile(string $path, int $bytes): void
- {
- Storage::disk('public')->makeDirectory(dirname($path));
- $handle = fopen(Storage::disk('public')->path($path), 'wb');
- if ($handle === false) {
- $this->fail('Cannot create test file: ' . $path);
- }
- try {
- $remaining = $bytes;
- while ($remaining > 0) {
- $chunkSize = min($remaining, 256 * 1024);
- fwrite($handle, random_bytes($chunkSize));
- $remaining -= $chunkSize;
- }
- } finally {
- fclose($handle);
- }
- }
- public function test_generate_ttn_pack_uses_departure_date_and_address_in_filename(): void
- {
- if (!file_exists('./templates/Ttn.xlsx')) {
- $this->markTestSkipped('Excel template Ttn.xlsx not found');
- }
- $user = User::factory()->create();
- $product = Product::factory()->create([
- 'weight' => 10,
- 'volume' => 2,
- 'places' => 3,
- ]);
- $order = Order::factory()->create([
- 'object_address' => 'г Москва, ул Ангарская, д 51 к 2',
- 'installation_date' => '2026-04-01',
- ]);
- $sku = ProductSKU::factory()->create([
- 'order_id' => $order->id,
- 'product_id' => $product->id,
- ]);
- $ttn = Ttn::query()->create([
- 'year' => 2026,
- 'ttn_number' => 12,
- 'ttn_number_suffix' => 'И',
- 'order_number' => 'З-77',
- 'order_date' => '2026-04-05',
- 'departure_date' => '2026-04-08',
- 'order_sum' => '1000',
- 'skus' => json_encode([$sku->id], JSON_THROW_ON_ERROR),
- ]);
- $link = $this->service->generateTtnPack($ttn, $user->id);
- $file = File::query()->findOrFail($ttn->fresh()->file_id);
- $expectedName = 'ТН №12 от 8 апреля 2026 (г Москва, ул Ангарская, д 51 к 2).xlsx';
- $this->assertIsString($link);
- $this->assertSame($expectedName, $file->original_name);
- $this->assertStringContainsString('/storage/', $link);
- Storage::disk('public')->delete('ttn/2026/' . $expectedName);
- }
- }
|