IKHSAN
EN Read in EnglishID

homeio

Publik

Fork dari doctor-io/homeio

Branch
main
Commit terakhir
958d74616 Oktober 2026

service.test.ts

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

const { env, row, updates, runtime } = vi.hoisted(() => ({
  runtime: { container: false },
  env: {
    HOMEIO_TELEMETRY: true,
    HOMEIO_TELEMETRY_URL: "https://homeio.app/api/stats",
    DEMO_MODE: false,
    NODE_ENV: "production",
  },
  row: { current: { enabled: true, instanceId: null as string | null } },
  updates: [] as Array<Record<string, unknown>>,
}));

vi.mock("@/lib/server/env", () => ({ serverEnv: env }));
vi.mock("@/lib/server/logging/logger", () => ({ logServerAction: vi.fn() }));
vi.mock("@/lib/server/modules/system/update-service", () => ({
  isContainerRuntime: () => runtime.container,
}));
vi.mock("@/lib/server/db/drizzle", () => ({
  db: {
    execute: vi.fn(async () => undefined),
    select: () => ({
      from: () => ({
        where: () => ({ limit: async () => [row.current] }),
      }),
    }),
    update: () => ({
      set: (values: Record<string, unknown>) => ({
        where: async () => {
          updates.push(values);
          if (typeof values.telemetryInstanceId === "string") {
            row.current = { ...row.current, instanceId: values.telemetryInstanceId };
          }
          if (typeof values.telemetryEnabled === "boolean") {
            row.current = { ...row.current, enabled: values.telemetryEnabled };
          }
        },
      }),
    }),
  },
}));

import {
  getTelemetrySettings,
  parseOsRelease,
  sendTelemetryPing,
  setTelemetryEnabled,
} from "@/lib/server/modules/telemetry/service";

function okFetch() {
  return vi.fn(async () => new Response(null, { status: 204 })) as unknown as typeof fetch & {
    mock: { calls: Array<[string, RequestInit]> };
  };
}

describe("telemetry service", () => {
  beforeEach(() => {
    env.HOMEIO_TELEMETRY = true;
    env.DEMO_MODE = false;
    env.NODE_ENV = "production";
    row.current = { enabled: true, instanceId: null };
    updates.length = 0;
    runtime.container = false;
  });

  it("sends only the documented fields", async () => {
    const fetchImpl = okFetch();

    expect(await sendTelemetryPing(fetchImpl)).toBe(true);

    const [url, init] = fetchImpl.mock.calls[0];
    expect(url).toBe("https://homeio.app/api/stats");
    const body = JSON.parse(String(init.body));
    expect(Object.keys(body).sort()).toEqual([
      "arch",
      "distro",
      "distroVersion",
      "install",
      "instanceId",
      "platform",
      "version",
    ]);
    expect(body.install).toBe("host");
    expect(body.instanceId).toMatch(/^[0-9a-f-]{36}$/);
    expect(body.arch).toBe(process.arch);
    expect(body.platform).toBe(process.platform);
  });

  it("reports Docker without a distro, since os-release would name the image", async () => {
    runtime.container = true;
    const fetchImpl = okFetch();

    await sendTelemetryPing(fetchImpl);

    const body = JSON.parse(String(fetchImpl.mock.calls[0][1].body));
    expect(body).toMatchObject({ install: "docker", distro: null, distroVersion: null });
  });

  it("keeps the same instance ID across pings", async () => {
    const fetchImpl = okFetch();

    await sendTelemetryPing(fetchImpl);
    await sendTelemetryPing(fetchImpl);

    const ids = fetchImpl.mock.calls.map(([, init]) => JSON.parse(String(init.body)).instanceId);
    expect(ids[0]).toBe(ids[1]);
    expect(updates.filter((values) => "telemetryInstanceId" in values)).toHaveLength(1);
  });

  it("sends nothing when turned off in settings", async () => {
    await setTelemetryEnabled(false);
    const fetchImpl = okFetch();

    expect(await sendTelemetryPing(fetchImpl)).toBe(false);
    expect(fetchImpl).not.toHaveBeenCalled();
  });

  it("sends nothing when HOMEIO_TELEMETRY=false, whatever the setting says", async () => {
    env.HOMEIO_TELEMETRY = false;
    const fetchImpl = okFetch();

    expect(await sendTelemetryPing(fetchImpl)).toBe(false);
    expect(fetchImpl).not.toHaveBeenCalled();
    expect(await getTelemetrySettings()).toEqual({ enabled: false, disabledByEnv: true });
  });

  it("sends nothing outside production or in demo mode", async () => {
    const fetchImpl = okFetch();

    env.NODE_ENV = "development";
    expect(await sendTelemetryPing(fetchImpl)).toBe(false);

    env.NODE_ENV = "production";
    env.DEMO_MODE = true;
    expect(await sendTelemetryPing(fetchImpl)).toBe(false);

    expect(fetchImpl).not.toHaveBeenCalled();
  });

  it("swallows network failures", async () => {
    const fetchImpl = vi.fn(async () => {
      throw new Error("offline");
    }) as unknown as typeof fetch;

    await expect(sendTelemetryPing(fetchImpl)).resolves.toBe(false);
  });
});

describe("parseOsRelease", () => {
  it("reads ID and VERSION_ID, quoted or not", () => {
    expect(parseOsRelease('PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"\nID=debian\nVERSION_ID="12"\n')).toEqual({
      distro: "debian",
      distroVersion: "12",
    });
    expect(parseOsRelease("ID=ubuntu\nVERSION_ID='24.04'\n")).toEqual({ distro: "ubuntu", distroVersion: "24.04" });
  });

  it("keeps a rolling release without a version", () => {
    expect(parseOsRelease("ID=arch\nBUILD_ID=rolling\n")).toEqual({ distro: "arch", distroVersion: null });
  });

  it("drops values that are not plain identifiers", () => {
    expect(parseOsRelease('ID="My Custom Distro"\nVERSION_ID=1\n')).toEqual({ distro: null, distroVersion: null });
    expect(parseOsRelease("")).toEqual({ distro: null, distroVersion: null });
  });
});
byte
5.473
baris
168
Jenis
ts
Commit terakhir
16 Oktober 2026