136 lines
5.6 KiB
PHP
136 lines
5.6 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use App\Jobs\ClientTg\RunTelegramCampaignJob;
|
||
|
|
use App\Models\ClientTg\AutoRule;
|
||
|
|
use App\Models\ClientTg\Campaign;
|
||
|
|
use App\Models\Tenant;
|
||
|
|
use App\Services\ClientTg\TelegramAutoAccumulator;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Illuminate\Support\Facades\Queue;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Авто-режим (план §Сессия 4, задача 4.1): новые лиды копятся в открытый черновик
|
||
|
|
* авто-кампании; при достижении порога (≥367 кандидатов; в тестах порог занижаем
|
||
|
|
* через config) черновик формируется в кампанию и ставится в очередь В РАМКАХ
|
||
|
|
* `budget_cap_rub` из авто-правила. Ниже порога — только копим.
|
||
|
|
*
|
||
|
|
* Отличие от СМС-близнеца: СМС шлёт по одной на каждый лид, Telegram копит ПАЧКУ
|
||
|
|
* (МТС показывает базе, а не по одному). Синтетические номера 7999… — реальных нет.
|
||
|
|
*/
|
||
|
|
uses(RefreshDatabase::class);
|
||
|
|
|
||
|
|
function makeTgAutoRule(int $tenantId, bool $enabled = true): AutoRule
|
||
|
|
{
|
||
|
|
return AutoRule::create([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'enabled' => $enabled,
|
||
|
|
'ad_text' => 'Заходите в наш канал',
|
||
|
|
'ad_link' => 'https://t.me/example_channel',
|
||
|
|
'ord_category' => 'Размещение рекламы',
|
||
|
|
'budget_cap_rub' => '2500.00',
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
function tgAccumulator(): TelegramAutoAccumulator
|
||
|
|
{
|
||
|
|
return app(TelegramAutoAccumulator::class);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @return list<string> */
|
||
|
|
function tgFakePhones(int $n, int $offset = 0): array
|
||
|
|
{
|
||
|
|
$out = [];
|
||
|
|
for ($i = 1; $i <= $n; $i++) {
|
||
|
|
// 7999 + 7 цифр — синтетический номер (ПДн реальных клиентов не касаемся).
|
||
|
|
$out[] = '7999'.str_pad((string) ($offset + $i), 7, '0', STR_PAD_LEFT);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $out;
|
||
|
|
}
|
||
|
|
|
||
|
|
beforeEach(function () {
|
||
|
|
Queue::fake();
|
||
|
|
$this->tenant = Tenant::factory()->create();
|
||
|
|
config(['client_tg.auto_batch_threshold' => 3]); // в тестах порог занижен
|
||
|
|
});
|
||
|
|
|
||
|
|
it('правило выключено — ничего не копит и не запускает', function () {
|
||
|
|
makeTgAutoRule($this->tenant->id, enabled: false);
|
||
|
|
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79990000001');
|
||
|
|
|
||
|
|
expect(Campaign::where('tenant_id', $this->tenant->id)->count())->toBe(0);
|
||
|
|
Queue::assertNothingPushed();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('нет правила вовсе — тихо выходим', function () {
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79990000001');
|
||
|
|
|
||
|
|
expect(Campaign::where('tenant_id', $this->tenant->id)->count())->toBe(0);
|
||
|
|
Queue::assertNothingPushed();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('ниже порога — копим в один черновик, в очередь не ставим', function () {
|
||
|
|
makeTgAutoRule($this->tenant->id);
|
||
|
|
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79990000001');
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79990000002');
|
||
|
|
|
||
|
|
$campaigns = Campaign::where('tenant_id', $this->tenant->id)->get();
|
||
|
|
expect($campaigns)->toHaveCount(1);
|
||
|
|
$draft = $campaigns->first();
|
||
|
|
expect($draft->status)->toBe(Campaign::STATUS_DRAFT);
|
||
|
|
expect($draft->audience_kind)->toBe(Campaign::AUDIENCE_LIST);
|
||
|
|
expect($draft->created_by)->toBeNull(); // авто-кампания — без автора
|
||
|
|
expect($draft->phones()->count())->toBe(2);
|
||
|
|
Queue::assertNothingPushed();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('повторный номер не задваивает пачку', function () {
|
||
|
|
makeTgAutoRule($this->tenant->id);
|
||
|
|
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79990000001');
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79990000001');
|
||
|
|
|
||
|
|
$draft = Campaign::where('tenant_id', $this->tenant->id)->first();
|
||
|
|
expect($draft->planned_count)->toBe(1); // кандидат один
|
||
|
|
Queue::assertNothingPushed();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('достигли порога — кампания в очереди, budget_cap из правила соблюдён', function () {
|
||
|
|
$rule = makeTgAutoRule($this->tenant->id);
|
||
|
|
|
||
|
|
foreach (tgFakePhones(3) as $phone) {
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, $phone);
|
||
|
|
}
|
||
|
|
|
||
|
|
$campaign = Campaign::where('tenant_id', $this->tenant->id)->first();
|
||
|
|
expect($campaign->status)->toBe(Campaign::STATUS_QUEUED);
|
||
|
|
expect($campaign->planned_count)->toBe(3);
|
||
|
|
expect((string) $campaign->budget_cap_rub)->toBe((string) $rule->budget_cap_rub);
|
||
|
|
Queue::assertPushed(RunTelegramCampaignJob::class, 1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('после запуска пачки следующий лид открывает новую пачку', function () {
|
||
|
|
makeTgAutoRule($this->tenant->id);
|
||
|
|
|
||
|
|
foreach (tgFakePhones(3) as $phone) { // первая пачка → в очередь
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, $phone);
|
||
|
|
}
|
||
|
|
tgAccumulator()->accumulate($this->tenant->id, '79991110001'); // новый лид
|
||
|
|
|
||
|
|
$drafts = Campaign::where('tenant_id', $this->tenant->id)
|
||
|
|
->where('status', Campaign::STATUS_DRAFT)->get();
|
||
|
|
expect($drafts)->toHaveCount(1);
|
||
|
|
expect($drafts->first()->phones()->count())->toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('порог пачки по умолчанию — 367', function () {
|
||
|
|
expect(config('client_tg.auto_batch_threshold'))->not->toBeNull();
|
||
|
|
// Значение по умолчанию читаем из свежесобранного конфига (без тестового override).
|
||
|
|
$default = require config_path('client_tg.php');
|
||
|
|
expect($default['auto_batch_threshold'])->toBe(367);
|
||
|
|
});
|