admin = User::factory()->admin()->create(); $this->assistant = User::factory()->assistantHead()->create(); $this->manager = User::factory()->manager()->create(); $this->order = ProductionOrder::factory()->create(['manager_id' => $this->manager->id]); $this->item = ProductionOrderItem::factory()->create([ 'production_order_id' => $this->order->id, 'common_catalog_item_id' => CommonCatalogItem::factory(), ]); } public function test_assistant_can_upload_documents_photos_and_item_passport(): void { $this->actingAs($this->assistant) ->post(route('schedule.orders.documents.store', $this->order), [ 'documents' => [UploadedFile::fake()->create('instruction.pdf', 10, 'application/pdf')], ]) ->assertRedirect(); $this->actingAs($this->assistant) ->post(route('schedule.orders.photos.store', $this->order), [ 'photos' => [UploadedFile::fake()->image('object.jpg', 100, 100)], ]) ->assertRedirect(); $this->actingAs($this->assistant) ->post(route('schedule.orders.items.passport.store', [$this->order, $this->item]), [ 'passport' => UploadedFile::fake()->create('passport.pdf', 10, 'application/pdf'), ]) ->assertRedirect(); $this->assertSame(1, $this->order->documents()->count()); $this->assertSame(1, $this->order->photos()->count()); $this->assertNotNull($this->item->fresh()->passport_file_id); foreach (File::query()->get() as $file) { Storage::disk('public')->assertExists($file->path); } } public function test_manager_can_chat_but_cannot_manage_order_files(): void { $this->actingAs($this->manager) ->post(route('schedule.orders.chat-messages.store', $this->order), [ 'message' => 'Заказ принят в работу', 'attachments' => [UploadedFile::fake()->create('note.txt', 1, 'text/plain')], ]) ->assertRedirect() ->assertSessionHas('success'); $message = ChatMessage::query()->where('production_order_id', $this->order->id)->firstOrFail(); $this->assertSame('Заказ принят в работу', $message->message); $this->assertSame(1, $message->files()->count()); $this->actingAs($this->manager) ->post(route('schedule.orders.documents.store', $this->order), [ 'documents' => [UploadedFile::fake()->create('forbidden.pdf', 1, 'application/pdf')], ]) ->assertForbidden(); $this->actingAs($this->manager) ->post(route('schedule.orders.items.passport.store', [$this->order, $this->item]), [ 'passport' => UploadedFile::fake()->create('forbidden.pdf', 1, 'application/pdf'), ]) ->assertForbidden(); } public function test_order_card_uses_queued_multi_file_upload_forms(): void { $this->actingAs($this->assistant) ->get(route('schedule.orders.show', $this->order)) ->assertOk() ->assertSee('id="production-order-documents-input"', false) ->assertSee('name="documents[]" multiple', false) ->assertSee('id="production-order-photos-input"', false) ->assertSee('name="photos[]" multiple', false) ->assertSee('onchange="$(this).parent().submit()"', false); } public function test_document_and_photo_upload_validation_errors_are_in_russian(): void { $documentResponse = $this->actingAs($this->assistant) ->withHeader('Accept', 'application/json') ->post(route('schedule.orders.documents.store', $this->order), [ 'documents' => ['не файл'], ]) ->assertUnprocessable() ->assertJsonValidationErrors('documents.0'); $this->assertSame( 'Выбранный документ не является файлом.', $documentResponse->json('errors')['documents.0'][0], ); $this->assertStringContainsString( 'Выбранный документ не является файлом.', (string) $documentResponse->json('message'), ); $photoResponse = $this->actingAs($this->assistant) ->withHeader('Accept', 'application/json') ->post(route('schedule.orders.photos.store', $this->order), [ 'photos' => [UploadedFile::fake()->create('document.pdf', 10, 'application/pdf')], ]) ->assertUnprocessable() ->assertJsonValidationErrors('photos.0'); $this->assertSame( 'Допустимы только фотографии JPG, JPEG, PNG и WebP.', $photoResponse->json('errors')['photos.0'][0], ); $this->assertStringContainsString( 'Допустимы только фотографии JPG, JPEG, PNG и WebP.', (string) $photoResponse->json('message'), ); } public function test_order_card_shows_mime_icons_image_previews_and_chat(): void { $pdf = File::factory()->pdf()->create(['user_id' => $this->admin->id]); $image = File::factory()->image()->create(['user_id' => $this->admin->id]); $passport = File::factory()->image()->create(['user_id' => $this->admin->id]); $this->order->documents()->attach([$pdf->id, $image->id]); $this->order->photos()->attach($image->id); $this->item->update(['passport_file_id' => $passport->id]); ChatMessage::query()->create([ 'production_order_id' => $this->order->id, 'user_id' => $this->manager->id, 'message' => 'Сообщение в чате заказа', ]); $this->actingAs($this->manager) ->get(route('schedule.orders.show', $this->order)) ->assertOk() ->assertSee('bi-file-earmark-pdf', false) ->assertSee('bi-file-earmark-image', false) ->assertSee('data-toggle="lightbox"', false) ->assertSee('Фотографии (1)') ->assertSee('data-bs-target="#production-order-photos-'.$this->order->id.'"', false) ->assertSee('id="production-order-photos-'.$this->order->id.'" class="card-body collapse"', false) ->assertSee('data-lazy-images', false) ->assertSee('data-src="'.$image->thumbnail_link.'"', false) ->assertSee('class="img-thumbnail maf-img"', false) ->assertSee('Сообщение в чате заказа') ->assertSee(route('schedule.orders.chat-messages.store', $this->order), false) ->assertDontSee(route('schedule.orders.documents.store', $this->order), false); } public function test_file_and_chat_routes_reject_resources_from_another_order(): void { $otherOrder = ProductionOrder::factory()->create(['manager_id' => $this->manager->id]); $otherItem = ProductionOrderItem::factory()->create([ 'production_order_id' => $otherOrder->id, 'common_catalog_item_id' => CommonCatalogItem::factory(), ]); $message = ChatMessage::query()->create([ 'production_order_id' => $otherOrder->id, 'user_id' => $this->admin->id, 'message' => 'Чужое сообщение', ]); $this->actingAs($this->admin) ->post(route('schedule.orders.items.passport.store', [$this->order, $otherItem]), [ 'passport' => UploadedFile::fake()->create('passport.pdf', 1, 'application/pdf'), ]) ->assertNotFound(); $this->actingAs($this->admin) ->delete(route('schedule.orders.chat-messages.destroy', [$this->order, $message])) ->assertNotFound(); } }