2026-06-25 16:51:42 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Feature;
|
|
|
|
|
|
|
|
|
|
use App\Livewire\Projects\ProjectMap;
|
|
|
|
|
use App\Models\Feature;
|
|
|
|
|
use App\Models\Inspection;
|
|
|
|
|
use App\Models\InspectionTemplate;
|
|
|
|
|
use App\Models\Layer;
|
2026-08-03 13:44:10 +02:00
|
|
|
use App\Models\Media;
|
2026-06-25 16:51:42 +02:00
|
|
|
use App\Models\Phase;
|
|
|
|
|
use App\Models\Project;
|
|
|
|
|
use App\Models\User;
|
2026-08-28 13:04:28 +02:00
|
|
|
use Database\Seeders\PermissionCatalogSeeder;
|
|
|
|
|
use Database\Seeders\RolesAndPermissionsSeeder;
|
2026-06-25 16:51:42 +02:00
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
use Illuminate\Http\UploadedFile;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Livewire\Livewire;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
class InspectionFormTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
use RefreshDatabase;
|
|
|
|
|
|
2026-08-03 13:44:10 +02:00
|
|
|
protected function setUp(): void
|
2026-06-25 16:51:42 +02:00
|
|
|
{
|
2026-08-03 13:44:10 +02:00
|
|
|
parent::setUp();
|
2026-06-25 16:51:42 +02:00
|
|
|
Storage::fake('public');
|
2026-08-28 13:04:28 +02:00
|
|
|
$this->seed(RolesAndPermissionsSeeder::class);
|
|
|
|
|
$this->seed(PermissionCatalogSeeder::class);
|
2026-08-03 13:44:10 +02:00
|
|
|
}
|
2026-07-29 01:44:38 +02:00
|
|
|
|
2026-08-03 13:44:10 +02:00
|
|
|
private function createTestData(): array
|
|
|
|
|
{
|
2026-06-25 16:51:42 +02:00
|
|
|
$user = User::factory()->create();
|
2026-07-29 01:44:38 +02:00
|
|
|
$user->assignRole('Supervisor');
|
|
|
|
|
|
2026-06-25 16:51:42 +02:00
|
|
|
$project = Project::create([
|
|
|
|
|
'reference' => 'INS', 'name' => 'Proyecto Insp', 'address' => 'x', 'lat' => 40, 'lng' => -3,
|
|
|
|
|
'start_date' => now()->toDateString(), 'end_date_estimated' => now()->addMonth()->toDateString(),
|
|
|
|
|
'status' => 'in_progress', 'created_by' => $user->id,
|
|
|
|
|
]);
|
|
|
|
|
$project->users()->attach($user->id, ['role_in_project' => 'supervisor']);
|
|
|
|
|
|
|
|
|
|
$phase = Phase::create(['project_id' => $project->id, 'name' => 'F1', 'order' => 1, 'color' => '#000', 'progress_percent' => 0]);
|
|
|
|
|
$layer = Layer::create(['project_id' => $project->id, 'phase_id' => $phase->id, 'name' => 'L', 'color' => '#111', 'uploaded_by' => $user->id]);
|
|
|
|
|
$feature = Feature::create([
|
|
|
|
|
'layer_id' => $layer->id, 'name' => 'Pilar', 'geometry' => ['type' => 'Point', 'coordinates' => [-3, 40]],
|
|
|
|
|
'progress' => 0, 'status' => 'planned',
|
|
|
|
|
]);
|
|
|
|
|
$template = InspectionTemplate::create([
|
|
|
|
|
'project_id' => $project->id, 'name' => 'Recepción',
|
|
|
|
|
'fields' => [
|
|
|
|
|
['group' => 'Geometría', 'name' => 'altura', 'label' => 'Altura', 'question' => '¿Cota OK?', 'type' => 'text', 'required' => false, 'help' => 'Medir'],
|
|
|
|
|
],
|
|
|
|
|
]);
|
2026-07-29 01:44:38 +02:00
|
|
|
$project->inspectionTemplates()->attach($template->id);
|
|
|
|
|
|
2026-08-03 13:44:10 +02:00
|
|
|
return [$user, $project, $feature, $template];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_register_inspection_with_comments_and_photo(): void
|
|
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
|
|
|
|
|
2026-06-25 16:51:42 +02:00
|
|
|
Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id)
|
|
|
|
|
->set('selectedTemplateId', $template->id)
|
|
|
|
|
->set('inspectionFormData.altura', '5.2')
|
|
|
|
|
->set('inspectionNotes', 'Todo conforme')
|
|
|
|
|
->set('inspectionPhotos', [UploadedFile::fake()->image('foto.jpg')])
|
|
|
|
|
->call('saveInspection');
|
|
|
|
|
|
|
|
|
|
$ins = Inspection::where('feature_id', $feature->id)->first();
|
|
|
|
|
$this->assertNotNull($ins);
|
|
|
|
|
$this->assertEquals('Todo conforme', $ins->notes);
|
|
|
|
|
$this->assertEquals('5.2', $ins->data['altura']);
|
|
|
|
|
$this->assertCount(1, $ins->media);
|
|
|
|
|
}
|
2026-08-03 13:44:10 +02:00
|
|
|
|
|
|
|
|
public function test_edit_inspection_updates_data_and_photos(): void
|
|
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
|
|
|
|
|
|
|
|
|
// Create initial inspection
|
|
|
|
|
$inspection = Inspection::create([
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'layer_id' => $feature->layer_id,
|
|
|
|
|
'feature_id' => $feature->id,
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'inspector_user_id' => $user->id,
|
|
|
|
|
'status' => 'completed',
|
|
|
|
|
'completed_at' => now(),
|
|
|
|
|
'result' => 'pass',
|
|
|
|
|
'notes' => 'Original note',
|
|
|
|
|
'data' => ['altura' => '5.0'],
|
|
|
|
|
]);
|
|
|
|
|
$photo1 = Media::create([
|
|
|
|
|
'mediable_type' => Inspection::class,
|
|
|
|
|
'mediable_id' => $inspection->id,
|
|
|
|
|
'name' => 'original.jpg',
|
2026-08-28 13:04:28 +02:00
|
|
|
'file_path' => 'uploads/inspections/'.$inspection->id.'/original.jpg',
|
2026-08-03 13:44:10 +02:00
|
|
|
'file_type' => 'image/jpeg',
|
|
|
|
|
'file_extension' => 'jpg',
|
|
|
|
|
'file_size' => 1000,
|
|
|
|
|
'category' => 'image',
|
|
|
|
|
'uploaded_by' => $user->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id)
|
|
|
|
|
->call('editInspection', $inspection->id)
|
|
|
|
|
->assertSet('editingInspection.id', $inspection->id)
|
|
|
|
|
->assertSet('editInspectionFormData.altura', '5.0')
|
|
|
|
|
->assertSet('editInspectionResult', 'pass')
|
|
|
|
|
->assertSet('editInspectionNotes', 'Original note')
|
|
|
|
|
->set('editInspectionFormData.altura', '5.5')
|
|
|
|
|
->set('editInspectionResult', 'fail')
|
|
|
|
|
->set('editInspectionNotes', 'Updated note')
|
|
|
|
|
->set('editInspectionPhotos', [UploadedFile::fake()->image('new.jpg')])
|
|
|
|
|
->call('saveEditInspection');
|
|
|
|
|
|
|
|
|
|
$ins = $inspection->fresh();
|
|
|
|
|
$this->assertEquals('5.5', $ins->data['altura']);
|
|
|
|
|
$this->assertEquals('fail', $ins->result);
|
|
|
|
|
$this->assertEquals('Updated note', $ins->notes);
|
|
|
|
|
$this->assertCount(2, $ins->media); // original + new
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_edit_inspection_delete_existing_photo(): void
|
|
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
|
|
|
|
|
|
|
|
|
$inspection = Inspection::create([
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'layer_id' => $feature->layer_id,
|
|
|
|
|
'feature_id' => $feature->id,
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'inspector_user_id' => $user->id,
|
|
|
|
|
'status' => 'completed',
|
|
|
|
|
'completed_at' => now(),
|
|
|
|
|
'result' => 'pass',
|
|
|
|
|
'notes' => 'Note',
|
|
|
|
|
'data' => ['altura' => '5.0'],
|
|
|
|
|
]);
|
|
|
|
|
$photo1 = Media::create([
|
|
|
|
|
'mediable_type' => Inspection::class,
|
|
|
|
|
'mediable_id' => $inspection->id,
|
|
|
|
|
'name' => 'to_delete.jpg',
|
2026-08-28 13:04:28 +02:00
|
|
|
'file_path' => 'uploads/inspections/'.$inspection->id.'/to_delete.jpg',
|
2026-08-03 13:44:10 +02:00
|
|
|
'file_type' => 'image/jpeg',
|
|
|
|
|
'file_extension' => 'jpg',
|
|
|
|
|
'file_size' => 1000,
|
|
|
|
|
'category' => 'image',
|
|
|
|
|
'uploaded_by' => $user->id,
|
|
|
|
|
]);
|
|
|
|
|
$photo2 = Media::create([
|
|
|
|
|
'mediable_type' => Inspection::class,
|
|
|
|
|
'mediable_id' => $inspection->id,
|
|
|
|
|
'name' => 'keep.jpg',
|
2026-08-28 13:04:28 +02:00
|
|
|
'file_path' => 'uploads/inspections/'.$inspection->id.'/keep.jpg',
|
2026-08-03 13:44:10 +02:00
|
|
|
'file_type' => 'image/jpeg',
|
|
|
|
|
'file_extension' => 'jpg',
|
|
|
|
|
'file_size' => 1000,
|
|
|
|
|
'category' => 'image',
|
|
|
|
|
'uploaded_by' => $user->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id)
|
|
|
|
|
->call('editInspection', $inspection->id)
|
|
|
|
|
->call('deleteEditPhoto', 0) // delete first photo (to_delete.jpg)
|
|
|
|
|
->call('saveEditInspection');
|
|
|
|
|
|
|
|
|
|
$this->assertCount(1, $inspection->fresh()->media);
|
|
|
|
|
$this->assertEquals('keep.jpg', $inspection->fresh()->media->first()->name);
|
|
|
|
|
$this->assertDatabaseMissing('media', ['id' => $photo1->id]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_edit_inspection_forbidden_without_permission(): void
|
|
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
|
|
|
|
$user->syncRoles([]);
|
|
|
|
|
$user->givePermissionTo(['view projects', 'view inspections']); // only view
|
|
|
|
|
|
|
|
|
|
$inspection = Inspection::create([
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'layer_id' => $feature->layer_id,
|
|
|
|
|
'feature_id' => $feature->id,
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'inspector_user_id' => $user->id,
|
|
|
|
|
'status' => 'completed',
|
|
|
|
|
'completed_at' => now(),
|
|
|
|
|
'result' => 'pass',
|
|
|
|
|
'notes' => 'Note',
|
|
|
|
|
'data' => ['altura' => '5.0'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Modal opens but save is blocked
|
|
|
|
|
Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id)
|
|
|
|
|
->call('editInspection', $inspection->id)
|
|
|
|
|
->set('editInspectionNotes', 'Intento editar')
|
|
|
|
|
->call('saveEditInspection')
|
|
|
|
|
->assertDispatched('notify', 'Sin permisos para editar inspecciones.');
|
|
|
|
|
|
|
|
|
|
// Data should not have changed
|
|
|
|
|
$this->assertEquals('Note', $inspection->fresh()->notes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_create_inspection_forbidden_without_permission(): void
|
|
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
|
|
|
|
$user->syncRoles([]);
|
|
|
|
|
$user->givePermissionTo(['view projects', 'view inspections']);
|
|
|
|
|
|
|
|
|
|
Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id)
|
|
|
|
|
->set('selectedTemplateId', $template->id)
|
|
|
|
|
->set('inspectionFormData.altura', '5.2')
|
|
|
|
|
->set('inspectionPhotos', [UploadedFile::fake()->image('foto.jpg')])
|
|
|
|
|
->call('saveInspection')
|
|
|
|
|
->assertDispatched('notify', 'Sin permisos para crear inspecciones.');
|
|
|
|
|
|
|
|
|
|
$this->assertDatabaseMissing('inspections', ['feature_id' => $feature->id]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_delete_inspection_removes_inspection_and_media(): void
|
2026-08-28 13:04:28 +02:00
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
2026-08-03 13:44:10 +02:00
|
|
|
|
2026-08-28 13:04:28 +02:00
|
|
|
$inspection = Inspection::create([
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'layer_id' => $feature->layer_id,
|
|
|
|
|
'feature_id' => $feature->id,
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'inspector_user_id' => $user->id,
|
|
|
|
|
'status' => 'completed',
|
|
|
|
|
'completed_at' => now(),
|
|
|
|
|
'result' => 'pass',
|
|
|
|
|
'notes' => 'Note',
|
|
|
|
|
'data' => ['altura' => '5.0'],
|
|
|
|
|
]);
|
|
|
|
|
$photo = Media::create([
|
|
|
|
|
'mediable_type' => Inspection::class,
|
|
|
|
|
'mediable_id' => $inspection->id,
|
|
|
|
|
'name' => 'test.jpg',
|
|
|
|
|
'file_path' => 'uploads/inspections/'.$inspection->id.'/test.jpg',
|
|
|
|
|
'file_type' => 'image/jpeg',
|
|
|
|
|
'file_extension' => 'jpg',
|
|
|
|
|
'file_size' => 1000,
|
|
|
|
|
'category' => 'image',
|
|
|
|
|
'uploaded_by' => $user->id,
|
|
|
|
|
]);
|
2026-08-03 13:44:10 +02:00
|
|
|
|
2026-08-28 13:04:28 +02:00
|
|
|
// Test direct model delete first
|
|
|
|
|
$inspection->delete();
|
|
|
|
|
$this->assertSoftDeleted('inspections', ['id' => $inspection->id]);
|
|
|
|
|
$this->assertDatabaseMissing('media', ['id' => $photo->id]);
|
2026-08-03 13:44:10 +02:00
|
|
|
|
2026-08-28 13:04:28 +02:00
|
|
|
// Restore for the actual test
|
|
|
|
|
$inspection->restore();
|
|
|
|
|
$this->assertDatabaseHas('inspections', ['id' => $inspection->id, 'deleted_at' => null]);
|
2026-08-03 13:44:10 +02:00
|
|
|
|
2026-08-28 13:04:28 +02:00
|
|
|
// Now test through Livewire component
|
|
|
|
|
$inspection2 = Inspection::create([
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'layer_id' => $feature->layer_id,
|
|
|
|
|
'feature_id' => $feature->id,
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'inspector_user_id' => $user->id,
|
|
|
|
|
'status' => 'completed',
|
|
|
|
|
'completed_at' => now(),
|
|
|
|
|
'result' => 'pass',
|
|
|
|
|
'notes' => 'Note',
|
|
|
|
|
'data' => ['altura' => '5.0'],
|
|
|
|
|
]);
|
|
|
|
|
$photo2 = Media::create([
|
|
|
|
|
'mediable_type' => Inspection::class,
|
|
|
|
|
'mediable_id' => $inspection2->id,
|
|
|
|
|
'name' => 'test2.jpg',
|
|
|
|
|
'file_path' => 'uploads/inspections/'.$inspection2->id.'/test2.jpg',
|
|
|
|
|
'file_type' => 'image/jpeg',
|
|
|
|
|
'file_extension' => 'jpg',
|
|
|
|
|
'file_size' => 1000,
|
|
|
|
|
'category' => 'image',
|
|
|
|
|
'uploaded_by' => $user->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$component = Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id);
|
|
|
|
|
|
|
|
|
|
// Debug: check if component is instantiated
|
|
|
|
|
$component->assertSet('project.id', $project->id);
|
|
|
|
|
$component->assertSet('selectedFeature.id', $feature->id);
|
|
|
|
|
|
|
|
|
|
// Call deleteInspection and capture events
|
|
|
|
|
$component->call('deleteInspection', $inspection2->id);
|
|
|
|
|
|
|
|
|
|
// Check if notify was dispatched
|
|
|
|
|
$component->assertDispatched('notify', 'Inspección eliminada correctamente');
|
|
|
|
|
|
|
|
|
|
$this->assertSoftDeleted('inspections', ['id' => $inspection2->id]);
|
|
|
|
|
$this->assertDatabaseMissing('media', ['id' => $photo2->id]);
|
|
|
|
|
}
|
2026-08-03 13:44:10 +02:00
|
|
|
|
|
|
|
|
public function test_delete_inspection_forbidden_without_permission(): void
|
|
|
|
|
{
|
|
|
|
|
[$user, $project, $feature, $template] = $this->createTestData();
|
|
|
|
|
$user->syncRoles([]);
|
|
|
|
|
$user->givePermissionTo(['view projects', 'view inspections']);
|
|
|
|
|
|
|
|
|
|
$inspection = Inspection::create([
|
|
|
|
|
'project_id' => $project->id,
|
|
|
|
|
'layer_id' => $feature->layer_id,
|
|
|
|
|
'feature_id' => $feature->id,
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'inspector_user_id' => $user->id,
|
|
|
|
|
'status' => 'completed',
|
|
|
|
|
'completed_at' => now(),
|
|
|
|
|
'result' => 'pass',
|
|
|
|
|
'notes' => 'Note',
|
|
|
|
|
'data' => ['altura' => '5.0'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Livewire::actingAs($user)
|
|
|
|
|
->test(ProjectMap::class, ['project' => $project])
|
|
|
|
|
->call('selectFeature', $feature->id)
|
|
|
|
|
->call('deleteInspection', $inspection->id)
|
|
|
|
|
->assertDispatched('notify', 'Sin permisos para eliminar inspecciones.');
|
|
|
|
|
|
|
|
|
|
$this->assertDatabaseHas('inspections', ['id' => $inspection->id, 'deleted_at' => null]);
|
|
|
|
|
}
|
2026-08-28 13:04:28 +02:00
|
|
|
}
|