From 2ffbb49faaeb4402b52f32aa8610166157efd3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 11 May 2026 18:38:52 +0300 Subject: [PATCH] fix(projects): Plan 5 Task 3 code-review fixes (2 Important + 2 Minor) Co-Authored-By: Claude Sonnet 4.6 --- app/app/Http/Requests/StoreProjectRequest.php | 4 ++-- .../2026_05_11_150000_add_limits_to_tenants.php | 3 +++ app/phpstan-baseline.neon | 2 +- .../Feature/Plan5/Projects/ProjectsStoreTest.php | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/app/Http/Requests/StoreProjectRequest.php b/app/app/Http/Requests/StoreProjectRequest.php index 0ef002b2..3fa6fe5b 100644 --- a/app/app/Http/Requests/StoreProjectRequest.php +++ b/app/app/Http/Requests/StoreProjectRequest.php @@ -28,13 +28,13 @@ class StoreProjectRequest extends FormRequest ]; if ($signalType === 'site') { - $base['signal_identifier'] = ['required', 'string', 'regex:/^[a-z0-9.\-]+\.[a-z]{2,}$/i']; + $base['signal_identifier'] = ['required', 'string', 'regex:/^[a-z0-9][a-z0-9\-]*(\.[a-z0-9][a-z0-9\-]*)*\.[a-z]{2,}$/i']; } elseif ($signalType === 'call') { $base['signal_identifier'] = ['required', 'string', 'regex:/^7\d{10}$/']; } elseif ($signalType === 'sms') { $base['sms_senders'] = ['required', 'array', 'min:1']; $base['sms_senders.*'] = ['string', 'max:11']; - $base['sms_keyword'] = ['nullable', 'string', 'max:50']; + $base['sms_keyword'] = ['nullable', 'string', 'min:1', 'max:50']; } return $base; diff --git a/app/database/migrations/2026_05_11_150000_add_limits_to_tenants.php b/app/database/migrations/2026_05_11_150000_add_limits_to_tenants.php index 3c2ab745..f6d12d79 100644 --- a/app/database/migrations/2026_05_11_150000_add_limits_to_tenants.php +++ b/app/database/migrations/2026_05_11_150000_add_limits_to_tenants.php @@ -16,6 +16,9 @@ return new class extends Migration { public function up(): void { + if (Schema::hasColumn('tenants', 'limits')) { + return; + } Schema::table('tenants', function (Blueprint $table) { // limits JSONB: {"max_users":5,"max_projects":10,"api_rps":60} // Аналог limits в tariff_plans — per-tenant override лимитов тарифа. diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon index 99c28436..1d5c5d8b 100644 --- a/app/phpstan-baseline.neon +++ b/app/phpstan-baseline.neon @@ -867,7 +867,7 @@ parameters: - message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:actingAs\(\)\.$#' identifier: method.notFound - count: 8 + count: 9 path: tests/Feature/Plan5/Projects/ProjectsStoreTest.php - diff --git a/app/tests/Feature/Plan5/Projects/ProjectsStoreTest.php b/app/tests/Feature/Plan5/Projects/ProjectsStoreTest.php index 1f16e217..5ef14afd 100644 --- a/app/tests/Feature/Plan5/Projects/ProjectsStoreTest.php +++ b/app/tests/Feature/Plan5/Projects/ProjectsStoreTest.php @@ -25,6 +25,7 @@ it('creates a site project with valid payload', function () { ]); $response->assertCreated(); + $response->assertJsonPath('data.sync_status', 'pending'); expect(Project::where('signal_identifier', 'okna-spb.ru')->exists())->toBeTrue(); Queue::assertPushed(SyncSupplierProjectJob::class); }); @@ -129,3 +130,17 @@ it('forces tenant_id from auth user (not from payload)', function () { $project = Project::where('signal_identifier', 'x.ru')->latest()->first(); expect($project->tenant_id)->toBe($tenantA->id); }); + +it('rejects site domain with consecutive dots', function () { + $tenant = Tenant::factory()->create(); + $user = User::factory()->create(['tenant_id' => $tenant->id]); + + $response = $this->actingAs($user)->postJson('/api/projects', [ + 'name' => 'X', 'signal_type' => 'site', 'signal_identifier' => 'okna..spb.ru', + 'daily_limit_target' => 50, 'region_mask' => 0, 'region_mode' => 'include', + 'delivery_days_mask' => 127, + ]); + + $response->assertStatus(422); + $response->assertJsonValidationErrors(['signal_identifier']); +});