id(); $table->string('codigo'); $table->boolean('scanner_category_validation_enabled')->default(false); }); Schema::create('roles', function (Blueprint $table): void { $table->id(); $table->string('codigo'); $table->string('nombre'); }); Schema::create('users', function (Blueprint $table): void { $table->id(); $table->string('rol_codigo'); $table->string('tenant_codigo'); $table->unsignedBigInteger('event_id')->nullable(); $table->string('nombre_apellido'); $table->string('dni'); $table->string('email'); $table->string('active_email')->nullable(); $table->string('password')->nullable(); $table->timestamps(); $table->softDeletes(); }); Schema::create('categorias', function (Blueprint $table): void { $table->id(); $table->string('nombre'); $table->string('tenant_code')->nullable(); $table->unsignedBigInteger('categoria_id')->nullable(); }); Schema::create('catalog_items', function (Blueprint $table): void { $table->id(); $table->unsignedBigInteger('category_id'); $table->string('tenant_code'); $table->softDeletes(); }); Schema::create('category_scanners', function (Blueprint $table): void { $table->unsignedBigInteger('user_id'); $table->unsignedBigInteger('categoria_id'); $table->timestamps(); }); Schema::create('personal_access_tokens', function (Blueprint $table): void { $table->id(); $table->string('tokenable_type'); $table->unsignedBigInteger('tokenable_id'); }); DB::table('tenants')->insert(['codigo' => 'onticket']); foreach (['adminapp', 'scanner'] as $role) { DB::table('roles')->insert(['codigo' => $role, 'nombre' => $role]); foreach ([10, 20, null] as $eventId) { $this->insertUser($role, 'onticket', $eventId); } $this->insertUser($role, 'other', 10); } Sanctum::actingAs(User::query()->findOrFail(1)); } public function test_lists_and_searches_are_restricted_to_the_authenticated_event(): void { foreach (['administrators' => 1, 'staff' => 5] as $path => $id) { foreach (['', '?search=Persona&event_id=20'] as $query) { $this->getJson("/api/v1/adminapp/tenant/{$path}{$query}") ->assertOk()->assertJsonCount(1, 'data') ->assertJsonPath('data.0.id', $id)->assertJsonPath('data.0.event_id', 10); } } } public function test_creation_inherits_the_actor_event_even_if_the_client_supplies_another(): void { $this->mock(ResetPasswordAttemptService::class, function (MockInterface $mock): void { $mock->shouldReceive('createForAdminAppEmail')->once(); $mock->shouldReceive('createForScannerEmail')->once(); }); foreach (['administrators' => 'adminapp', 'staff' => 'scanner'] as $path => $role) { $this->postJson("/api/v1/adminapp/tenant/{$path}", [ ...$this->payload("new-{$role}@example.com"), 'event_id' => 20, ])->assertSuccessful()->assertJsonPath('data.event_id', 10); $this->assertDatabaseHas('users', ['email' => "new-{$role}@example.com", 'event_id' => 10, 'rol_codigo' => $role]); } } public function test_foreign_and_unassigned_staff_cannot_be_edited_deleted_or_inspected(): void { foreach (['administrators' => [2, 3, 4], 'staff' => [6, 7, 8]] as $path => $ids) { foreach ($ids as $id) { $this->putJson("/api/v1/adminapp/tenant/{$path}/{$id}", $this->payload("update-{$id}@example.com"))->assertNotFound(); $this->deleteJson("/api/v1/adminapp/tenant/{$path}/{$id}")->assertNotFound(); $this->assertDatabaseHas('users', ['id' => $id, 'deleted_at' => null, 'email' => "person-{$id}@example.com"]); if ($path === 'staff') { $this->getJson("/api/v1/adminapp/tenant/staff/{$id}/scan-attempts")->assertNotFound(); } } } } public function test_own_staff_can_be_edited_and_deleted_without_changing_its_event(): void { $adminId = $this->insertUser('adminapp', 'onticket', 10); foreach (['administrators' => $adminId, 'staff' => 5] as $path => $id) { $this->putJson("/api/v1/adminapp/tenant/{$path}/{$id}", [ ...$this->payload("updated-{$id}@example.com"), 'event_id' => 20, ])->assertOk()->assertJsonPath('data.event_id', 10); $this->deleteJson("/api/v1/adminapp/tenant/{$path}/{$id}")->assertNoContent(); $this->assertSoftDeleted('users', ['id' => $id]); } } public function test_unscoped_actor_keeps_tenant_lists_and_creates_without_an_event(): void { Sanctum::actingAs(User::query()->findOrFail(3)); $this->mock(ResetPasswordAttemptService::class, function (MockInterface $mock): void { $mock->shouldReceive('createForAdminAppEmail')->once(); $mock->shouldReceive('createForScannerEmail')->once(); }); foreach (['administrators', 'staff'] as $path) { $this->getJson("/api/v1/adminapp/tenant/{$path}")->assertOk()->assertJsonCount(3, 'data'); $this->postJson("/api/v1/adminapp/tenant/{$path}", $this->payload("legacy-{$path}@example.com")) ->assertSuccessful()->assertJsonPath('data.event_id', null); } } private function insertUser(string $role, string $tenant, ?int $eventId): int { $id = DB::table('users')->count() + 1; return DB::table('users')->insertGetId([ 'id' => $id, 'rol_codigo' => $role, 'tenant_codigo' => $tenant, 'event_id' => $eventId, 'nombre_apellido' => 'Persona', 'dni' => '12345678', 'email' => "person-{$id}@example.com", 'active_email' => "person-{$id}@example.com", ]); } private function payload(string $email): array { return ['nombre_apellido' => 'Persona editada', 'dni' => '87654321', 'email' => $email]; } }