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>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
|
vi.mock('../../resources/js/api/client', () => ({
|
|
apiClient: {
|
|
get: vi.fn(),
|
|
post: vi.fn(),
|
|
},
|
|
ensureCsrfCookie: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
import {
|
|
fetchTelegram,
|
|
createTelegram,
|
|
fetchTelegramCampaign,
|
|
launchTelegram,
|
|
} from '../../resources/js/api/telegram';
|
|
import { apiClient, ensureCsrfCookie } from '../../resources/js/api/client';
|
|
|
|
describe('api/telegram', () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
it('fetchTelegram() GETs /api/telegram/campaigns and unwraps', async () => {
|
|
vi.mocked(apiClient.get).mockResolvedValue({
|
|
data: { campaigns: [], sandbox: true, balance_rub: '100.00', frozen_rub: '0.00' },
|
|
});
|
|
const res = await fetchTelegram();
|
|
expect(apiClient.get).toHaveBeenCalledWith('/api/telegram/campaigns');
|
|
expect(res.sandbox).toBe(true);
|
|
expect(res.balance_rub).toBe('100.00');
|
|
expect(res.campaigns).toEqual([]);
|
|
});
|
|
|
|
it('createTelegram() ensures csrf then POSTs /api/telegram/campaigns with payload', async () => {
|
|
vi.mocked(apiClient.post).mockResolvedValue({
|
|
data: {
|
|
id: 1,
|
|
status: 'draft',
|
|
ad_text: 'Приходите',
|
|
ad_link: 'https://t.me/x',
|
|
audience_kind: 'list',
|
|
planned_count: 2,
|
|
estimated_cost_rub: '10.00',
|
|
},
|
|
});
|
|
const payload = {
|
|
ad_text: 'Приходите',
|
|
ad_link: 'https://t.me/x',
|
|
audience_kind: 'list' as const,
|
|
budget_cap_rub: '1000.00',
|
|
phones: ['79990000001', '79990000002'],
|
|
};
|
|
const c = await createTelegram(payload);
|
|
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
|
|
expect(apiClient.post).toHaveBeenCalledWith('/api/telegram/campaigns', payload);
|
|
expect(c.id).toBe(1);
|
|
expect(c.status).toBe('draft');
|
|
expect(c.planned_count).toBe(2);
|
|
});
|
|
|
|
it('fetchTelegramCampaign(id) GETs /api/telegram/campaigns/{id} and returns {campaign}', async () => {
|
|
vi.mocked(apiClient.get).mockResolvedValue({
|
|
data: { campaign: { id: 3, status: 'draft_ready', ad_text: 'Y', ad_link: 'https://t.me/y' } },
|
|
});
|
|
const res = await fetchTelegramCampaign(3);
|
|
expect(apiClient.get).toHaveBeenCalledWith('/api/telegram/campaigns/3');
|
|
expect(res.campaign.id).toBe(3);
|
|
expect(res.campaign.status).toBe('draft_ready');
|
|
});
|
|
|
|
it('launchTelegram(id) ensures csrf then POSTs /api/telegram/campaigns/{id}/launch', async () => {
|
|
vi.mocked(apiClient.post).mockResolvedValue({
|
|
data: { id: 5, status: 'queued', ad_text: 'Z', ad_link: 'https://t.me/z' },
|
|
});
|
|
const res = await launchTelegram(5);
|
|
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
|
|
expect(apiClient.post).toHaveBeenCalledWith('/api/telegram/campaigns/5/launch');
|
|
expect(res.status).toBe('queued');
|
|
});
|
|
});
|