Skip to content

Commit

Permalink
test: setup unit test and actions
Browse files Browse the repository at this point in the history
  • Loading branch information
escwxyz committed Oct 28, 2024
1 parent 63bc0d7 commit 50222e2
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Install dependencies
run: pnpm install

- name: Format code
run: pnpm format

- name: Run tests
run: pnpm test

- name: Check types
run: pnpm type-check
40 changes: 40 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Release

permissions:
contents: write
id-token: write

on:
workflow_dispatch:

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Git config
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Set node
uses: actions/setup-node@v4
with:
node-version: lts/*
registry-url: "https://registry.npmjs.org"

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: |
- args: [--frozen-lockfile]
- name: Release
run: npx release-it --verbose
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
NPM_CONFIG_PROVENANCE: true
79 changes: 79 additions & 0 deletions tests/fixtures/mock-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as si from "systeminformation";
import { expect } from "vitest";

type NetworkStatsData = si.Systeminformation.NetworkStatsData;
type NetworkConnectionsData = si.Systeminformation.NetworkConnectionsData;

export const mockNetworkStats: NetworkStatsData[] = [
{
iface: "eth0",
operstate: "up",
rx_bytes: 1000,
tx_bytes: 2000,
rx_sec: 100,
tx_sec: 200,
rx_dropped: 0,
rx_errors: 0,
tx_dropped: 0,
tx_errors: 0,
ms: 0,
},
];

export const mockNetworkConnections: NetworkConnectionsData[] = [
{
protocol: "tcp",
localAddress: "eth0",
localPort: "1080",
peerAddress: "remote",
peerPort: "12345",
state: "ESTABLISHED",
pid: 1234,
process: "proxy",
},
{
protocol: "tcp",
localAddress: "eth0",
localPort: "1080",
peerAddress: "remote",
peerPort: "12346",
state: "ESTABLISHED",
pid: 1234,
process: "proxy",
},
];

export const expectedStats = {
basic: {
upload: 2000,
download: 1000,
uploadSpeed: 200,
downloadSpeed: 100,
timestamp: expect.any(Number),
},
proxy: {
activeConnections: 2,
stats: {
upload: 2000,
download: 1000,
uploadSpeed: 200,
downloadSpeed: 100,
},
},
fullStats: {
proxied: {
activeConnections: 2,
upload: 2000,
download: 1000,
uploadSpeed: 200,
downloadSpeed: 100,
},
direct: {
upload: 0,
download: 0,
uploadSpeed: 0,
downloadSpeed: 0,
},
timestamp: expect.any(Number),
},
};
20 changes: 20 additions & 0 deletions tests/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { afterEach, beforeEach, vi } from "vitest";

// Set up fake timers globally
vi.useFakeTimers();

// Reset all mocks before each test
beforeEach(() => {
vi.clearAllMocks();
vi.clearAllTimers();
});

// Clean up after each test
afterEach(() => {
vi.restoreAllMocks();
});

// Global error handler for unhandled promises
process.on("unhandledRejection", (error) => {
console.error("Unhandled Promise Rejection:", error);
});
151 changes: 151 additions & 0 deletions tests/unit/traffic-lens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { vi, describe, it, expect, beforeEach } from "vitest";
import TrafficLens from "../../src";
import * as si from "systeminformation";
import { mockNetworkStats, mockNetworkConnections, expectedStats } from "../fixtures/mock-data";

// Mock systeminformation module
vi.mock("systeminformation", () => ({
networkStats: vi.fn(),
networkConnections: vi.fn(),
}));

describe("TrafficLens", () => {
let monitor: TrafficLens;

beforeEach(() => {
vi.clearAllMocks();
monitor = new TrafficLens({ proxyPort: 1080 });
});

describe("Constructor", () => {
it("should initialize with default update interval", () => {
expect(monitor["config"].updateInterval).toBe(5000);
});

it("should initialize with custom update interval", () => {
const monitor = new TrafficLens({ proxyPort: 1080, updateInterval: 8000 });
expect(monitor["config"].updateInterval).toBe(8000);
});

it("should throw an error if update interval is less than 3 seconds", () => {
expect(() => new TrafficLens({ proxyPort: 1080, updateInterval: 2000 })).toThrow(
"Update interval must be greater than 3 seconds",
);
});
});

describe("Subscription Management", () => {
it("should add and remove subscribers correctly", () => {
const callback = vi.fn();
const unsubscribe = monitor.subscribe(callback);

expect(monitor["callbacks"].length).toBe(1);
unsubscribe();
expect(monitor["callbacks"].length).toBe(0);
});

it("should notify all subscribers with stats updates", async () => {
const callback1 = vi.fn();
const callback2 = vi.fn();

monitor.subscribe(callback1);
monitor.subscribe(callback2);

// Mock for first interval (setting lastStats)
vi.mocked(si.networkStats).mockResolvedValueOnce(mockNetworkStats).mockResolvedValueOnce(mockNetworkStats);
vi.mocked(si.networkConnections).mockResolvedValueOnce(mockNetworkConnections);

// Mock for second interval (calculating and notifying)
vi.mocked(si.networkStats).mockResolvedValueOnce(mockNetworkStats).mockResolvedValueOnce(mockNetworkStats);
vi.mocked(si.networkConnections).mockResolvedValueOnce(mockNetworkConnections);

await monitor.start();
// Wait for two intervals
await vi.advanceTimersByTimeAsync(10000);

// Verify callbacks were called with correct data
expect(callback1).toHaveBeenCalledWith(expectedStats.fullStats);
expect(callback2).toHaveBeenCalledWith(expectedStats.fullStats);
});
});

describe("Network Stats Collection", () => {
it("should collect network stats correctly", async () => {
vi.mocked(si.networkStats).mockResolvedValueOnce(mockNetworkStats);

const stats = await monitor["getCurrentStats"]();
expect(stats).toEqual(expectedStats.basic);
});

it("should handle network stats errors", async () => {
// Temporarily suppress console.error
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});

vi.mocked(si.networkStats).mockRejectedValueOnce(new Error("Network error"));
const stats = await monitor["getCurrentStats"]();

expect(stats).toBeNull();
expect(consoleSpy).toHaveBeenCalledWith("Error getting network stats:", expect.any(Error));

// Restore console.error
consoleSpy.mockRestore();
});
});

describe("Proxy Connection Detection", () => {
it("should detect proxy connections correctly", async () => {
vi.mocked(si.networkConnections).mockResolvedValueOnce(mockNetworkConnections);
vi.mocked(si.networkStats).mockResolvedValueOnce(mockNetworkStats);

const result = await monitor["checkProxyConnections"]();
expect(result).toEqual(expectedStats.proxy);
});
});

describe("Formatting Functions", () => {
it("should format bytes correctly", () => {
expect(TrafficLens.formatBytes(1024)).toBe("1.00 KB");
expect(TrafficLens.formatBytes(1048576)).toBe("1.00 MB");
expect(TrafficLens.formatBytes(1073741824)).toBe("1.00 GB");
});

it("should format speed correctly", () => {
expect(TrafficLens.formatSpeed(1024)).toBe("1.00 KB/s");
expect(TrafficLens.formatSpeed(1048576)).toBe("1.00 MB/s");
});
});

describe("Monitor Control", () => {
it("should prevent multiple start calls", async () => {
await monitor.start();
await expect(monitor.start()).rejects.toThrow("Monitor is already running");
});

it("should stop monitoring correctly", async () => {
await monitor.start();
monitor.stop();
expect(monitor["intervalId"]).toBeNull();
});
});

describe("Stats Retrieval", () => {
it("should return current traffic stats", () => {
const stats = monitor.getTrafficStats();
expect(stats).toHaveProperty("proxied");
expect(stats).toHaveProperty("direct");
expect(stats).toHaveProperty("timestamp");
});

it("should return proxy speeds", () => {
const speeds = monitor.getProxySpeed();
expect(speeds).toHaveProperty("uploadSpeed");
expect(speeds).toHaveProperty("downloadSpeed");
});

it("should return direct speeds", () => {
const speeds = monitor.getDirectSpeed();
expect(speeds).toHaveProperty("uploadSpeed");
expect(speeds).toHaveProperty("downloadSpeed");
});
});
});

0 comments on commit 50222e2

Please sign in to comment.