initial
This commit is contained in:
86
test/test.integration.node.ts
Normal file
86
test/test.integration.node.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { VpnClient, VpnServer } from '../ts/index.js';
|
||||
import type { IVpnClientOptions, IVpnServerOptions } from '../ts/index.js';
|
||||
|
||||
// Integration test: both client and server daemon spawns, keypair generation, full IPC roundtrip
|
||||
|
||||
let server: VpnServer;
|
||||
let client: VpnClient;
|
||||
|
||||
tap.test('Integration: spawn server daemon', async () => {
|
||||
const options: IVpnServerOptions = {
|
||||
transport: { transport: 'stdio' },
|
||||
};
|
||||
server = new VpnServer(options);
|
||||
const started = await server['bridge'].start();
|
||||
expect(started).toBeTrue();
|
||||
expect(server.running).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Integration: server generateKeypair', async () => {
|
||||
const keypair = await server.generateKeypair();
|
||||
expect(keypair.publicKey).toBeTypeofString();
|
||||
expect(keypair.privateKey).toBeTypeofString();
|
||||
// Verify base64-encoded 32-byte keys = 44 chars
|
||||
expect(keypair.publicKey.length).toEqual(44);
|
||||
expect(keypair.privateKey.length).toEqual(44);
|
||||
});
|
||||
|
||||
tap.test('Integration: server getStatus is disconnected', async () => {
|
||||
const status = await server.getStatus();
|
||||
expect(status.state).toEqual('disconnected');
|
||||
});
|
||||
|
||||
tap.test('Integration: server listClients returns empty', async () => {
|
||||
const clients = await server.listClients();
|
||||
expect(clients).toBeArray();
|
||||
expect(clients.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('Integration: server getStatistics returns zeros', async () => {
|
||||
const stats = await server.getStatistics();
|
||||
expect(stats.bytesSent).toEqual(0);
|
||||
expect(stats.bytesReceived).toEqual(0);
|
||||
expect(stats.activeClients).toEqual(0);
|
||||
expect(stats.totalConnections).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('Integration: spawn client daemon', async () => {
|
||||
const options: IVpnClientOptions = {
|
||||
transport: { transport: 'stdio' },
|
||||
};
|
||||
client = new VpnClient(options);
|
||||
const started = await client.start();
|
||||
expect(started).toBeTrue();
|
||||
expect(client.running).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Integration: client getStatus is disconnected', async () => {
|
||||
const status = await client.getStatus();
|
||||
expect(status.state).toEqual('disconnected');
|
||||
});
|
||||
|
||||
tap.test('Integration: client getStatistics returns zeros', async () => {
|
||||
const stats = await client.getStatistics();
|
||||
expect(stats.bytesSent).toEqual(0);
|
||||
expect(stats.bytesReceived).toEqual(0);
|
||||
expect(stats.packetsSent).toEqual(0);
|
||||
expect(stats.packetsReceived).toEqual(0);
|
||||
expect(stats.keepalivesSent).toEqual(0);
|
||||
expect(stats.keepalivesReceived).toEqual(0);
|
||||
expect(stats.uptimeSeconds).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('Integration: stop client daemon', async () => {
|
||||
client.stop();
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
expect(client.running).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('Integration: stop server daemon', async () => {
|
||||
server.stop();
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
expect(server.running).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
37
test/test.vpnclient.node.ts
Normal file
37
test/test.vpnclient.node.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { VpnClient } from '../ts/index.js';
|
||||
import type { IVpnClientOptions } from '../ts/index.js';
|
||||
|
||||
let client: VpnClient;
|
||||
|
||||
tap.test('VpnClient: spawn daemon in stdio mode', async () => {
|
||||
const options: IVpnClientOptions = {
|
||||
transport: { transport: 'stdio' },
|
||||
};
|
||||
client = new VpnClient(options);
|
||||
const started = await client.start();
|
||||
expect(started).toBeTrue();
|
||||
expect(client.running).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnClient: getStatus returns disconnected', async () => {
|
||||
const status = await client.getStatus();
|
||||
expect(status.state).toEqual('disconnected');
|
||||
});
|
||||
|
||||
tap.test('VpnClient: getStatistics returns zeros', async () => {
|
||||
const stats = await client.getStatistics();
|
||||
expect(stats.bytesSent).toEqual(0);
|
||||
expect(stats.bytesReceived).toEqual(0);
|
||||
expect(stats.packetsSent).toEqual(0);
|
||||
expect(stats.packetsReceived).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('VpnClient: stop daemon', async () => {
|
||||
client.stop();
|
||||
// Give it a moment to clean up
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
expect(client.running).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
125
test/test.vpnconfig.node.ts
Normal file
125
test/test.vpnconfig.node.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { VpnConfig } from '../ts/index.js';
|
||||
import type { IVpnClientConfig, IVpnServerConfig } from '../ts/index.js';
|
||||
|
||||
tap.test('VpnConfig: validate valid client config', async () => {
|
||||
const config: IVpnClientConfig = {
|
||||
serverUrl: 'wss://vpn.example.com/tunnel',
|
||||
serverPublicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
dns: ['1.1.1.1', '8.8.8.8'],
|
||||
mtu: 1420,
|
||||
keepaliveIntervalSecs: 30,
|
||||
};
|
||||
// Should not throw
|
||||
VpnConfig.validateClientConfig(config);
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: reject client config without serverUrl', async () => {
|
||||
const config = {
|
||||
serverPublicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
} as IVpnClientConfig;
|
||||
let threw = false;
|
||||
try {
|
||||
VpnConfig.validateClientConfig(config);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect((e as Error).message).toContain('serverUrl');
|
||||
}
|
||||
expect(threw).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: reject client config with invalid serverUrl scheme', async () => {
|
||||
const config: IVpnClientConfig = {
|
||||
serverUrl: 'http://vpn.example.com/tunnel',
|
||||
serverPublicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
};
|
||||
let threw = false;
|
||||
try {
|
||||
VpnConfig.validateClientConfig(config);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect((e as Error).message).toContain('wss://');
|
||||
}
|
||||
expect(threw).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: reject client config with invalid MTU', async () => {
|
||||
const config: IVpnClientConfig = {
|
||||
serverUrl: 'wss://vpn.example.com/tunnel',
|
||||
serverPublicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
mtu: 100,
|
||||
};
|
||||
let threw = false;
|
||||
try {
|
||||
VpnConfig.validateClientConfig(config);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect((e as Error).message).toContain('mtu');
|
||||
}
|
||||
expect(threw).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: reject client config with invalid DNS', async () => {
|
||||
const config: IVpnClientConfig = {
|
||||
serverUrl: 'wss://vpn.example.com/tunnel',
|
||||
serverPublicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
dns: ['not-an-ip'],
|
||||
};
|
||||
let threw = false;
|
||||
try {
|
||||
VpnConfig.validateClientConfig(config);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect((e as Error).message).toContain('DNS');
|
||||
}
|
||||
expect(threw).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: validate valid server config', async () => {
|
||||
const config: IVpnServerConfig = {
|
||||
listenAddr: '0.0.0.0:443',
|
||||
privateKey: 'dGVzdHByaXZhdGVrZXk=',
|
||||
publicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
subnet: '10.8.0.0/24',
|
||||
dns: ['1.1.1.1'],
|
||||
mtu: 1420,
|
||||
enableNat: true,
|
||||
};
|
||||
// Should not throw
|
||||
VpnConfig.validateServerConfig(config);
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: reject server config with invalid subnet', async () => {
|
||||
const config: IVpnServerConfig = {
|
||||
listenAddr: '0.0.0.0:443',
|
||||
privateKey: 'dGVzdHByaXZhdGVrZXk=',
|
||||
publicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
subnet: 'invalid',
|
||||
};
|
||||
let threw = false;
|
||||
try {
|
||||
VpnConfig.validateServerConfig(config);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect((e as Error).message).toContain('subnet');
|
||||
}
|
||||
expect(threw).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnConfig: reject server config without privateKey', async () => {
|
||||
const config = {
|
||||
listenAddr: '0.0.0.0:443',
|
||||
publicKey: 'dGVzdHB1YmxpY2tleQ==',
|
||||
subnet: '10.8.0.0/24',
|
||||
} as IVpnServerConfig;
|
||||
let threw = false;
|
||||
try {
|
||||
VpnConfig.validateServerConfig(config);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect((e as Error).message).toContain('privateKey');
|
||||
}
|
||||
expect(threw).toBeTrue();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
54
test/test.vpninstaller.node.ts
Normal file
54
test/test.vpninstaller.node.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { VpnInstaller } from '../ts/index.js';
|
||||
|
||||
tap.test('VpnInstaller: detect platform', async () => {
|
||||
const platform = VpnInstaller.detectPlatform();
|
||||
expect(['linux', 'macos', 'windows', 'unknown']).toContain(platform);
|
||||
});
|
||||
|
||||
tap.test('VpnInstaller: generate systemd unit', async () => {
|
||||
const unit = VpnInstaller.generateSystemdUnit({
|
||||
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
||||
socketPath: '/var/run/smartvpn.sock',
|
||||
mode: 'server',
|
||||
});
|
||||
|
||||
expect(unit.platform).toEqual('linux');
|
||||
expect(unit.installPath).toContain('systemd');
|
||||
expect(unit.installPath).toContain('smartvpn-server');
|
||||
expect(unit.content).toContain('[Unit]');
|
||||
expect(unit.content).toContain('[Service]');
|
||||
expect(unit.content).toContain('[Install]');
|
||||
expect(unit.content).toContain('--management-socket');
|
||||
expect(unit.content).toContain('/var/run/smartvpn.sock');
|
||||
expect(unit.content).toContain('--mode server');
|
||||
});
|
||||
|
||||
tap.test('VpnInstaller: generate launchd plist', async () => {
|
||||
const unit = VpnInstaller.generateLaunchdPlist({
|
||||
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
||||
socketPath: '/var/run/smartvpn.sock',
|
||||
mode: 'client',
|
||||
});
|
||||
|
||||
expect(unit.platform).toEqual('macos');
|
||||
expect(unit.installPath).toContain('LaunchDaemons');
|
||||
expect(unit.content).toContain('rocks.push.smartvpn.client');
|
||||
expect(unit.content).toContain('--management-socket');
|
||||
expect(unit.content).toContain('/var/run/smartvpn.sock');
|
||||
});
|
||||
|
||||
tap.test('VpnInstaller: generate service unit for current platform', async () => {
|
||||
const platform = VpnInstaller.detectPlatform();
|
||||
if (platform === 'linux' || platform === 'macos') {
|
||||
const unit = VpnInstaller.generateServiceUnit({
|
||||
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
||||
socketPath: '/var/run/smartvpn.sock',
|
||||
mode: 'server',
|
||||
});
|
||||
expect(unit.platform).toEqual(platform);
|
||||
expect(unit.content.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
49
test/test.vpnserver.node.ts
Normal file
49
test/test.vpnserver.node.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { VpnServer } from '../ts/index.js';
|
||||
import type { IVpnServerOptions } from '../ts/index.js';
|
||||
|
||||
let server: VpnServer;
|
||||
|
||||
tap.test('VpnServer: spawn daemon in stdio mode', async () => {
|
||||
const options: IVpnServerOptions = {
|
||||
transport: { transport: 'stdio' },
|
||||
};
|
||||
server = new VpnServer(options);
|
||||
// Start without a config (just start bridge, don't send 'start' command)
|
||||
const started = await server['bridge'].start();
|
||||
expect(started).toBeTrue();
|
||||
expect(server.running).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('VpnServer: getStatus returns disconnected', async () => {
|
||||
const status = await server.getStatus();
|
||||
expect(status.state).toEqual('disconnected');
|
||||
});
|
||||
|
||||
tap.test('VpnServer: getStatistics returns zeros', async () => {
|
||||
const stats = await server.getStatistics();
|
||||
expect(stats.bytesSent).toEqual(0);
|
||||
expect(stats.activeClients).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('VpnServer: listClients returns empty array', async () => {
|
||||
const clients = await server.listClients();
|
||||
expect(clients).toBeArray();
|
||||
expect(clients.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('VpnServer: generateKeypair returns valid keypair', async () => {
|
||||
const keypair = await server.generateKeypair();
|
||||
expect(keypair.publicKey).toBeTypeofString();
|
||||
expect(keypair.privateKey).toBeTypeofString();
|
||||
expect(keypair.publicKey.length).toBeGreaterThan(0);
|
||||
expect(keypair.privateKey.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('VpnServer: stop daemon', async () => {
|
||||
server.stop();
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
expect(server.running).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user