3fdcd88ad3
Задача 16 закрыта решением владельца. Дороги «робот везёт документ в кабинет Яндекса» не существует — доказано двумя нарочными отказами, обычной тематикой и лицензируемой: в окне отказа ноль полей для файла, документы Яндекс принимает только снаружи кабинета. Приём документа оставлен, но портал больше не молчит: клиент приложил файл — в ленту сразу ложится отметка «документ у нас, передать его Яндексу автоматически нельзя, при необходимости отнесём сами и напишем здесь», а владельцу уходит письмо на адрес алертов. Без письма обещание было бы пустым: файл просто лёг бы на диск. Сам файл письмом не отправляем — это чужие бумаги. Обычный ответ без файла ни отметки, ни письма не даёт. Экран «ждёт разбора» в админке: ручка была, экрана не было. Третья карточка на странице «Реклама» — клиент, кампания, что робот делал человеческими словами, номер объявления и на чём споткнулся. В подписи прямо сказано, чего там НЕ будет: обычных отказов, их клиент разбирает сам. Дальше — разбор собственной работы этого дня. Найдено четыре ошибки, все исправлены. 1. ТЯЖЁЛАЯ. Доклад разведки на бою уронил бы очередь заданий целиком. Робот пишет в ленту под служебной ролью, а у неё на этой таблице было только чтение. Отказ по правам, 500 роботу, три повтора — и задание навсегда «в работе». Пока хоть одно задание в работе, выдача отвечает «работы нет» ВСЕМ клиентам. Лечение — запись схемы v9.17: право на запись плюс нумератор. В плане про это было написано прямым текстом, я прошёл мимо. Тесты поймать не могли: ходят суперпользователем. 2. Признак «набор создан» я выдумал: взял метку, которая в нашей же разметке описана как СКРЫТАЯ галочка. Проверка «видно ли её» не сработала бы никогда. Признак с экрана убран совсем: успех определяет портал слепком креативов, а «окно не закрылось» — это норма, так и есть живьём. 3. Сломал ленту для повторного отказа. Поменял защиту от дублей на «такой текст уже когда-либо был» — и клиент, починивший рекламу и получивший тот же отказ, не увидел бы ничего. Вернул сравнение с последним сообщением, а заглушку «причину выясняем» держит теперь сам джоб: показываем один раз, пока сказать нечего. 4. Мой собственный тест оказался пустышкой: оставался зелёным при вырезанной защите. В нём отклонялись ВСЕ объявления, а тогда кампания уходит в «отклонена» и обход её больше не берёт. Сценарий существует только при частичном отказе — тест переписан на два объявления и теперь вырезание защиты его роняет. Портал 391/391, админские экраны 9/9, фронт на затронутых наборах 52/52, робот 74/74, мест снятия заморозки денег по-прежнему четыре. На боевой не выкатывалось, рубильник Директа выключен, живьём разведка не гонялась. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
458 lines
21 KiB
JavaScript
458 lines
21 KiB
JavaScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { existsSync, mkdirSync, utimesSync, writeFileSync } from 'node:fs';
|
||
import { mkdtemp, rm } from 'node:fs/promises';
|
||
import { tmpdir } from 'node:os';
|
||
import { join } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { runOnce } from '../src/runner.js';
|
||
|
||
const JOB = {
|
||
id: 7,
|
||
campaign_id: 42,
|
||
banners: [
|
||
{ banner_id: 1, width: 300, height: 250, file_url: 'https://lk.liderra.ru/f/1' },
|
||
{ banner_id: 2, width: 728, height: 90, file_url: 'https://lk.liderra.ru/f/2' },
|
||
],
|
||
};
|
||
|
||
async function withWorkDir(fn) {
|
||
const dir = await mkdtemp(join(tmpdir(), 'yc-runner-'));
|
||
try {
|
||
return await fn(dir);
|
||
} finally {
|
||
await rm(dir, { recursive: true, force: true });
|
||
}
|
||
}
|
||
|
||
function stubs({
|
||
job = null,
|
||
uploadThrows = null,
|
||
uploadResult = { modalClosed: false, cabinetSaid: '' },
|
||
loggedIn = true,
|
||
inspectThrows = null,
|
||
inspectResult = { report: 'Нет предупреждения: финансовые услуги', screenshotPath: '/tmp/shot.png' },
|
||
} = {}) {
|
||
const sent = [];
|
||
const reports = [];
|
||
const downloaded = [];
|
||
const uploads = [];
|
||
const inspected = [];
|
||
let opened = 0;
|
||
let closed = 0;
|
||
|
||
return {
|
||
sent,
|
||
reports,
|
||
downloaded,
|
||
uploads,
|
||
inspected,
|
||
opened: () => opened,
|
||
closed: () => closed,
|
||
portal: {
|
||
takeJob: async () => job,
|
||
downloadBanner: async (banner, path) => { downloaded.push(path); return path; },
|
||
reportDone: async (id) => reports.push({ ok: true, id }),
|
||
reportFailure: async (id, reason) => reports.push({ ok: false, id, reason }),
|
||
reportInspection: async (id, report, shot) => reports.push({ inspection: true, id, report, shot }),
|
||
},
|
||
browser: {
|
||
open: async () => {
|
||
opened += 1;
|
||
|
||
return { context: { close: async () => { closed += 1; } }, page: { screenshot: async () => {} } };
|
||
},
|
||
isLoggedIn: async () => loggedIn,
|
||
uploadCreatives: async (page, config, files) => {
|
||
uploads.push(files);
|
||
if (uploadThrows) throw new Error(uploadThrows);
|
||
|
||
return uploadResult;
|
||
},
|
||
readRejection: async (page, config, j, shotPath) => {
|
||
inspected.push({ adId: j.yandex_ad_id, shotPath });
|
||
if (inspectThrows) throw new Error(inspectThrows);
|
||
|
||
return inspectResult;
|
||
},
|
||
},
|
||
mailer: {
|
||
alarm: async (m) => sent.push({ kind: 'alarm', ...m }),
|
||
report: async (m) => sent.push({ kind: 'report', ...m }),
|
||
},
|
||
};
|
||
}
|
||
|
||
const opts = (workDir) => ({ timestamp: 't', workDir });
|
||
|
||
// Без пауз: в проверках ждать нечего, а повтор доклада порталу иначе тянул бы секунды.
|
||
const FAST_CONFIG = { humanDelayMs: 0, reportRetryDelayMs: 0 };
|
||
|
||
test('без работы ничего не делает и браузер не открывает', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: null });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.idle, true);
|
||
assert.equal(s.opened(), 0);
|
||
assert.equal(s.reports.length, 0);
|
||
});
|
||
});
|
||
|
||
test('успешная загрузка отчитывается «готово» и шлёт письмо', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, true);
|
||
assert.deepEqual(s.reports, [{ ok: true, id: 7 }]);
|
||
assert.equal(s.sent[0].kind, 'report');
|
||
assert.equal(s.sent[0].count, 2);
|
||
});
|
||
});
|
||
|
||
test('в кабинет уходят ПУТИ к файлам, а не описания баннеров', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB });
|
||
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
const given = s.uploads[0];
|
||
assert.equal(given.length, 2);
|
||
assert.ok(given.every((f) => typeof f === 'string'), `в кабинет ушло не то: ${JSON.stringify(given)}`);
|
||
assert.deepEqual(given, s.downloaded);
|
||
assert.ok(given[0].includes('300x250'));
|
||
});
|
||
});
|
||
|
||
test('файлы клиента живут в переданной рабочей папке', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB });
|
||
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.ok(s.downloaded.every((p) => p.startsWith(workDir)), `файлы легли мимо: ${s.downloaded[0]}`);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* 🪤 Прежний тест на это был ПУСТЫШКОЙ: он всегда передавал рабочую папку явно, поэтому
|
||
* значение по умолчанию не проверялось вообще. Проверено вырезанием: подмена умолчания
|
||
* на `process.cwd()` оставляла все тесты зелёными.
|
||
*
|
||
* А умолчание тут и есть вся суть: робота запускает расписание, каталог запуска у него
|
||
* может быть любым. Считай мы папки от него — картинки клиента и снимки экрана
|
||
* с логином и остатком счёта раскидывало бы по случайным местам сервера.
|
||
*
|
||
* Поэтому запускаем ИЗ ЧУЖОГО каталога и без явной рабочей папки.
|
||
*/
|
||
test('без явной рабочей папки файлы всё равно ложатся к роботу, а не в каталог запуска', async () => {
|
||
const alien = await mkdtemp(join(tmpdir(), 'yc-alien-'));
|
||
const wasCwd = process.cwd();
|
||
const robotRoot = fileURLToPath(new URL('..', import.meta.url));
|
||
const s = stubs({ job: JOB });
|
||
|
||
try {
|
||
process.chdir(alien);
|
||
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, { timestamp: 't' });
|
||
|
||
assert.ok(s.downloaded.length > 0, 'файлы должны были скачаться');
|
||
assert.ok(
|
||
s.downloaded.every((p) => p.startsWith(robotRoot)),
|
||
`файлы легли мимо папки робота: ${s.downloaded[0]}`,
|
||
);
|
||
assert.ok(
|
||
s.downloaded.every((p) => !p.startsWith(alien)),
|
||
`файлы легли в каталог запуска: ${s.downloaded[0]}`,
|
||
);
|
||
} finally {
|
||
process.chdir(wasCwd);
|
||
// Робот чистит downloads сам, а вот папку снимков экрана он создаёт и оставляет.
|
||
await rm(join(robotRoot, 'downloads'), { recursive: true, force: true });
|
||
await rm(join(robotRoot, 'screenshots'), { recursive: true, force: true });
|
||
await rm(alien, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* Снимки экрана копились навсегда. Это не просто мусор на диске: на снимке видна боковая
|
||
* панель кабинета — логин и остаток счёта. Чем дольше они лежат, тем больше на сервере
|
||
* копится того, что там лежать не должно. Свежие нужны — по ним разбирают вчерашний сбой,
|
||
* старые не нужны никому.
|
||
*/
|
||
test('старые снимки экрана убираются, свежие остаются', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const shots = join(workDir, 'screenshots');
|
||
mkdirSync(shots, { recursive: true });
|
||
|
||
const old = join(shots, 'job-1-старый.png');
|
||
const fresh = join(shots, 'job-2-свежий.png');
|
||
writeFileSync(old, 'снимок');
|
||
writeFileSync(fresh, 'снимок');
|
||
|
||
const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||
utimesSync(old, monthAgo, monthAgo);
|
||
|
||
const s = stubs({ job: JOB });
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(existsSync(old), false, 'месячной давности снимок должен был уйти');
|
||
assert.equal(existsSync(fresh), true, 'свежий снимок нужен для разбора сбоя');
|
||
});
|
||
});
|
||
|
||
test('файлы клиента удаляются при любом исходе — это чужие картинки, копиться им нельзя', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB, uploadThrows: 'кабинет упал' });
|
||
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(existsSync(join(workDir, 'downloads', 'job-7')), false);
|
||
assert.equal(s.closed(), 1, 'браузер должен закрыться даже при сбое');
|
||
});
|
||
});
|
||
|
||
test('сбой загрузки отчитывается сбоем и шлёт алярм с причиной', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB, uploadThrows: 'кнопка не найдена' });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, false);
|
||
assert.equal(s.reports[0].ok, false);
|
||
assert.match(s.reports[0].reason, /кнопка не найдена/);
|
||
assert.match(s.sent[0].reason, /кнопка не найдена/);
|
||
});
|
||
});
|
||
|
||
test('слетевший вход не пытается грузить и отчитывается сбоем', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB, loggedIn: false });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, false);
|
||
assert.equal(s.uploads.length, 0);
|
||
assert.match(s.reports[0].reason, /вход/i);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* 🔴 Живьём окно загрузки НЕ закрывается: после «Создать» оно переключается на вкладку
|
||
* «Мои креативы» (cabinet-flow.md §5.2). Пока роботу это казалось бедой, он слал
|
||
* письмо-алярм на КАЖДОЙ удачной загрузке — а письмо, которое приходит всегда,
|
||
* перестают читать вовсе.
|
||
*
|
||
* 🔴 Настоящий сбой это не прячет: если креатив не появился, портал ловит это слепком
|
||
* creatives.get и закрывает задание сбоем — оно видно в админке «ждёт разбора».
|
||
*/
|
||
test('обычная удачная загрузка человека письмом НЕ дёргает', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({
|
||
job: JOB,
|
||
uploadResult: { modalClosed: false, cabinetSaid: 'Мои креативы 300×250' },
|
||
});
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, true);
|
||
assert.deepEqual(s.reports, [{ ok: true, id: 7 }]);
|
||
assert.equal(
|
||
s.sent.some((m) => m.kind === 'alarm'),
|
||
false,
|
||
`на удачной загрузке ушло письмо-алярм: ${JSON.stringify(s.sent)}`,
|
||
);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* Слова кабинета не теряем: они уезжают в результат прохода и попадают в журнал робота.
|
||
* Но письмом по ним человека НЕ зовём — приговор об успехе выносит портал по слепку
|
||
* креативов, а не робот по экрану.
|
||
*/
|
||
test('слова кабинета уносим в результат прохода, но письмом по ним не зовём', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({
|
||
job: JOB,
|
||
uploadResult: { modalClosed: false, cabinetSaid: 'Файл слишком большой' },
|
||
});
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, true);
|
||
assert.equal(res.cabinetSaid, 'Файл слишком большой');
|
||
assert.deepEqual(s.reports, [{ ok: true, id: 7 }]);
|
||
assert.equal(s.sent.some((m) => m.kind === 'alarm'), false);
|
||
});
|
||
});
|
||
|
||
test('обрыв на докладе «готово» НЕ превращается в отчёт о сбое — креативы уже в кабинете', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
// Приговор об успехе выносит портал по слепку креативов. Если доклад «готово» не дошёл
|
||
// (502 от nginx, обрыв, таймаут), робот раньше падал в общий обработчик сбоя и слал
|
||
// «сбой» — на СВОЮ УДАЧНУЮ работу. Портал помечал задание сбойным, а креативы уже лежали
|
||
// в кабинете. Хуже того: если портал успел принять «готово», а ответ потерялся, «сбой»
|
||
// затирал правильный результат с уже проставленными номерами.
|
||
const s = stubs({ job: JOB });
|
||
s.portal.reportDone = async () => { throw new Error('Портал ответил 502 на /done'); };
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(
|
||
s.reports.filter((r) => r.ok === false).length,
|
||
0,
|
||
`доложен сбой об удачной работе: ${JSON.stringify(s.reports)}`,
|
||
);
|
||
const alarm = s.sent.find((m) => m.kind === 'alarm');
|
||
assert.ok(alarm, 'человека надо позвать письмом');
|
||
assert.match(alarm.reason, /502/);
|
||
assert.equal(res.ok, true, 'работа сделана: креативы в кабинете');
|
||
});
|
||
});
|
||
|
||
test('доклад «готово» повторяется, если с первого раза не прошёл', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB });
|
||
let tries = 0;
|
||
s.portal.reportDone = async (id) => {
|
||
tries += 1;
|
||
if (tries < 3) throw new Error('обрыв связи');
|
||
s.reports.push({ ok: true, id });
|
||
};
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(tries, 3, `доклад пробовали ${tries} раз(а)`);
|
||
assert.deepEqual(s.reports, [{ ok: true, id: 7 }]);
|
||
assert.equal(res.ok, true);
|
||
});
|
||
});
|
||
|
||
// Письмо обязано честно сказать, что в кабинете уже лежат креативы. Пока оно было одно
|
||
// на все случаи и всегда утверждало «робот ничего не менял», человек читал письмо
|
||
// «окно загрузки не закрылось» и в кабинет не шёл — а идти надо: повторная заливка
|
||
// оставит дубли, вычистить которые можно только руками.
|
||
test('письма после заливки помечены «креативы уже в кабинете»', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB, uploadResult: { modalClosed: false, cabinetSaid: 'что-то не так' } });
|
||
s.portal.reportDone = async () => { throw new Error('Портал ответил 502 на /done'); };
|
||
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
const alarms = s.sent.filter((m) => m.kind === 'alarm');
|
||
assert.ok(alarms.length > 0, 'человека надо позвать письмом');
|
||
assert.ok(alarms.every((m) => m.uploaded === true), 'письма после заливки обязаны быть помечены');
|
||
});
|
||
});
|
||
|
||
test('письмо о сбое ДО заливки не помечено «креативы уже в кабинете»', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB, uploadThrows: 'кабинет упал' });
|
||
|
||
await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
const alarm = s.sent.find((m) => m.kind === 'alarm');
|
||
assert.ok(alarm);
|
||
assert.notEqual(alarm.uploaded, true, 'работа не сделана — врать в обратную сторону тоже нельзя');
|
||
});
|
||
});
|
||
|
||
test('портал не принял отчёт о сбое — человек узнаёт об этом из письма', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
// Раньше провал отчёта глотался молча. А это самый опасный исход: задание остаётся
|
||
// «в работе» навсегда, очередь встаёт колом для ВСЕХ клиентов, и никто об этом
|
||
// не знает — в письме написана только исходная причина сбоя.
|
||
const s = stubs({ job: JOB, uploadThrows: 'кнопка не найдена' });
|
||
s.portal.reportFailure = async () => { throw new Error('Портал ответил 422 на /done'); };
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, false);
|
||
const alarm = s.sent.find((m) => m.kind === 'alarm');
|
||
assert.ok(alarm, 'письмо-алярм должно уйти');
|
||
assert.match(alarm.reason, /кнопка не найдена/, 'исходная причина должна остаться');
|
||
assert.match(alarm.reason, /422/, 'провал отчёта порталу должен быть виден человеку');
|
||
});
|
||
});
|
||
|
||
test('письмо не ушло — задание всё равно закрыто, робот не зависает', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: JOB, uploadThrows: 'кабинет упал' });
|
||
s.mailer.alarm = async () => { throw new Error('почта недоступна'); };
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, false);
|
||
assert.equal(s.reports[0].ok, false);
|
||
});
|
||
});
|
||
|
||
// ── Разведка ──────────────────────────────────────────────────────────────────────────
|
||
|
||
const INSPECT_JOB = {
|
||
id: 9,
|
||
campaign_id: 42,
|
||
kind: 'inspect',
|
||
yandex_campaign_id: 713110757,
|
||
yandex_ad_id: 17787055204,
|
||
banners: [],
|
||
};
|
||
|
||
/**
|
||
* 🔑 Разведка — единственный способ узнать, за что Яндекс отклонил рекламу: машине он этого
|
||
* не говорит. Робот читает экран кабинета и приносит текст порталу, а тот кладёт его
|
||
* клиенту в ленту.
|
||
*/
|
||
test('разведка приносит порталу причину и снимок, а картинки не возит', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: INSPECT_JOB });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, true);
|
||
assert.equal(s.inspected.length, 1);
|
||
assert.equal(s.inspected[0].adId, 17787055204);
|
||
assert.equal(s.uploads.length, 0, 'разведка ничего не грузит');
|
||
assert.equal(s.downloaded.length, 0, 'разведке картинки клиента не нужны');
|
||
assert.equal(s.reports.length, 1);
|
||
assert.equal(s.reports[0].inspection, true);
|
||
assert.match(s.reports[0].report, /Нет предупреждения/);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* 🔴 Робот не понял, что видит — задание сбойное, человека зовём письмом, а клиенту
|
||
* В ЛЕНТУ НИЧЕГО НЕ СОЧИНЯЕМ. Придуманная причина хуже молчания: по ней клиент пойдёт
|
||
* переделывать не то.
|
||
*/
|
||
test('разведка растерялась — сбой порталу и письмо, доклада в ленту нет', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: INSPECT_JOB, inspectThrows: 'окна причины нет' });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, false);
|
||
assert.equal(s.reports.length, 1);
|
||
assert.equal(s.reports[0].ok, false);
|
||
assert.match(s.reports[0].reason, /окна причины нет/);
|
||
assert.equal(s.reports.some((r) => r.inspection), false, 'доклад в ленту уходить не должен');
|
||
assert.ok(s.sent.some((m) => m.kind === 'alarm'), 'владельца надо позвать письмом');
|
||
});
|
||
});
|
||
|
||
/** Вход в кабинет слетел — в кабинет не лезем и ничего не докладываем как причину. */
|
||
test('разведка при слетевшем входе не читает экран', async () => {
|
||
await withWorkDir(async (workDir) => {
|
||
const s = stubs({ job: INSPECT_JOB, loggedIn: false });
|
||
|
||
const res = await runOnce(FAST_CONFIG, s.portal, s.browser, s.mailer, opts(workDir));
|
||
|
||
assert.equal(res.ok, false);
|
||
assert.equal(s.inspected.length, 0);
|
||
assert.equal(s.reports[0].ok, false);
|
||
});
|
||
});
|