d50a93e55c
Хвост №1 из STATE ревью-правок: оформили доказанный в A2 путь пересдачи в
постоянный режим робота mode:'resubmit'. Робот берёт ОТКЛОНЁННУЮ кампанию, входит
в её редактор через «Исправить», вносит исправления и повторно отправляет на
модерацию БЕЗ ОПЛАТЫ (0 ₽ — деньги не списываются).
Что нового:
- parseResubmitTask (task.js): своё задание пересдачи — campaignId + submitMode
(draft|live, без дефолта, чтобы live не случился сам) + правки на выбор
(moderatorFile и/или adText/buttonUrl/ordCategory). Нужна хоть одна правка —
иначе тот же контент снова отклонят. Номера/бюджет не нужны (уже у кампании).
- openResubmitEditor (cabinet.js): нативный клик «Исправить» в строке кампании по
id → ждём редактор /telegram-a2p/{id}/message (в чужую кампанию не лезем).
- submitWithoutPayment (cabinet.js): на /payment жмём ТОЛЬКО «Отправить на
модерацию без оплаты»; денежные кнопки («Списать…»/«Оплатить») — двойная защита
через isForbiddenButtonText, не жмём никогда.
- editResubmitFields + вынос fillAd в хелперы (fillAdText/fillAdLink/
selectOrdCategory/fillAdMedia): пересдача правит только заданные поля тем же
проверенным кодом (DRY, поведение fillAd не изменилось).
- runResubmit (runner.js) + ветка mode:'resubmit' в bin/run.js (до parseTask, как
read-status). draft — предохранитель (до /confirmation, не шлём); live — отправка
без оплаты.
Живая проверка на реальных отклонённых кампаниях (28.07.2026):
- DRAFT (2231132, правка текста + документ): вошёл «Исправить» → правка → загрузка
документа → /confirmation → НЕ отправил (resubmitted:false, stoppedAt:confirmation).
- LIVE (2231134, правка текст+ссылка+ОРД, без документа): полный цикл → «без оплаты»
(0 ₽) → resubmitted:true; read-status подтвердил moderating. Баланс не тронут.
Робот npm test 110/110 (было 94, +16). Приёмочный лист и живые результаты:
docs/superpowers/2026-07-28-robot-resubmit-mode-ACCEPT.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
3.5 KiB
JavaScript
83 lines
3.5 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { parseResubmitTask } from '../src/task.js';
|
|
|
|
// Пересдача (mode:'resubmit', задача 3.6 → robot-mode): чинит ОТКЛОНЁННУЮ кампанию
|
|
// по её id и повторно шлёт на модерацию без оплаты (0 ₽). Что чиним — на выбор:
|
|
// документ модератору (moderatorFile) и/или правка текста/ссылки/категории ОРД.
|
|
const base = {
|
|
mode: 'resubmit',
|
|
campaignId: '2231134',
|
|
submitMode: 'draft',
|
|
moderatorFile: 'C:/tmp/licenziya.pdf',
|
|
};
|
|
|
|
test('parseResubmitTask принимает задание с документом модератору', () => {
|
|
const t = parseResubmitTask(base);
|
|
assert.equal(t.campaignId, '2231134');
|
|
assert.equal(t.moderatorFile, 'C:/tmp/licenziya.pdf');
|
|
assert.equal(t.submitMode, 'draft');
|
|
});
|
|
|
|
test('parseResubmitTask требует campaignId (какую кампанию чинить)', () => {
|
|
const { campaignId, ...rest } = base;
|
|
assert.throws(() => parseResubmitTask(rest), /campaignId/);
|
|
});
|
|
|
|
test('parseResubmitTask требует submitMode draft|live (без дефолта — чтобы live не случился сам)', () => {
|
|
const { submitMode, ...rest } = base;
|
|
assert.throws(() => parseResubmitTask(rest), /submitMode/);
|
|
});
|
|
|
|
test('parseResubmitTask отвергает чужой submitMode', () => {
|
|
assert.throws(() => parseResubmitTask({ ...base, submitMode: 'xxx' }), /submitMode/);
|
|
});
|
|
|
|
test('parseResubmitTask принимает submitMode live', () => {
|
|
const t = parseResubmitTask({ ...base, submitMode: 'live' });
|
|
assert.equal(t.submitMode, 'live');
|
|
});
|
|
|
|
test('parseResubmitTask пробрасывает правки текста/ссылки/ОРД', () => {
|
|
const t = parseResubmitTask({
|
|
mode: 'resubmit',
|
|
campaignId: '2231134',
|
|
submitMode: 'draft',
|
|
adText: 'Исправленный текст',
|
|
buttonUrl: 'https://t.me/liderra',
|
|
ordCategory: 'Другая категория',
|
|
});
|
|
assert.equal(t.adText, 'Исправленный текст');
|
|
assert.equal(t.buttonUrl, 'https://t.me/liderra');
|
|
assert.equal(t.ordCategory, 'Другая категория');
|
|
});
|
|
|
|
test('parseResubmitTask принимает правку ТОЛЬКО текста, без документа', () => {
|
|
const t = parseResubmitTask({
|
|
mode: 'resubmit', campaignId: '2231134', submitMode: 'draft', adText: 'Новый текст',
|
|
});
|
|
assert.equal(t.adText, 'Новый текст');
|
|
assert.equal(t.moderatorFile, null);
|
|
});
|
|
|
|
test('parseResubmitTask требует ХОТЬ ОДНО исправление (документ или правку поля)', () => {
|
|
// Пересдача без единой правки = тот же контент → снова отказ. Бессмысленно.
|
|
assert.throws(
|
|
() => parseResubmitTask({ mode: 'resubmit', campaignId: '2231134', submitMode: 'draft' }),
|
|
/исправлен|документ|текст/i,
|
|
);
|
|
});
|
|
|
|
test('parseResubmitTask: незаданные поля правок → null', () => {
|
|
const t = parseResubmitTask(base); // только moderatorFile
|
|
assert.equal(t.adText, null);
|
|
assert.equal(t.buttonUrl, null);
|
|
assert.equal(t.ordCategory, null);
|
|
});
|
|
|
|
test('parseResubmitTask НЕ требует phonesFile/budgetRub (аудитория и стоимость уже у кампании)', () => {
|
|
const t = parseResubmitTask(base);
|
|
assert.equal(t.phonesFile, undefined);
|
|
assert.equal(t.budgetRub, undefined);
|
|
});
|