tenant = Tenant::factory()->create(); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); }); /** @return array валидный payload создания кампании по списку номеров. */ function limStorePayload(array $overrides = []): array { return array_merge([ 'ad_text' => 'Приходите к нам в канал', 'ad_link' => 'https://t.me/example_channel', 'audience_kind' => 'list', 'budget_cap_rub' => '1000.00', 'phones' => ['79990000001', '79990000002'], ], $overrides); } // 🪤 Полный HTTP-прогон с 200001 реальным элементом здесь НЕ используется: // замерено — 'phones' => 'array|max:200000' САМ ПО СЕБЕ отбрасывает 200001 // элементов за ~0.1с (Validator::fails() проверялся изолированно), но // соседнее правило 'phones.*' => 'string|max:32' — это Laravel wildcard-rule, // который валидирует КАЖДЫЙ элемент независимо от того, что 'phones' уже не // прошёл (bail/stopOnFirstFailure на это не влияют — разные top-level поля). // Замер роста: n=1000→0.09с, n=5000→0.37с, n=20000→2.8с (не линейно, ~n^1.8) — // на n=200001 это уже единицы минут за ОДИН прогон теста, что не пригодно для // регулярного TDD-цикла. Поэтому граница 200000 проверяется СТРУКТУРНО (текст // правила в контроллере), а не полным раннтайм-прогоном 200001 элементов. it('верхний предел phones задан в контроллере как max:200000 (структурная проверка — полный HTTP-прогон 200001 элемента через Laravel Validator занимает единицы минут из-за wildcard-правила phones.*, см. комментарий выше)', function () { $source = file_get_contents(app_path('Http/Controllers/Api/ClientTg/CampaignController.php')); expect($source)->toContain("'phones' => 'nullable|array|max:200000',"); }); it('phones немного за пределами разумного (500 валидных номеров) — 201, не ложное срабатывание', function () { Queue::fake(); $this->postJson('/api/telegram/campaigns', limStorePayload([ 'phones' => array_fill(0, 500, '79990000001'), ]))->assertCreated(); }); it('phones.* элемент длиннее 32 символов отклоняется — 422', function () { $this->postJson('/api/telegram/campaigns', limStorePayload([ 'phones' => [str_repeat('7', 40)], ]))->assertStatus(422)->assertJsonValidationErrors('phones.0'); }); it('audience_days=366 при audience_kind=deals отклоняется — 422', function () { $this->postJson('/api/telegram/campaigns', limStorePayload([ 'audience_kind' => 'deals', 'audience_days' => 366, 'phones' => null, ]))->assertStatus(422)->assertJsonValidationErrors('audience_days'); }); it('валидный запрос (2 номера) проходит — 201 и dropped_count=0', function () { $this->postJson('/api/telegram/campaigns', limStorePayload()) ->assertCreated() ->assertJsonPath('dropped_count', 0); }); it('валидный запрос с 1 мусорным номером — 201 и dropped_count=1', function () { $this->postJson('/api/telegram/campaigns', limStorePayload([ 'phones' => ['79990000001', 'абв'], ]))->assertCreated()->assertJsonPath('dropped_count', 1); });