Files
Auto-Mirror-Switcher/tests/shared.test.js
T

356 lines
12 KiB
JavaScript

const assert = require('assert');
const S = require('../shared.js');
function test(name, fn) {
try {
fn();
console.log('ok - ' + name);
} catch (error) {
console.error('not ok - ' + name);
throw error;
}
}
test('numeric rule rejects empty host when includeEmpty is disabled', function () {
const rule = {
enabled: true,
hostRegex: '^v\\d*\\.vost\\.pw$',
strategy: 'number',
template: 'v{n}.vost.pw',
includeEmpty: false,
numberStart: 1,
numberEnd: 100
};
assert.strictEqual(S.matchesRule(rule, 'v.vost.pw'), false);
assert.strictEqual(S.matchesRule(rule, 'v50.vost.pw'), true);
});
test('default rules use probe mode', function () {
assert.strictEqual(S.createMultclubRule().mode, 'probe');
assert.strictEqual(S.createEmptyRule().mode, 'probe');
});
test('default blocked markers match the Multclub preset list', function () {
const requestedMarkers = `доступ ограничен
доступ к сайту ограничен
сайт заблокирован
заблокировано
ограничение доступа
ресурс заблокирован
по решению органов государственной власти
роскомнадзор
forbidden
403 forbidden
404 not found
bad gateway
service unavailable
cloudflare host error
host error
cloudflare ray id
ray id
cloudflare-ray
cf-ray
идентификатор луча cloudflare
идентификатор ray cloudflare
идентификатор cloudflare ray
идентификатор луча
web server is down
origin is unreachable
connection timed out
время ожидания соединения истекло
истекло время ожидания соединения
код ошибки 520
код ошибки 521
код ошибки 522
код ошибки 523
код ошибки 524
код ошибки 525
код ошибки 526
код ошибки 530
ошибка 520
ошибка 521
ошибка 522
ошибка 523
ошибка 524
ошибка 525
ошибка 526
ошибка 530
первоначальное соединение между сетью cloudflare
между сетью cloudflare и исходным веб-сервером
исходным веб-сервером истекло
веб-страница не может быть отображена
a timeout occurred
gateway timeout
error 520
error 521
error 522
error 523
error 524
error 525
error 526
error 530
the web server is down
the origin web server is not reachable
the initial connection between cloudflare
cloudflare was unable to establish a connection
browser working
cloudflare working`.split('\n');
assert.deepStrictEqual(S.DEFAULT_MARKERS, requestedMarkers);
assert.strictEqual(S.createMultclubRule().blockedMarkers, requestedMarkers.join('\n'));
});
test('numeric rule rejects hosts outside the configured range', function () {
const rule = {
enabled: true,
hostRegex: '^v\\d+\\.vost\\.pw$',
strategy: 'number',
template: 'v{n}.vost.pw',
includeEmpty: false,
numberStart: 1,
numberEnd: 100
};
assert.strictEqual(S.matchesRule(rule, 'v109.vost.pw'), false);
});
test('numeric rule supports ranges that start at zero', function () {
const rule = {
enabled: true,
hostRegex: '^mirror\\d+\\.example$',
strategy: 'number',
template: 'mirror{n}.example',
includeEmpty: false,
numberStart: 0,
numberEnd: 2,
numberStep: 1
};
assert.strictEqual(S.matchesRule(rule, 'mirror0.example'), true);
assert.deepStrictEqual(S.buildCandidates(rule, 'mirror1.example', null), [
'mirror2.example',
'mirror0.example'
]);
});
test('numeric rule distinguishes zero from an empty number slot', function () {
const rule = {
enabled: true,
hostRegex: '^mirror\\d*\\.example$',
strategy: 'number',
template: 'mirror{n}.example',
includeEmpty: false,
numberStart: 0,
numberEnd: 2,
numberStep: 1
};
assert.strictEqual(S.matchesRule(rule, 'mirror0.example'), true);
assert.strictEqual(S.matchesRule(rule, 'mirror.example'), false);
});
test('buildCandidates starts after current number and wraps without duplicates', function () {
const rule = {
enabled: true,
hostRegex: '^site\\d+\\.example$',
strategy: 'number',
template: 'site{n}.example',
includeEmpty: false,
numberStart: 1,
numberEnd: 5,
numberStep: 1
};
assert.deepStrictEqual(S.buildCandidates(rule, 'site3.example', null), [
'site4.example',
'site5.example',
'site1.example',
'site2.example'
]);
});
test('cached host is first only when it still matches the rule', function () {
const rule = {
enabled: true,
hostRegex: '^site\\d+\\.example$',
strategy: 'number',
template: 'site{n}.example',
includeEmpty: false,
numberStart: 1,
numberEnd: 5,
numberStep: 1
};
assert.deepStrictEqual(S.buildCandidates(rule, 'site3.example', 'site2.example').slice(0, 3), [
'site2.example',
'site4.example',
'site5.example'
]);
assert.strictEqual(S.buildCandidates(rule, 'site3.example', 'site9.example')[0], 'site4.example');
});
test('list strategy matches hosts from custom hosts without a host regex', function () {
const rule = {
enabled: true,
hostRegex: '',
strategy: 'list',
customHosts: 'mirror1.example\nmirror2.example'
};
assert.strictEqual(S.validateRule(rule), null);
assert.strictEqual(S.matchesRule(rule, 'mirror1.example'), true);
assert.strictEqual(S.matchesRule(rule, 'mirror3.example'), false);
assert.deepStrictEqual(S.buildCandidates(rule, 'mirror1.example', null), [
'mirror1.example',
'mirror2.example'
]);
});
test('list strategy validation requires custom hosts instead of host regex', function () {
assert.deepStrictEqual(S.validateRuleCode({
hostRegex: '',
strategy: 'list',
customHosts: ''
}), {
code: 'listHostsRequired',
message: 'Для режима списка нужно указать хотя бы один host.'
});
});
test('letter strategy rotates after the current token', function () {
const rule = {
enabled: true,
hostRegex: '^mirror-[ab]+\\.example$',
strategy: 'letters',
template: 'mirror-{s}.example',
letterCharset: 'ab',
letterMinLen: 1,
letterMaxLen: 2,
maxCandidates: 6
};
assert.deepStrictEqual(S.buildCandidates(rule, 'mirror-b.example', null), [
'mirror-aa.example',
'mirror-ab.example',
'mirror-ba.example',
'mirror-bb.example',
'mirror-a.example'
]);
});
test('blocked-page detection recognizes Cloudflare and configured markers', function () {
const rule = {
blockedMarkers: 'custom block marker'
};
assert.strictEqual(S.findBlockedMarkerInText(rule, 'Cloudflare Ray ID: abcdef1234567890'), 'cloudflare-ray-id');
assert.strictEqual(S.findBlockedMarkerInText(rule, 'Код ошибки 522 cloudflare'), 'код ошибки 522');
assert.strictEqual(S.findBlockedMarkerInText(rule, 'page says CUSTOM BLOCK MARKER here'), 'custom block marker');
});
test('probe blocked-page detection ignores hidden HTML marker text', function () {
const rule = {
blockedMarkers: ''
};
const workingHtml = `
<html>
<head><title>Working anime page</title></head>
<body>
<h1>Episode page</h1>
<script>var staleError = "Cloudflare error 522";</script>
<template>Error 522 Cloudflare Host Error</template>
<div hidden>Cloudflare Ray ID: abcdef1234567890</div>
<div style="display: none">Error 523 Cloudflare</div>
</body>
</html>`;
const blockedHtml = `
<html>
<head><title>site.example | 522: Connection timed out</title></head>
<body>
<h1>Connection timed out</h1>
<p>Error 522</p>
<p>Cloudflare</p>
</body>
</html>`;
assert.strictEqual(S.responseLooksBlocked(rule, workingHtml), false);
assert.strictEqual(S.responseLooksBlocked(rule, blockedHtml), true);
});
test('page blocked-page detection ignores embedded Cloudflare text on a normal page', function () {
const normalText = [
'Koko wa Ore ni Makasete Saki ni Ike episode page',
'Navigation catalog comments related episodes release schedule'.repeat(80),
'Player tab 3 failed: Cloudflare 522 connection timed out',
'More normal page content'.repeat(80)
].join('\n');
assert.strictEqual(S.findBlockedMarkerInPage({}, 'Animevost episode page', normalText), '');
assert.strictEqual(S.findBlockedMarkerInPage({}, '522: Connection timed out', normalText), 'connection timed out');
});
test('page blocked-page detection ignores embedded generic forbidden text on a normal page', function () {
const normalText = [
'Multclub home page',
'Navigation catalog comments releases schedule profile search'.repeat(80),
'A player message mentions forbidden in a nested widget.',
'More normal page content'.repeat(80)
].join('\n');
assert.strictEqual(S.findBlockedMarkerInPage({}, 'Multclub', normalText), '');
assert.strictEqual(S.findBlockedMarkerInPage({}, '403 Forbidden', normalText), 'forbidden');
assert.strictEqual(S.findBlockedMarkerInPage({}, 'Multclub', 'Forbidden'), 'forbidden');
});
test('page blocked-page detection ignores every built-in marker embedded in a long normal page', function () {
S.DEFAULT_MARKERS.forEach(function (marker) {
const normalText = [
'Normal mirror page',
'Navigation catalog comments releases schedule profile search'.repeat(90),
'Embedded diagnostic text: ' + marker,
'More normal page content'.repeat(90)
].join('\n');
assert.strictEqual(
S.findBlockedMarkerInPage({}, 'Normal mirror page', normalText),
'',
'marker should not stop a long normal page: ' + marker
);
});
});
test('page blocked-page detection still accepts focused built-in error pages', function () {
assert.strictEqual(S.findBlockedMarkerInPage({}, 'Multclub', 'Доступ к сайту ограничен по решению органов государственной власти'), 'доступ к сайту ограничен');
assert.strictEqual(S.findBlockedMarkerInPage({}, 'Multclub', 'Bad gateway'), 'bad gateway');
assert.strictEqual(S.findBlockedMarkerInPage({}, 'Multclub', 'Cloudflare host error. Error 522. Ray ID abcdef1234567890.'), 'cloudflare-ray-id');
});
test('URL helpers preserve path, query, and hash', function () {
const rule = { protocol: 'https' };
const source = new URL('http://old.example/path/page.html?a=1#top');
assert.strictEqual(S.buildTargetUrl(rule, 'new.example', source), 'https://new.example/path/page.html?a=1#top');
});
test('rule validation rejects an empty host regex', function () {
assert.strictEqual(S.validateRule({
hostRegex: '',
strategy: 'number',
template: 'site{n}.example'
}), 'Поле hostRegex не должно быть пустым.');
});
test('rule validation exposes stable error codes for localization', function () {
assert.deepStrictEqual(S.validateRuleCode({
hostRegex: '',
strategy: 'number',
template: 'site{n}.example'
}), {
code: 'hostRegexRequired',
message: 'Поле hostRegex не должно быть пустым.'
});
});