seed(RoleSeeder::class); $this->organisation = Organisation::factory()->create(); $this->otherOrganisation = Organisation::factory()->create(); $this->event = Event::factory()->create(['organisation_id' => $this->organisation->id]); $this->orgAdmin = User::factory()->create(); $this->organisation->users()->attach($this->orgAdmin, ['role' => 'org_admin']); $this->outsider = User::factory()->create(); $this->otherOrganisation->users()->attach($this->outsider, ['role' => 'org_admin']); } public function test_index_returns_fields_ordered_by_sort_order(): void { RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'label' => 'Third', 'slug' => 'third', 'sort_order' => 3, ]); RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'label' => 'First', 'slug' => 'first', 'sort_order' => 1, ]); RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'label' => 'Second', 'slug' => 'second', 'sort_order' => 2, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields"); $response->assertOk(); $this->assertCount(3, $response->json('data')); $this->assertEquals('First', $response->json('data.0.label')); $this->assertEquals('Second', $response->json('data.1.label')); $this->assertEquals('Third', $response->json('data.2.label')); } public function test_store_creates_field_with_auto_slug(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields", [ 'label' => 'Shirtmaat', 'field_type' => 'select', 'options' => ['XS', 'S', 'M', 'L', 'XL'], ]); $response->assertCreated() ->assertJsonPath('data.slug', 'shirtmaat') ->assertJsonPath('data.label', 'Shirtmaat'); $this->assertDatabaseHas('registration_form_fields', [ 'event_id' => $this->event->id, 'slug' => 'shirtmaat', ]); } public function test_store_select_field_requires_options(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields", [ 'label' => 'Shirtmaat', 'field_type' => 'select', ]); $response->assertUnprocessable() ->assertJsonValidationErrors('options'); } public function test_store_text_field_rejects_options(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields", [ 'label' => 'Naam', 'field_type' => 'text', 'options' => ['A', 'B'], ]); $response->assertUnprocessable() ->assertJsonValidationErrors('options'); } public function test_store_tag_picker_accepts_tag_category(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields", [ 'label' => 'Vaardigheden', 'field_type' => 'tag_picker', 'tag_category' => 'Vaardigheid', ]); $response->assertCreated() ->assertJsonPath('data.tag_category', 'Vaardigheid'); } public function test_store_text_field_rejects_tag_category(): void { Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields", [ 'label' => 'Naam', 'field_type' => 'text', 'tag_category' => 'Vaardigheid', ]); $response->assertUnprocessable() ->assertJsonValidationErrors('tag_category'); } public function test_slug_uniqueness_per_event(): void { RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'label' => 'Shirtmaat', 'slug' => 'shirtmaat', ]); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields", [ 'label' => 'Shirtmaat', 'field_type' => 'text', ]); $response->assertCreated() ->assertJsonPath('data.slug', 'shirtmaat-2'); } public function test_same_slug_allowed_on_different_events(): void { $otherEvent = Event::factory()->create(['organisation_id' => $this->organisation->id]); RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'label' => 'Shirtmaat', 'slug' => 'shirtmaat', ]); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$otherEvent->id}/registration-fields", [ 'label' => 'Shirtmaat', 'field_type' => 'text', ]); $response->assertCreated() ->assertJsonPath('data.slug', 'shirtmaat'); } public function test_update_field(): void { $field = RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'label' => 'Old Label', 'slug' => 'old-label', ]); Sanctum::actingAs($this->orgAdmin); $response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/{$field->id}", [ 'label' => 'New Label', ]); $response->assertOk() ->assertJsonPath('data.label', 'New Label') ->assertJsonPath('data.slug', 'new-label'); } public function test_cannot_change_field_type(): void { $field = RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'field_type' => RegistrationFieldType::TEXT, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->putJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/{$field->id}", [ 'field_type' => 'select', ]); // field_type is not in UpdateRegistrationFormFieldRequest rules, so it's ignored $response->assertOk(); $this->assertDatabaseHas('registration_form_fields', [ 'id' => $field->id, 'field_type' => 'text', ]); } public function test_destroy_deletes_field(): void { $field = RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->deleteJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/{$field->id}"); $response->assertNoContent(); $this->assertDatabaseMissing('registration_form_fields', [ 'id' => $field->id, ]); } public function test_reorder_fields(): void { $fieldA = RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'sort_order' => 0, ]); $fieldB = RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'sort_order' => 1, ]); $fieldC = RegistrationFormField::factory()->create([ 'event_id' => $this->event->id, 'sort_order' => 2, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/reorder", [ 'ids' => [$fieldC->id, $fieldA->id, $fieldB->id], ]); $response->assertNoContent(); $this->assertDatabaseHas('registration_form_fields', ['id' => $fieldC->id, 'sort_order' => 0]); $this->assertDatabaseHas('registration_form_fields', ['id' => $fieldA->id, 'sort_order' => 1]); $this->assertDatabaseHas('registration_form_fields', ['id' => $fieldB->id, 'sort_order' => 2]); } public function test_import_from_event(): void { $sourceEvent = Event::factory()->create(['organisation_id' => $this->organisation->id]); RegistrationFormField::factory()->create([ 'event_id' => $sourceEvent->id, 'label' => 'Shirtmaat', 'slug' => 'shirtmaat', 'field_type' => RegistrationFieldType::SELECT, 'options' => ['S', 'M', 'L'], ]); RegistrationFormField::factory()->create([ 'event_id' => $sourceEvent->id, 'label' => 'Opmerkingen', 'slug' => 'opmerkingen', 'field_type' => RegistrationFieldType::TEXTAREA, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/import-from-event", [ 'source_event_id' => $sourceEvent->id, ]); $response->assertOk(); $this->assertDatabaseHas('registration_form_fields', [ 'event_id' => $this->event->id, 'slug' => 'shirtmaat', ]); $this->assertDatabaseHas('registration_form_fields', [ 'event_id' => $this->event->id, 'slug' => 'opmerkingen', ]); } public function test_import_from_different_org_rejected(): void { $otherEvent = Event::factory()->create(['organisation_id' => $this->otherOrganisation->id]); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/import-from-event", [ 'source_event_id' => $otherEvent->id, ]); $response->assertUnprocessable() ->assertJsonValidationErrors('source_event_id'); } public function test_from_template_creates_copy(): void { $template = RegistrationFieldTemplate::factory()->selectField()->create([ 'organisation_id' => $this->organisation->id, ]); Sanctum::actingAs($this->orgAdmin); $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields/from-template", [ 'template_id' => $template->id, ]); $response->assertCreated() ->assertJsonPath('data.label', $template->label) ->assertJsonPath('data.field_type', $template->field_type->value); $this->assertDatabaseHas('registration_form_fields', [ 'event_id' => $this->event->id, 'label' => $template->label, ]); } public function test_cross_org_returns_403(): void { Sanctum::actingAs($this->outsider); $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields"); $response->assertForbidden(); } public function test_unauthenticated_returns_401(): void { $response = $this->getJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/registration-fields"); $response->assertUnauthorized(); } }