refactor(form-builder): strict validator on save; strip rules.unique fallback
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\FormBuilder\ValidationRules;
|
||||
|
||||
use App\Enums\FormBuilder\FormFieldType;
|
||||
use App\Models\FormBuilder\FormField;
|
||||
use App\Models\FormBuilder\FormFieldValidationRule;
|
||||
use App\Models\FormBuilder\FormSchema;
|
||||
use App\Models\Organisation;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RoleSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* WS-5b commit 3 — FormRequest level strict validation of the
|
||||
* `validation_rules` array-of-specs shape. Unknown rule_type, bad
|
||||
* parameter shape, unregistered callback key all return 422 BEFORE any
|
||||
* write lands.
|
||||
*/
|
||||
final class FormFieldStrictValidationRulesRequestTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Organisation $org;
|
||||
|
||||
private User $admin;
|
||||
|
||||
private FormSchema $schema;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\FormBuilder\ValidationRules;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* WS-5b consolidation — static guard that the legacy
|
||||
* `validation_rules.unique` JSON fallback has been stripped from
|
||||
* `FormValueService`. The is_unique column is the single source of truth.
|
||||
*
|
||||
* We assert the textual absence rather than a behavioural test: uniqueness
|
||||
* enforcement is heavily coupled to the FormValueObserver typed-column
|
||||
* write path, submission status, etc. The cheap + high-signal check is
|
||||
* "the code no longer references `$rules['unique']`".
|
||||
*/
|
||||
final class UniqueJsonFallbackRemovedTest extends TestCase
|
||||
{
|
||||
public function test_form_value_service_no_longer_reads_rules_unique_key(): void
|
||||
{
|
||||
$source = (string) file_get_contents(
|
||||
__DIR__.'/../../../../app/Services/FormBuilder/FormValueService.php',
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString("\$rules['unique']", $source);
|
||||
$this->assertStringNotContainsString('$rules["unique"]', $source);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user