IKHSAN
EN Read in EnglishID

homeio

Publik

Fork dari doctor-io/homeio

Branch
main
Commit terakhir
958d74616 Oktober 2026

overview-service.test.ts

Buka raw
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const { siMock, readFileMock } = vi.hoisted(() => ({
  siMock: {
    networkInterfaces: vi.fn(),
    networkStats: vi.fn(),
    networkGatewayDefault: vi.fn(),
  },
  readFileMock: vi.fn(),
}));

vi.mock("systeminformation", () => ({ default: siMock }));
vi.mock("node:fs/promises", () => ({ readFile: readFileMock }));
vi.mock("@/lib/server/modules/system/update-service", () => ({ isContainerRuntime: () => false }));

import { getNetworkOverview, parseNameservers } from "@/lib/server/modules/network/overview-service";

function iface(name: string, extra: Record<string, unknown> = {}) {
  return { iface: name, internal: false, operstate: "up", type: "wired", ip4: "", mac: "", speed: -1, default: false, dhcp: false, ...extra };
}

describe("network overview", () => {
  let minute = 0;

  beforeEach(() => {
    // A minute apart per test, so the service's 2 s cache never carries over.
    vi.useFakeTimers();
    minute += 1;
    vi.setSystemTime(Date.UTC(2026, 8, 27, 12, minute));
    siMock.networkInterfaces.mockReset();
    siMock.networkStats.mockReset();
    siMock.networkGatewayDefault.mockReset();
    readFileMock.mockReset();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it("lists real interfaces with their traffic, default first, and hides container plumbing", async () => {
    siMock.networkInterfaces.mockResolvedValue([
      iface("lo", { internal: true }),
      iface("docker0"),
      iface("veth12ab"),
      iface("br-5f2c"),
      iface("wlan0", { type: "wireless", operstate: "down" }),
      iface("tailscale0", { type: "virtual", ip4: "100.113.171.79" }),
      iface("eth0", { default: true, ip4: "192.168.1.43", ip4subnet: "255.255.255.0", mac: "aa:bb", speed: 1000, duplex: "full", dhcp: true }),
    ]);
    siMock.networkStats.mockResolvedValue([
      { iface: "eth0", rx_bytes: 5_000_000, tx_bytes: 1_000_000, rx_sec: 125_000, tx_sec: 12_500, rx_errors: 0, tx_errors: 2, rx_dropped: 0, tx_dropped: 0 },
      { iface: "tailscale0", rx_bytes: 10, tx_bytes: 20, rx_sec: null, tx_sec: -1, rx_errors: 0, tx_errors: 0, rx_dropped: 0, tx_dropped: 0 },
    ]);
    siMock.networkGatewayDefault.mockResolvedValue("192.168.1.1");
    readFileMock.mockImplementation(async (file: string) => {
      if (file === "/run/systemd/resolve/resolv.conf") return "nameserver 1.1.1.1\nnameserver 9.9.9.9\n";
      return "nameserver 127.0.0.53\n";
    });

    const overview = await getNetworkOverview();

    expect(overview.interfaces.map((i) => [i.iface, i.kind, i.up])).toEqual([
      ["eth0", "wired", true],
      ["tailscale0", "vpn", true],
      ["wlan0", "wireless", false],
    ]);
    const eth0 = overview.interfaces[0];
    expect(eth0).toMatchObject({
      isDefault: true,
      ip4: "192.168.1.43",
      speedMbps: 1000,
      dhcp: true,
      rxBytes: 5_000_000,
      downloadMbps: 1,
      uploadMbps: 0.1,
      txErrors: 2,
    });
    expect(overview.interfaces[1]).toMatchObject({ downloadMbps: null, uploadMbps: null, ip4: "100.113.171.79" });
    expect(overview.gateway).toBe("192.168.1.1");
    expect(overview.inContainer).toBe(false);
    expect(overview.dnsServers).toEqual(["1.1.1.1", "9.9.9.9"]);
  });

  it("falls back to /etc/resolv.conf and reports no gateway as null", async () => {
    siMock.networkInterfaces.mockResolvedValue([iface("en0", { default: true })]);
    siMock.networkStats.mockResolvedValue([]);
    siMock.networkGatewayDefault.mockResolvedValue("");
    readFileMock.mockImplementation(async (file: string) => {
      if (file === "/run/systemd/resolve/resolv.conf") throw new Error("ENOENT");
      return "# generated\nnameserver 192.168.1.1\n";
    });

    const overview = await getNetworkOverview();

    expect(overview.gateway).toBeNull();
    expect(overview.dnsServers).toEqual(["192.168.1.1"]);
    expect(overview.interfaces[0]).toMatchObject({ iface: "en0", rxBytes: null, downloadMbps: null });
  });

  it("reads nameserver lines once each", () => {
    expect(parseNameservers("search lan\nnameserver 1.1.1.1\n  nameserver 1.1.1.1\nnameserver ::1\n")).toEqual([
      "1.1.1.1",
      "::1",
    ]);
  });
});
byte
4.203
baris
107
Jenis
ts
Commit terakhir
16 Oktober 2026