Files
3proxyUI/server/lib/traffic.test.ts

105 lines
3.7 KiB
TypeScript

import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import type { ProxyUserRecord } from '../../src/shared/contracts';
import { readObservedTraffic } from './traffic';
const cleanupDirs: string[] = [];
afterEach(async () => {
await Promise.all(cleanupDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
});
describe('readObservedTraffic', () => {
it('derives current user usage, recent activity, and daily totals from 3proxy access logs', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), '3proxy-traffic-'));
cleanupDirs.push(dir);
const logDir = path.join(dir, 'logs');
await fs.mkdir(logDir, { recursive: true });
await fs.writeFile(
path.join(logDir, '3proxy.log.2026.04.02'),
[
'260402111500.000 1080 00000 night-shift 172.19.0.1:54402 104.18.27.120:80 100 900 0 GET http://example.com/ HTTP/1.1',
'260402115900.000 2080 00000 lab-unlimited 172.19.0.1:53490 8.6.112.0:80 75 842 0 CONNECT example.com:80',
'260402115930.000 1080 00000 - 0.0.0.0:1080 0.0.0.0:0 0 0 0 Accepting connections [18/1]',
].join('\n'),
'utf8',
);
await fs.writeFile(
path.join(logDir, '3proxy.log.2026.04.01'),
'260401230000.000 1080 00000 night-shift 172.19.0.1:50000 104.18.27.120:80 50 150 0 GET http://example.com/ HTTP/1.1\n',
'utf8',
);
const users: ProxyUserRecord[] = [
{
id: 'u-1',
username: 'night-shift',
password: 'secret',
serviceId: 'socks-main',
status: 'idle',
usedBytes: 0,
quotaBytes: 1024,
},
{
id: 'u-2',
username: 'lab-unlimited',
password: 'secret',
serviceId: 'socks-lab',
status: 'idle',
usedBytes: 0,
quotaBytes: null,
},
];
const observed = await readObservedTraffic(
{
rootDir: dir,
configPath: path.join(dir, 'generated', '3proxy.cfg'),
counterPath: path.join(dir, 'state', 'counters.3cf'),
reportDir: path.join(dir, 'state', 'reports'),
logPath: path.join(logDir, '3proxy.log'),
pidPath: path.join(dir, '3proxy.pid'),
},
users,
new Date(2026, 3, 2, 12, 0, 0, 0),
);
expect(observed.totalBytes).toBe(1917);
expect(observed.liveConnections).toBe(1);
expect(observed.activeUsers).toBe(2);
expect(observed.userBytesByName.get('night-shift')).toBe(1000);
expect(observed.userBytesByName.get('lab-unlimited')).toBe(917);
expect(observed.recentUsers.has('night-shift')).toBe(false);
expect(observed.recentUsers.has('lab-unlimited')).toBe(true);
expect(observed.daily[observed.daily.length - 2].bytes).toBe(200);
expect(observed.daily[observed.daily.length - 1].bytes).toBe(1917);
});
it('returns zeroed metrics when no runtime logs exist yet', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), '3proxy-traffic-empty-'));
cleanupDirs.push(dir);
const observed = await readObservedTraffic(
{
rootDir: dir,
configPath: path.join(dir, 'generated', '3proxy.cfg'),
counterPath: path.join(dir, 'state', 'counters.3cf'),
reportDir: path.join(dir, 'state', 'reports'),
logPath: path.join(dir, 'logs', '3proxy.log'),
pidPath: path.join(dir, '3proxy.pid'),
},
[],
new Date(2026, 3, 2, 12, 0, 0, 0),
);
expect(observed.totalBytes).toBe(0);
expect(observed.liveConnections).toBe(0);
expect(observed.activeUsers).toBe(0);
expect(observed.daily).toHaveLength(5);
expect(observed.daily.every((entry) => entry.bytes === 0 && entry.share === 0)).toBe(true);
});
});