133 lines
4.8 KiB
JavaScript
133 lines
4.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
accessTabForKey,
|
|
connectionAction,
|
|
copyText,
|
|
formatConnectionDuration,
|
|
formatConnectionDurationWords,
|
|
isSubscriptionUrlValid,
|
|
localProxyUrls,
|
|
subscriptionDomain,
|
|
subscriptionDaysLeft,
|
|
subscriptionUsage,
|
|
} from '../../src/web/utils/clientControls.js';
|
|
import { instructionBlocks } from '../../src/web/instructions.js';
|
|
|
|
test('connection button chooses the only valid client action', () => {
|
|
assert.deepEqual(connectionAction({ connected: true }), { type: 'stop' });
|
|
assert.deepEqual(connectionAction({ selectedServerId: 'srv_nl' }), {
|
|
type: 'apply',
|
|
serverId: 'srv_nl',
|
|
});
|
|
assert.deepEqual(connectionAction({ configExists: true }), { type: 'restart' });
|
|
assert.equal(connectionAction({}), null);
|
|
});
|
|
|
|
test('connection duration is derived from the backend start time', () => {
|
|
const startedAt = '2026-07-10T10:00:00.000Z';
|
|
const now = Date.parse('2026-07-11T12:03:04.900Z');
|
|
|
|
assert.equal(formatConnectionDuration(startedAt, now), '26:03:04');
|
|
assert.equal(formatConnectionDuration(null, now), '00:00:00');
|
|
});
|
|
|
|
test('connection duration can be written with Russian units', () => {
|
|
const startedAt = '2026-07-10T10:00:00.000Z';
|
|
const now = Date.parse('2026-07-11T12:03:04.900Z');
|
|
|
|
assert.equal(formatConnectionDurationWords(startedAt, now), '1 день 2 часа 3 минуты 4 секунды');
|
|
assert.equal(formatConnectionDurationWords(null, now), '0 секунд');
|
|
});
|
|
|
|
test('saved subscription is reduced to its public domain', () => {
|
|
assert.equal(subscriptionDomain('sub.example.com/…'), 'sub.example.com');
|
|
assert.equal(subscriptionDomain(''), '');
|
|
});
|
|
|
|
test('subscription URL is validated locally by shape', () => {
|
|
assert.equal(isSubscriptionUrlValid(' https://sub.example/token '), true);
|
|
assert.equal(isSubscriptionUrlValid('http://127.0.0.1/subscription'), true);
|
|
assert.equal(isSubscriptionUrlValid('ftp://sub.example/token'), false);
|
|
assert.equal(isSubscriptionUrlValid('not-a-url'), false);
|
|
});
|
|
|
|
test('local proxy exposes both supported URLs', () => {
|
|
assert.deepEqual(localProxyUrls(18080), {
|
|
socks5: 'socks5://127.0.0.1:18080',
|
|
http: 'http://127.0.0.1:18080',
|
|
});
|
|
});
|
|
|
|
test('local proxy defaults to the macOS client port', () => {
|
|
assert.deepEqual(localProxyUrls(), {
|
|
socks5: 'socks5://127.0.0.1:8082',
|
|
http: 'http://127.0.0.1:8082',
|
|
});
|
|
});
|
|
|
|
test('gateway proxy URLs use its network address', () => {
|
|
assert.deepEqual(localProxyUrls(8080, '192.168.50.111'), {
|
|
socks5: 'socks5://192.168.50.111:8080',
|
|
http: 'http://192.168.50.111:8080',
|
|
});
|
|
});
|
|
|
|
test('copy uses the synchronous native path available on gateway HTTP', async () => {
|
|
const textarea = {
|
|
style: {},
|
|
setAttribute() {},
|
|
select() { this.selected = true; },
|
|
remove() { this.removed = true; },
|
|
};
|
|
const documentRef = {
|
|
body: { append(node) { node.appended = true; } },
|
|
createElement: () => textarea,
|
|
execCommand: (command) => command === 'copy',
|
|
};
|
|
|
|
await copyText('192.168.50.111', { documentRef });
|
|
|
|
assert.equal(textarea.value, '192.168.50.111');
|
|
assert.equal(textarea.selected, true);
|
|
assert.equal(textarea.removed, true);
|
|
});
|
|
|
|
test('Gateway access tabs wrap with arrows and support Home and End', () => {
|
|
const tabs = ['gateway', 'proxy'];
|
|
|
|
assert.equal(accessTabForKey(tabs, 'gateway', 'ArrowLeft'), 'proxy');
|
|
assert.equal(accessTabForKey(tabs, 'proxy', 'ArrowRight'), 'gateway');
|
|
assert.equal(accessTabForKey(tabs, 'proxy', 'Home'), 'gateway');
|
|
assert.equal(accessTabForKey(tabs, 'gateway', 'End'), 'proxy');
|
|
assert.equal(accessTabForKey(tabs, 'gateway', 'Enter'), 'gateway');
|
|
});
|
|
|
|
test('subscription usage combines traffic and caps progress', () => {
|
|
assert.deepEqual(subscriptionUsage({ upload: 30, download: 80, total: 100, expire: 2 }), {
|
|
upload: 30,
|
|
download: 80,
|
|
total: 100,
|
|
used: 110,
|
|
percent: 100,
|
|
expiresAt: new Date(2000),
|
|
});
|
|
});
|
|
|
|
test('subscription expiry uses Russian day forms', () => {
|
|
const now = Date.parse('2026-07-11T00:00:00Z');
|
|
assert.equal(subscriptionDaysLeft(new Date('2026-07-12T00:00:00Z'), now), 'остался 1 день');
|
|
assert.equal(subscriptionDaysLeft(new Date('2026-07-13T00:00:00Z'), now), 'осталось 2 дня');
|
|
assert.equal(subscriptionDaysLeft(new Date('2026-07-16T00:00:00Z'), now), 'осталось 5 дней');
|
|
});
|
|
|
|
test('gateway receives router instructions while local client does not', () => {
|
|
const gateway = instructionBlocks({ isGateway: true, host: '192.168.1.20', port: 8080 });
|
|
const client = instructionBlocks({ isGateway: false, host: '127.0.0.1', port: 8082 });
|
|
|
|
assert.equal(gateway.at(-1).id, 'router');
|
|
assert.equal(client.some((block) => block.id === 'router'), false);
|
|
assert.match(client.find((block) => block.id === 'vscode').code, /127\.0\.0\.1:8082/);
|
|
});
|