Publish Auto Mirror Switcher 1.4.3 with installation and privacy documentation
This commit is contained in:
commit
5052f542de
33 files changed
+4912
No files matched your search
@@ -0,0 +1,355 @@
|
||||
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 не должно быть пустым.'
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user