tenant = Tenant::factory()->create(); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); }); /** Столько контактов в своей базе тенанта. */ function zavestiKontakty(int $tenantId, int $skolko, int $ot = 0): void { for ($i = $ot; $i < $ot + $skolko; $i++) { Contact::create([ 'tenant_id' => $tenantId, 'phone' => sprintf('7999%07d', $i), 'name' => null, 'operator' => null, ]); } } it('🔴 считает охват и смету, НЕ создавая кампанию в базе', function () { zavestiKontakty($this->tenant->id, 5); $otvet = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ]); $otvet->assertOk()->assertJsonStructure(['planned_count', 'estimated_cost_rub', 'min_count', 'enough']); expect($otvet->json('planned_count'))->toBe(5); // Главное: в базе не осталось ни одного черновика. Иначе живой счётчик за минуту // работы клиента наплодил бы десятки кампаний-призраков. expect(Campaign::count())->toBe(0); }); it('смета совпадает с той, что даёт создание черновика — иначе счётчик врёт перед оплатой', function () { zavestiKontakty($this->tenant->id, 7); $ocenka = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertOk(); $chernovik = $this->postJson('/api/telegram/campaigns', [ 'ad_text' => 'Заходите', 'ad_link' => 'https://t.me/example', 'audience_kind' => Campaign::AUDIENCE_BASE, 'budget_cap_rub' => '1000.00', ])->assertCreated(); expect($ocenka->json('planned_count'))->toBe($chernovik->json('planned_count')); expect($ocenka->json('estimated_cost_rub'))->toBe($chernovik->json('estimated_cost_rub')); }); it('сделки за период: то, что старше окна, в охват не идёт', function () { Deal::factory()->create([ 'tenant_id' => $this->tenant->id, 'phone' => '79990000001', 'phone_operator' => 'МТС', 'received_at' => now()->subDays(2), ]); Deal::factory()->create([ 'tenant_id' => $this->tenant->id, 'phone' => '79990000002', 'phone_operator' => 'МТС', 'received_at' => now()->subDays(30), ]); $otvet = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_DEALS, 'audience_days' => 5, ])->assertOk(); expect($otvet->json('planned_count'))->toBe(1); }); it('свой список: считает по присланным номерам и схлопывает дубли', function () { $otvet = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_LIST, 'phones' => ['79990000001', '8 999 000 00 01', '79990000002', 'ерунда'], ])->assertOk(); // Первые два — один и тот же человек в разных записях, третий отдельный, четвёртый не номер. expect($otvet->json('planned_count'))->toBe(2); // И тут тоже ничего не записалось — ни кампании, ни номеров. expect(Campaign::count())->toBe(0); }); it('отписавшихся в охват не считает — как и создание черновика', function () { zavestiKontakty($this->tenant->id, 3); Optout::create(['tenant_id' => $this->tenant->id, 'phone' => '79990000000']); $otvet = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertOk(); expect($otvet->json('planned_count'))->toBe(2); }); it('чужих контактов не видит', function () { $chuzhoy = Tenant::factory()->create(); zavestiKontakty($chuzhoy->id, 40, 500); zavestiKontakty($this->tenant->id, 3); $otvet = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertOk(); expect($otvet->json('planned_count'))->toBe(3); }); it('говорит, хватает ли людей для площадки — 367 это её правило, не наше', function () { zavestiKontakty($this->tenant->id, 5); $malo = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertOk(); expect($malo->json('min_count'))->toBe(367); expect($malo->json('enough'))->toBeFalse(); zavestiKontakty($this->tenant->id, 400, 1000); $hvatit = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertOk(); expect($hvatit->json('enough'))->toBeTrue(); }); it('пустая своя база — честный ноль, а не отказ', function () { $otvet = $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertOk(); expect($otvet->json('planned_count'))->toBe(0); expect($otvet->json('enough'))->toBeFalse(); }); it('незнакомый вид аудитории отвергается, а не считается нулём молча', function () { $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => 'chto-to-svoyo', ])->assertStatus(422); }); it('сделки без числа дней отвергаются — иначе молча посчитаем за всё время', function () { $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_DEALS, ])->assertStatus(422); }); it('без входа в кабинет ручка недоступна', function () { app('auth')->forgetGuards(); $this->postJson('/api/telegram/campaigns/estimate', [ 'audience_kind' => Campaign::AUDIENCE_BASE, ])->assertStatus(401); });