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); }); });