0c9642f3d0
Сессия 3 плана docs/superpowers/plans/2026-07-27-client-telegram-ads-module.md. Клиент сам собирает объявление + аудиторию и жмёт «Запустить»; робот кабинета МТС (в тестах замокан) доводит до черновика. Всё в песочнице: деньги/живой запуск выключены. - Клиентский API (Api\ClientTg\CampaignController + маршруты /api/telegram/*): создание (store) и запуск (launch) РАЗДЕЛЕНЫ. store создаёт ЧЕРНОВИК со сметой и числом кандидатов (деньги/робот не трогаются); launch draft→queued, в бою бронирует лимит на объявление (budget_cap_rub) и ставит RunTelegramCampaignJob. Нехватка денег → 409 (кампания остаётся черновиком); повторный запуск не-черновика → 422; изоляция тенантов (чужой show → 404). Валидация текст/ссылка/аудитория/бюджет; deals без срока → 422. - Фронт-API telegram.ts (зеркало client-sms.ts, уже): fetch/create/get/launch, cookie-сессия. - Экран AdvertisingTelegramView.vue (/advertising/telegram): форма (объявление, ссылка, 3 способа аудитории, лимит), кнопка «Запустить» (create→launch), баннер песочницы, список кампаний с человеческим ярлыком статуса, сообщение при нехватке денег. - Канал «Реклама Телеграм» АКТИВИРОВАН: advertisingChannels.ts route, провод в AppSidebar и AppMoreDrawer (route → реальный экран, остальные каналы — прежняя заглушка), маршрут в router. Тесты каналов обновлены (телеграм больше не заглушка). - Ручной сквозной сценарий (песочница): создать → запустить → джоб (робот замокан, draft) → draft_ready + matched виден в API. Отличие от СМС-близнеца: отдельного эндпоинта «предпросчёт до создания» нет — смету и кандидатов клиент видит в самом черновике (создание черновик денег не тратит). Проверки: 60/60 Pest ClientTg зелёные (+10 новых), 1512 Vitest зелёные (0 регрессий), phpstan 0 по своему коду (level 5), pint чисто, deptrac 0 нарушений. Робот в тестах замокан (кабинет МТС не тронут). Синтетические номера 7999… — реальных нет. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
3.4 KiB
PHP
72 lines
3.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Jobs\ClientTg\RunTelegramCampaignJob;
|
|
use App\Models\ClientTg\Campaign;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\ClientTg\RobotResult;
|
|
use App\Services\ClientTg\TelegramRobotRunner;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
|
|
/**
|
|
* Ручной сквозной сценарий в песочнице (план §Сессия 3, задача 3.4):
|
|
* создать (API) → запустить (API) → джоб (робот ЗАМОКАН, mode:draft) →
|
|
* статус draft_ready + matched виден клиенту в API.
|
|
*
|
|
* Робот кабинета МТС не трогаем (замокан). Синтетические номера 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);
|
|
config(['client_tg.sandbox' => true]);
|
|
});
|
|
|
|
it('песочница: создать → запустить → робот-черновик → draft_ready + matched виден в API', function () {
|
|
Queue::fake();
|
|
|
|
// Робот замокан: черновик готов, нашлось 2 совпадения в кабинете МТС.
|
|
$mock = Mockery::mock(TelegramRobotRunner::class);
|
|
$mock->shouldReceive('run')->once()->andReturn(new RobotResult(ok: true, matched: 2));
|
|
app()->instance(TelegramRobotRunner::class, $mock);
|
|
|
|
// 1. Клиент создаёт черновик из списка своих номеров.
|
|
$create = $this->postJson('/api/telegram/campaigns', [
|
|
'ad_text' => 'Заходите в наш канал',
|
|
'ad_link' => 'https://t.me/example_channel',
|
|
'audience_kind' => 'list',
|
|
'budget_cap_rub' => '1000.00',
|
|
'phones' => ['79990000001', '79990000002'],
|
|
]);
|
|
$create->assertCreated()->assertJsonPath('status', Campaign::STATUS_DRAFT);
|
|
$id = $create->json('id');
|
|
expect($create->json('planned_count'))->toBe(2);
|
|
|
|
// 2. Клиент жмёт «Запустить» — кампания в очереди, джоб поставлен.
|
|
$this->postJson("/api/telegram/campaigns/{$id}/launch")
|
|
->assertOk()
|
|
->assertJsonPath('status', Campaign::STATUS_QUEUED);
|
|
Queue::assertPushed(RunTelegramCampaignJob::class);
|
|
|
|
// 3. Отрабатывает воркер очереди (робот замокан) — доводит до черновика.
|
|
// Зовём handle через контейнер: под Queue::fake() dispatchSync не выполняется,
|
|
// а нам нужен реальный прогон джоба с замоканным роботом.
|
|
app()->call([new RunTelegramCampaignJob($id, $this->tenant->id), 'handle']);
|
|
|
|
// 4. Клиент видит в кабинете: черновик готов, совпадений — 2.
|
|
$this->getJson("/api/telegram/campaigns/{$id}")
|
|
->assertOk()
|
|
->assertJsonPath('campaign.status', Campaign::STATUS_DRAFT_READY)
|
|
->assertJsonPath('campaign.matched_count', 2);
|
|
|
|
// Список тоже отражает финальный статус.
|
|
$index = $this->getJson('/api/telegram/campaigns')->assertOk();
|
|
expect(collect($index->json('campaigns'))->firstWhere('id', $id)['status'])
|
|
->toBe(Campaign::STATUS_DRAFT_READY);
|
|
});
|