|
|
|
@ -4,47 +4,29 @@ import { tmpdir } from 'node:os'; |
|
|
|
import { join } from 'node:path'; |
|
|
|
|
|
|
|
import { createClient } from '@libsql/client'; |
|
|
|
import { count, eq } from 'drizzle-orm'; |
|
|
|
import { drizzle } from 'drizzle-orm/libsql'; |
|
|
|
import { migrate } from 'drizzle-orm/libsql/migrator'; |
|
|
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest'; |
|
|
|
|
|
|
|
import { TrafficService } from '#server/database/repositories/traffic/service'; |
|
|
|
import * as schema from '#server/database/schema'; |
|
|
|
import type { DBType } from '#db/sqlite'; |
|
|
|
import { roles } from '#shared/utils/permissions'; |
|
|
|
|
|
|
|
describe('TrafficService', () => { |
|
|
|
let libsql: ReturnType<typeof createClient>; |
|
|
|
let db: DBType; |
|
|
|
let traffic: TrafficService; |
|
|
|
let databasePath: string; |
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
databasePath = join(tmpdir(), `wg-easy-traffic-${randomUUID()}.db`); |
|
|
|
libsql = createClient({ url: `file:${databasePath}` }); |
|
|
|
await libsql.executeMultiple(` |
|
|
|
PRAGMA foreign_keys = ON; |
|
|
|
CREATE TABLE clients_table ( |
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, |
|
|
|
public_key TEXT NOT NULL, |
|
|
|
enabled INTEGER NOT NULL, |
|
|
|
daily_quota INTEGER, |
|
|
|
weekly_quota INTEGER, |
|
|
|
monthly_quota INTEGER, |
|
|
|
updated_at TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL |
|
|
|
); |
|
|
|
CREATE TABLE client_traffic_state_table ( |
|
|
|
client_id INTEGER PRIMARY KEY NOT NULL, |
|
|
|
transfer_rx INTEGER DEFAULT 0 NOT NULL, |
|
|
|
transfer_tx INTEGER DEFAULT 0 NOT NULL, |
|
|
|
FOREIGN KEY (client_id) REFERENCES clients_table(id) ON UPDATE CASCADE ON DELETE CASCADE |
|
|
|
); |
|
|
|
CREATE TABLE client_traffic_usage_table ( |
|
|
|
client_id INTEGER NOT NULL, |
|
|
|
date TEXT NOT NULL, |
|
|
|
received_bytes INTEGER DEFAULT 0 NOT NULL, |
|
|
|
sent_bytes INTEGER DEFAULT 0 NOT NULL, |
|
|
|
PRIMARY KEY (client_id, date), |
|
|
|
FOREIGN KEY (client_id) REFERENCES clients_table(id) ON UPDATE CASCADE ON DELETE CASCADE |
|
|
|
); |
|
|
|
`);
|
|
|
|
const db = drizzle({ client: libsql, schema }); |
|
|
|
db = drizzle({ client: libsql, schema }); |
|
|
|
await migrate(db, { |
|
|
|
migrationsFolder: './server/database/migrations', |
|
|
|
}); |
|
|
|
traffic = new TrafficService(db); |
|
|
|
}); |
|
|
|
|
|
|
|
@ -53,13 +35,52 @@ describe('TrafficService', () => { |
|
|
|
await rm(databasePath, { force: true }); |
|
|
|
}); |
|
|
|
|
|
|
|
async function createTestClient({ |
|
|
|
dailyQuota = null, |
|
|
|
weeklyQuota = null, |
|
|
|
monthlyQuota = null, |
|
|
|
}: { |
|
|
|
dailyQuota?: number | null; |
|
|
|
weeklyQuota?: number | null; |
|
|
|
monthlyQuota?: number | null; |
|
|
|
} = {}) { |
|
|
|
const [testUser] = await db |
|
|
|
.insert(schema.user) |
|
|
|
.values({ |
|
|
|
username: `test-${randomUUID()}`, |
|
|
|
password: null, |
|
|
|
name: 'Test User', |
|
|
|
role: roles.ADMIN, |
|
|
|
totpVerified: false, |
|
|
|
enabled: true, |
|
|
|
}) |
|
|
|
.returning({ id: schema.user.id }); |
|
|
|
const [testClient] = await db |
|
|
|
.insert(schema.client) |
|
|
|
.values({ |
|
|
|
userId: testUser!.id, |
|
|
|
interfaceId: 'wg0', |
|
|
|
name: 'Test Client', |
|
|
|
ipv4Address: '10.8.0.2', |
|
|
|
ipv6Address: 'fdcc:ad94:bacf:61a3::2', |
|
|
|
privateKey: 'private-key', |
|
|
|
publicKey: 'public-key', |
|
|
|
preSharedKey: 'pre-shared-key', |
|
|
|
serverAllowedIps: [], |
|
|
|
persistentKeepalive: 0, |
|
|
|
mtu: 1420, |
|
|
|
enabled: true, |
|
|
|
dailyQuota, |
|
|
|
weeklyQuota, |
|
|
|
monthlyQuota, |
|
|
|
}) |
|
|
|
.returning({ id: schema.client.id }); |
|
|
|
|
|
|
|
return testClient!.id; |
|
|
|
} |
|
|
|
|
|
|
|
test('accumulates deltas, handles resets, and reports sparse days', async () => { |
|
|
|
await libsql.execute({ |
|
|
|
sql: `INSERT INTO clients_table
|
|
|
|
(id, public_key, enabled, daily_quota, weekly_quota, monthly_quota) |
|
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
|
|
args: [1, 'public-key', 1, null, null, null], |
|
|
|
}); |
|
|
|
const clientId = await createTestClient(); |
|
|
|
|
|
|
|
await traffic.record( |
|
|
|
[{ publicKey: 'public-key', transferRx: 100, transferTx: 50 }], |
|
|
|
@ -77,7 +98,7 @@ describe('TrafficService', () => { |
|
|
|
await expect( |
|
|
|
traffic.getReport( |
|
|
|
{ |
|
|
|
id: 1, |
|
|
|
id: clientId, |
|
|
|
dailyQuota: null, |
|
|
|
weeklyQuota: null, |
|
|
|
monthlyQuota: null, |
|
|
|
@ -106,11 +127,9 @@ describe('TrafficService', () => { |
|
|
|
}); |
|
|
|
|
|
|
|
test('disables at the exact combined quota and cascades history', async () => { |
|
|
|
await libsql.execute({ |
|
|
|
sql: `INSERT INTO clients_table
|
|
|
|
(id, public_key, enabled, daily_quota, weekly_quota, monthly_quota) |
|
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
|
|
args: [1, 'public-key', 1, 150, 1000, null], |
|
|
|
const clientId = await createTestClient({ |
|
|
|
dailyQuota: 150, |
|
|
|
weeklyQuota: 1000, |
|
|
|
}); |
|
|
|
|
|
|
|
await expect( |
|
|
|
@ -118,21 +137,22 @@ describe('TrafficService', () => { |
|
|
|
[{ publicKey: 'public-key', transferRx: 100, transferTx: 50 }], |
|
|
|
new Date('2026-06-22T12:00:00Z') |
|
|
|
) |
|
|
|
).resolves.toEqual([1]); |
|
|
|
).resolves.toEqual([clientId]); |
|
|
|
|
|
|
|
const client = await libsql.execute( |
|
|
|
'SELECT enabled FROM clients_table WHERE id = 1' |
|
|
|
); |
|
|
|
expect(client.rows[0]?.enabled).toBe(0); |
|
|
|
const [client] = await db |
|
|
|
.select({ enabled: schema.client.enabled }) |
|
|
|
.from(schema.client) |
|
|
|
.where(eq(schema.client.id, clientId)); |
|
|
|
expect(client?.enabled).toBe(false); |
|
|
|
|
|
|
|
await libsql.execute('DELETE FROM clients_table WHERE id = 1'); |
|
|
|
const usage = await libsql.execute( |
|
|
|
'SELECT COUNT(*) AS count FROM client_traffic_usage_table' |
|
|
|
); |
|
|
|
const state = await libsql.execute( |
|
|
|
'SELECT COUNT(*) AS count FROM client_traffic_state_table' |
|
|
|
); |
|
|
|
expect(usage.rows[0]?.count).toBe(0); |
|
|
|
expect(state.rows[0]?.count).toBe(0); |
|
|
|
await db.delete(schema.client).where(eq(schema.client.id, clientId)); |
|
|
|
const [usage] = await db |
|
|
|
.select({ count: count() }) |
|
|
|
.from(schema.trafficUsage); |
|
|
|
const [state] = await db |
|
|
|
.select({ count: count() }) |
|
|
|
.from(schema.trafficState); |
|
|
|
expect(usage?.count).toBe(0); |
|
|
|
expect(state?.count).toBe(0); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|