seed(RoleSeeder::class); $this->org = Organisation::factory()->create(); $this->admin = User::factory()->create(); $this->org->users()->attach($this->admin, ['role' => 'org_admin']); $this->schema = FormSchema::factory()->create(['organisation_id' => $this->org->id]); } public function test_store_rejects_unregistered_rule_type(): void { Sanctum::actingAs($this->admin); $response = $this->postJson( "/api/v1/organisations/{$this->org->id}/forms/schemas/{$this->schema->id}/fields", [ 'field_type' => FormFieldType::TEXT->value, 'slug' => 'voornaam', 'label' => 'Voornaam', 'validation_rules' => [ ['rule_type' => 'not_a_real_rule', 'parameters' => []], ], ], ); $response->assertStatus(422); $this->assertArrayHasKey('validation_rules', $response->json('errors') ?? []); } public function test_store_rejects_bad_parameter_shape(): void { Sanctum::actingAs($this->admin); $response = $this->postJson( "/api/v1/organisations/{$this->org->id}/forms/schemas/{$this->schema->id}/fields", [ 'field_type' => FormFieldType::TEXT->value, 'slug' => 'voornaam', 'label' => 'Voornaam', 'validation_rules' => [ ['rule_type' => 'min_length', 'parameters' => ['value' => 'not-an-int']], ], ], ); $response->assertStatus(422); } public function test_store_rejects_regex_missing_pattern(): void { Sanctum::actingAs($this->admin); $response = $this->postJson( "/api/v1/organisations/{$this->org->id}/forms/schemas/{$this->schema->id}/fields", [ 'field_type' => FormFieldType::TEXT->value, 'slug' => 'postcode', 'label' => 'Postcode', 'validation_rules' => [ ['rule_type' => 'regex', 'parameters' => []], ], ], ); $response->assertStatus(422); } public function test_store_rejects_unregistered_callback_key(): void { Config::set('form_builder.validation_callbacks', []); Sanctum::actingAs($this->admin); $response = $this->postJson( "/api/v1/organisations/{$this->org->id}/forms/schemas/{$this->schema->id}/fields", [ 'field_type' => FormFieldType::TEXT->value, 'slug' => 'kvk', 'label' => 'KvK-nummer', 'validation_rules' => [ ['rule_type' => 'callback', 'parameters' => ['key' => 'not_registered']], ], ], ); $response->assertStatus(422); } public function test_store_accepts_valid_specs_and_persists_rows(): void { Sanctum::actingAs($this->admin); $response = $this->postJson( "/api/v1/organisations/{$this->org->id}/forms/schemas/{$this->schema->id}/fields", [ 'field_type' => FormFieldType::TEXT->value, 'slug' => 'voornaam', 'label' => 'Voornaam', 'validation_rules' => [ ['rule_type' => 'min_length', 'parameters' => ['value' => 2]], ['rule_type' => 'max_length', 'parameters' => ['value' => 40]], ], ], ); $response->assertCreated(); $fieldId = $response->json('data.id'); $rules = FormFieldValidationRule::query() ->where('owner_type', 'form_field') ->where('owner_id', $fieldId) ->pluck('rule_type') ->map(static fn ($r) => $r instanceof \BackedEnum ? $r->value : (string) $r) ->sort()->values()->all(); $this->assertSame(['max_length', 'min_length'], $rules); // The JSON column is not written on the service path — stays null // until commit 5 drops it. $this->assertNull(FormField::query()->findOrFail($fieldId)->validation_rules); } public function test_update_empty_array_clears_rules(): void { Sanctum::actingAs($this->admin); $field = FormField::factory()->create(['form_schema_id' => $this->schema->id]); FormFieldValidationRule::factory()->forField($field)->create(); $this->assertCount(1, $field->fresh()->validationRules); $response = $this->putJson( "/api/v1/organisations/{$this->org->id}/forms/schemas/{$this->schema->id}/fields/{$field->id}", ['validation_rules' => []], ); $response->assertOk(); $this->assertCount(0, $field->fresh()->validationRules); } }