Files
portal/app/tests/Feature/ClientTg/InputLimitsTest.php
T

89 lines
4.8 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
/**
* Верхние пределы на вход в CampaignController::store (находка #13, задача 1.3).
* `phones`/`audience_days` без верхнего предела → клиент может прислать миллион
* номеров (нагрузка/DoS). `ad_text`/`ad_link` НЕ трогаем — уже ограничены.
*
* dropped_count — число нераспознанных (мусорных) номеров, отброшенных
* storePhones молча; добавочный ключ в ответе store, не ломает плоский контракт.
*
* Auth/tenant — как в CampaignApiTest. Синтетические номера 7999… — реальные НИКОГДА.
*/
uses(RefreshDatabase::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
$this->actingAs($this->user);
});
/** @return array<string, mixed> валидный 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);
});