From ec35cd1931f904ceca8778ff4f835ee463d54f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 8 Aug 2026 23:21:14 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=BE=D0=B1=D0=BE=D1=82:=20=D1=87=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=E2=80=94=20=D0=B1=D0=B5=D0=BB=D1=8B=D0=B9=20=D1=81=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BE=D0=BA=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=BA=D0=B0=D0=B7=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D1=81=D1=82=D0=B2=D0=BE=20=D0=BD=D0=B0=D0=B6=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Отдельным файлом от похода в кабинет нарочно: сам поход тестом не поймать, а «что жать» и «нажалось ли» — обычная логика, и опасные случаи живут именно в ней. 🔴 Нажатие необратимо и делается в боевом кабинете за живые деньги, а рядом там живут кнопки удаления. Поэтому: подпись кнопки сверяется полным словом, а не «похожим» («Завершить кампанию досрочно» — уже другая кнопка), и в окне подтверждения жмётся только кнопка из белого списка; незнакомое окно не трогаем вовсе — лучше отказ, чем удалённая кампания клиента. Доказательством остановки считается только смена слова кабинета на «Останавливается» или «Завершена». «Наверное получилось» не считается: отчитаться успехом, не проверив, значит сказать клиенту «остановлено» о рекламе, которая продолжает крутиться. Co-Authored-By: Claude Opus 5 (1M context) --- bots/mts-telegram-ads/src/stop-plan.js | 41 +++++++++++++++++++ bots/mts-telegram-ads/test/stop-plan.test.mjs | 31 ++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 bots/mts-telegram-ads/src/stop-plan.js create mode 100644 bots/mts-telegram-ads/test/stop-plan.test.mjs diff --git a/bots/mts-telegram-ads/src/stop-plan.js b/bots/mts-telegram-ads/src/stop-plan.js new file mode 100644 index 00000000..2282b013 --- /dev/null +++ b/bots/mts-telegram-ads/src/stop-plan.js @@ -0,0 +1,41 @@ +// Решения остановки, которые можно проверить без браузера. Отдельным файлом нарочно: сам +// поход в кабинет тестом не поймать, а «что жать» и «нажалось ли» — обычная логика, +// и именно в ней живут опасные случаи. +// +// 🔴 Нажатие необратимо и делается в БОЕВОМ кабинете за живые деньги: у завершённой +// кампании кнопок запуска больше нет (замер 08.08.2026, кампания 2237821). + +/** + * Подпись кнопки завершения в ряду кампании. Полное совпадение, без «похожих»: + * «Завершить кампанию досрочно» — это уже другая кнопка, и жать её вслепую нельзя. + */ +export const NADPIS_ZAVERSHIT = /^завершить$/i; + +/** Белый список кнопок окна подтверждения. Рядом в кабинете живут кнопки удаления. */ +const PODTVERZHDENIE = [/^завершить$/i, /^да$/i, /^подтвердить$/i, /^ок$/i]; + +/** + * Какую кнопку окна подтверждения жать. null — окно незнакомое, не трогаем ничего: + * лучше отчитаться отказом, чем удалить кампанию клиента. + */ +export function vybratPodtverzhdenie(nadpisi) { + if (!Array.isArray(nadpisi)) return null; + + for (const obrazec of PODTVERZHDENIE) { + const nayden = nadpisi.find((t) => obrazec.test(String(t ?? '').trim())); + if (nayden !== undefined) return nayden; + } + + return null; +} + +/** + * Кабинет подтвердил остановку? Доказательство — только смена слова в ряду: + * «Останавливается» (идёт остановка) или «Завершена» (уже закончилась). + * + * 🔴 «Наверное получилось» не считается. Отчитаться успехом, не проверив, значит + * сказать клиенту «остановлено» о рекламе, которая продолжает крутиться за его деньги. + */ +export function ostanovkaPodtverzhdena(status) { + return status === 'stopping' || status === 'completed'; +} diff --git a/bots/mts-telegram-ads/test/stop-plan.test.mjs b/bots/mts-telegram-ads/test/stop-plan.test.mjs new file mode 100644 index 00000000..ccd179d3 --- /dev/null +++ b/bots/mts-telegram-ads/test/stop-plan.test.mjs @@ -0,0 +1,31 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { vybratPodtverzhdenie, ostanovkaPodtverzhdena, NADPIS_ZAVERSHIT } from '../src/stop-plan.js'; + +// 🔴 Нажатие необратимо и делается в БОЕВОМ кабинете за живые деньги. Рядом в кабинете +// живут кнопки удаления — ничего «похожего» вслепую не жмём. + +test('в окне подтверждения жмём только кнопку из белого списка', () => { + assert.equal(vybratPodtverzhdenie(['Отмена', 'Завершить']), 'Завершить'); + assert.equal(vybratPodtverzhdenie(['Нет', 'Да']), 'Да'); +}); + +test('🔴 похожей кнопки мало — незнакомое окно не трогаем', () => { + assert.equal(vybratPodtverzhdenie(['Удалить кампанию', 'Отмена']), null); + assert.equal(vybratPodtverzhdenie([]), null); + assert.equal(vybratPodtverzhdenie(null), null); +}); + +test('подпись кнопки завершения — полное слово, а не «похожее»', () => { + assert.match('Завершить', NADPIS_ZAVERSHIT); + assert.equal(NADPIS_ZAVERSHIT.test('Завершить кампанию досрочно'), false); + assert.equal(NADPIS_ZAVERSHIT.test('Посмотреть настройки'), false); +}); + +test('🔴 доказательством остановки считаются ровно два слова кабинета', () => { + assert.equal(ostanovkaPodtverzhdena('stopping'), true); + assert.equal(ostanovkaPodtverzhdena('completed'), true); + // Слово не сменилось — значит не нажалось. «Наверное получилось» не считается. + assert.equal(ostanovkaPodtverzhdena('approved'), false); + assert.equal(ostanovkaPodtverzhdena(null), false); +});