Files
portal/app/tests/Frontend/telegram-data-starta.spec.ts
T
Дмитрий 337f6b9897 feat телеграм: дата старта показов, признак живости системы и человеческий язык ошибок
Дата старта (решение владельца 06.08.2026). Портал не передаёт кабинету МТС ни одной
даты — их ставит кабинет своими умолчаниями. Живой прогон показал: старт оказался
ЗАВТРАШНИМ, тогда как экран обещал клиенту показы «7 дней», подразумевая сегодня.
Робот читает день начала из ТОЙ ЖЕ строки списка, куда и так ходит за вердиктом —
ни одного лишнего захода в кабинет; портал хранит его в client_tg_campaigns.starts_on
и показывает клиенту «Показы начнутся 7 августа». Проверено на ЖИВОМ кабинете:
три задания подряд вернули startDate 2026-08-07 по кампании МТС 2237821.
Мастер перестал молчать о том, что день начала ставит кабинет, а не мы.

Ф-2, карточка приёмки Т-Ф4. У кампаний в движении видно «Проверяли 5 минут назад».
Считаются только ЗАКОНЧЕННЫЕ проверки, включая неудачные: задание в очереди работой
не является, а неудачная проверка — всё равно признак жизни. Именно в такой тишине
владелец 36 часов не знал, что робот вообще не может войти в кабинет.

Ф-3. Имена полей в ошибках формы по-русски: «Лимит на объявление не может быть меньше
1 ₽» вместо «Поле budget cap rub должно быть не меньше 1». Серая кнопка «Запустить»
называет причину и шаг, куда вернуться, а не гаснет молча.

Карточки Т-Р3 и Т-Р4 закрыты тестами (живьём не воспроизвести). Попутно найдено: обе
защиты, стерегущие ЕДИНСТВЕННОЕ место траты живых денег роботом, были без единого
теста. Сторожа доказаны вырезанием.

Тесты: ClientTg 378 зелёных, экраны 2158, робот 197. Статанализ 0, стиль 0, типы чисто.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:33:35 +03:00

135 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
/**
* Дата старта показов на экране клиента.
*
* 🔴 Откуда взялось. Портал не передаёт кабинету МТС ни одной даты — их ставит сам
* кабинет своими умолчаниями. Живьём 06.08.2026 (кампания 2237821) старт оказался
* ЗАВТРАШНИМ, а экран обещал показы «7 дней», подразумевая сегодня. Клиент ждёт
* рекламу сегодня, её нет, и объяснить нечем.
*
* Дату теперь привозит робот вместе с вердиктом модерации. Экран обязан её показать —
* и обязан МОЛЧАТЬ, пока даты нет: выдуманная дата хуже отсутствующей.
*/
vi.mock('vue-router', () => ({
useRouter: vi.fn(() => ({ push: vi.fn() })),
}));
const { fetchTelegramMock } = vi.hoisted(() => ({
fetchTelegramMock: vi.fn(),
}));
vi.mock('../../resources/js/api/telegram', () => ({
fetchTelegram: fetchTelegramMock,
createTelegram: vi.fn(),
fetchTelegramCampaign: vi.fn(() => Promise.resolve({ campaign: { id: 1 } })),
launchTelegram: vi.fn(),
resubmitTelegram: vi.fn(),
uploadTelegramMedia: vi.fn(),
estimateTelegram: vi.fn(() =>
Promise.resolve({ planned_count: 512, estimated_cost_rub: '430.08', min_count: 367, enough: true }),
),
uploadAutoRuleMedia: vi.fn(),
MEDIA_ACCEPT: 'image/jpeg,image/png,video/mp4',
MEDIA_TREBOVANIYA: 'Картинка: JPEG или PNG, до 25 МБ, от 640×360 до 5120×2880.',
MEDIA_CENA: 'С картинкой показ стоит дороже: 600 ₽ за тысячу, с видео — 680 ₽.',
fetchAutoRule: vi.fn(() =>
Promise.resolve({
enabled: false,
ad_text: '',
ad_link: '',
ad_headline: '',
media_path: null,
ord_category: 'Размещение рекламы',
budget_cap_rub: '0.00',
daily_limit_rub: '0.00',
batch_threshold: null,
}),
),
saveAutoRule: vi.fn(),
}));
import AdvertisingTelegramView from '../../resources/js/views/advertising/AdvertisingTelegramView.vue';
function otvet(kampaniya: Record<string, unknown>) {
return {
campaigns: [kampaniya],
sandbox: false,
balance_rub: '1000.00',
frozen_rub: '0.00',
};
}
const BAZA = {
id: 40,
ad_text: 'Приходите к нам',
ad_link: 'https://t.me/x',
planned_count: 400,
estimated_cost_rub: '268.80',
};
function smontirovat() {
return mount(AdvertisingTelegramView, { global: { plugins: [createVuetify()] } });
}
describe('дата старта показов на экране клиента', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('пока идёт модерация, видно с какого дня пойдут показы', async () => {
fetchTelegramMock.mockResolvedValue(
otvet({ ...BAZA, status: 'moderating', starts_on: '2026-08-07' }),
);
const wrapper = smontirovat();
await flushPromises();
const stroka = wrapper.find('[data-testid="tg-start-date-40"]');
expect(stroka.exists()).toBe(true);
// По-человечески, а не «2026-08-07»: заказчик не программист.
expect(stroka.text()).toContain('7 августа');
});
it('у запущенной кампании дата тоже видна', async () => {
fetchTelegramMock.mockResolvedValue(
otvet({ ...BAZA, status: 'launched', starts_on: '2026-12-31' }),
);
const wrapper = smontirovat();
await flushPromises();
expect(wrapper.find('[data-testid="tg-start-date-40"]').text()).toContain('31 декабря');
});
// 🪤 Робот ещё не сходил в кабинет — даты нет. Показать вместо неё «сегодня» или
// пустое место со словом «начнётся» значило бы соврать клиенту.
it('даты нет — строки нет вовсе, ничего не выдумываем', async () => {
fetchTelegramMock.mockResolvedValue(
otvet({ ...BAZA, status: 'moderating', starts_on: null }),
);
const wrapper = smontirovat();
await flushPromises();
expect(wrapper.find('[data-testid="tg-start-date-40"]').exists()).toBe(false);
// Подсказка про модерацию при этом никуда не делась.
expect(wrapper.find('[data-testid="tg-moderating-hint-40"]').exists()).toBe(true);
});
// 🪤 Старое поле могло приехать битым (кабинет сменил формат, чужая правка).
// Битую дату не показываем — но и экран из-за неё не роняем.
it('битая дата не показывается и экран не роняет', async () => {
fetchTelegramMock.mockResolvedValue(
otvet({ ...BAZA, status: 'moderating', starts_on: 'завтра' }),
);
const wrapper = smontirovat();
await flushPromises();
expect(wrapper.find('[data-testid="tg-start-date-40"]').exists()).toBe(false);
});
});