Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  dashboard.links.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { beforeEach, describe, expect, it, vi } from "vitest";
import { dashboardCommand } from "./dashboard.js";

const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
const resolveGatewayPortMock = vi.hoisted(() => vi.fn());
const resolveControlUiLinksMock = vi.hoisted(() => vi.fn());
const detectBrowserOpenSupportMock = vi.hoisted(() => vi.fn());
const openUrlMock = vi.hoisted(() => vi.fn());
const formatControlUiSshHintMock = vi.hoisted(() => vi.fn());
const copyToClipboardMock = vi.hoisted(() => vi.fn());
const resolveSecretRefValuesMock = vi.hoisted(() => vi.fn());

vi.mock("../config/config.js", () => ({
  readConfigFileSnapshot: readConfigFileSnapshotMock,
  resolveGatewayPort: resolveGatewayPortMock,
}));

vi.mock("./onboard-helpers.js", () => ({
  resolveControlUiLinks: resolveControlUiLinksMock,
  detectBrowserOpenSupport: detectBrowserOpenSupportMock,
  openUrl: openUrlMock,
  formatControlUiSshHint: formatControlUiSshHintMock,
}));

vi.mock("../infra/clipboard.js", () => ({
  copyToClipboard: copyToClipboardMock,
}));

vi.mock("../secrets/resolve.js", () => ({
  resolveSecretRefValues: resolveSecretRefValuesMock,
}));

const runtime = {
  log: vi.fn(),
  error: vi.fn(),
  exit: vi.fn(),
};

function resetRuntime() {
  runtime.log.mockClear();
  runtime.error.mockClear();
  runtime.exit.mockClear();
}

function mockSnapshot(token: unknown = "abc") {
  readConfigFileSnapshotMock.mockResolvedValue({
    path: "/tmp/openclaw.json",
    exists: true,
    raw: "{}",
    parsed: {},
    valid: true,
    config: { gateway: { auth: { token } } },
    issues: [],
    legacyIssues: [],
  });
  resolveGatewayPortMock.mockReturnValue(18789);
  resolveControlUiLinksMock.mockReturnValue({
    httpUrl: "http://127.0.0.1:18789/",
    wsUrl: "ws://127.0.0.1:18789",
  });
  resolveSecretRefValuesMock.mockReset();
}

describe("dashboardCommand", () => {
  beforeEach(() => {
    resetRuntime();
    readConfigFileSnapshotMock.mockClear();
    resolveGatewayPortMock.mockClear();
    resolveControlUiLinksMock.mockClear();
    detectBrowserOpenSupportMock.mockClear();
    openUrlMock.mockClear();
    formatControlUiSshHintMock.mockClear();
    copyToClipboardMock.mockClear();
    delete process.env.OPENCLAW_GATEWAY_TOKEN;
    delete process.env.CUSTOM_GATEWAY_TOKEN;
  });

  it("opens and copies the dashboard link by default", async () => {
    mockSnapshot("abc123");
    copyToClipboardMock.mockResolvedValue(true);
    detectBrowserOpenSupportMock.mockResolvedValue({ ok: true });
    openUrlMock.mockResolvedValue(true);

    await dashboardCommand(runtime);

    expect(resolveControlUiLinksMock).toHaveBeenCalledWith({
      port: 18789,
      bind: "loopback",
      customBindHost: undefined,
      basePath: undefined,
    });
    // clipboard and browser still get the full authenticated URL
    expect(copyToClipboardMock).toHaveBeenCalledWith("http://127.0.0.1:18789/#token=abc123");
    expect(openUrlMock).toHaveBeenCalledWith("http://127.0.0.1:18789/#token=abc123");
    expect(runtime.log).toHaveBeenCalledWith(
      "Opened in your browser. Keep that tab to control OpenClaw.",
    );
  });

  it("never logs the gateway token in the dashboard URL (CVE regression)", async () => {
    const secretToken = "super-secret-bearer-token";
    mockSnapshot(secretToken);
    copyToClipboardMock.mockResolvedValue(true);
    detectBrowserOpenSupportMock.mockResolvedValue({ ok: true });
    openUrlMock.mockResolvedValue(true);

    await dashboardCommand(runtime);

    // Clipboard and browser should still receive the tokenized URL.
    expect(copyToClipboardMock).toHaveBeenCalledWith(
      `http://127.0.0.1:18789/#token=${secretToken}`,
    );
    expect(openUrlMock).toHaveBeenCalledWith(`http://127.0.0.1:18789/#token=${secretToken}`);

    // The logged output must never contain the token — it flows into
    // console-captured log files readable by operator.read-scoped devices.
    for (const call of runtime.log.mock.calls) {
      const line = String(call[0]);
      expect(line).not.toContain(secretToken);
      expect(line).not.toContain("#token=");
    }

    // Base URL should be logged without the fragment.
    expect(runtime.log).toHaveBeenCalledWith("Dashboard URL: http://127.0.0.1:18789/");
    expect(runtime.log).toHaveBeenCalledWith("Token auto-auth included in browser/clipboard URL.");
  });

  it("prints SSH hint when browser cannot open", async () => {
    mockSnapshot("shhhh");
    copyToClipboardMock.mockResolvedValue(false);
    detectBrowserOpenSupportMock.mockResolvedValue({
      ok: false,
      reason: "ssh",
    });
    formatControlUiSshHintMock.mockReturnValue("ssh hint");

    await dashboardCommand(runtime);

    expect(openUrlMock).not.toHaveBeenCalled();
    expect(runtime.log).toHaveBeenCalledWith("ssh hint");
  });

  it("never passes token to SSH hint (CVE regression — SSH path)", async () => {
    const secretToken = "super-secret-bearer-token";
    mockSnapshot(secretToken);
    copyToClipboardMock.mockResolvedValue(false);
    detectBrowserOpenSupportMock.mockResolvedValue({ ok: false, reason: "ssh" });
    formatControlUiSshHintMock.mockReturnValue("ssh hint without token");

    await dashboardCommand(runtime);

    // formatControlUiSshHint must NOT receive the token — the returned
    // hint string is written to runtime.log, which flows into the same
    // console-captured log file readable by operator.read-scoped devices.
    expect(formatControlUiSshHintMock).toHaveBeenCalledWith({ port: 18789, basePath: undefined });
    expect(formatControlUiSshHintMock).not.toHaveBeenCalledWith(
      expect.objectContaining({ token: expect.anything() }),
    );

    // Double-check: no logged line contains the secret.
    for (const call of runtime.log.mock.calls) {
      const line = String(call[0]);
      expect(line).not.toContain(secretToken);
      expect(line).not.toContain("#token=");
    }
  });

  it("respects --no-open and tells user token URL is in clipboard", async () => {
    mockSnapshot("abc");
    copyToClipboardMock.mockResolvedValue(true);

    await dashboardCommand(runtime, { noOpen: true });

    expect(detectBrowserOpenSupportMock).not.toHaveBeenCalled();
    expect(openUrlMock).not.toHaveBeenCalled();
    expect(runtime.log).toHaveBeenCalledWith(
      "Browser launch disabled (--no-open). Token-authenticated URL copied to clipboard.",
    );
  });

  it("respects --no-open with plain URL hint when clipboard fails", async () => {
    mockSnapshot("abc");
    copyToClipboardMock.mockResolvedValue(false);

    await dashboardCommand(runtime, { noOpen: true });

    expect(runtime.log).toHaveBeenCalledWith(
      "Browser launch disabled (--no-open). Use the URL above.",
    );
  });

  it("prints non-tokenized URL with guidance when token SecretRef is unresolved", async () => {
    mockSnapshot({
      source: "env",
      provider: "default",
      id: "MISSING_GATEWAY_TOKEN",
    });
    copyToClipboardMock.mockResolvedValue(true);
    detectBrowserOpenSupportMock.mockResolvedValue({ ok: true });
    openUrlMock.mockResolvedValue(true);
    resolveSecretRefValuesMock.mockRejectedValue(new Error("missing env var"));

    await dashboardCommand(runtime);

    expect(copyToClipboardMock).toHaveBeenCalledWith("http://127.0.0.1:18789/");
    expect(runtime.log).toHaveBeenCalledWith(
      expect.stringContaining("Token auto-auth unavailable"),
    );
    expect(runtime.log).toHaveBeenCalledWith(
      expect.stringContaining(
        "gateway.auth.token SecretRef is unresolved (env:default:MISSING_GATEWAY_TOKEN).",
      ),
    );
    expect(runtime.log).not.toHaveBeenCalledWith(expect.stringContaining("missing env var"));
  });

  it("keeps URL non-tokenized when token SecretRef is unresolved but env fallback exists", async () => {
    mockSnapshot({
      source: "env",
      provider: "default",
      id: "MISSING_GATEWAY_TOKEN",
    });
    process.env.OPENCLAW_GATEWAY_TOKEN = "fallback-token";
    copyToClipboardMock.mockResolvedValue(true);
    detectBrowserOpenSupportMock.mockResolvedValue({ ok: true });
    openUrlMock.mockResolvedValue(true);
    resolveSecretRefValuesMock.mockRejectedValue(new Error("missing env var"));

    await dashboardCommand(runtime);

    expect(copyToClipboardMock).toHaveBeenCalledWith("http://127.0.0.1:18789/");
    expect(openUrlMock).toHaveBeenCalledWith("http://127.0.0.1:18789/");
    expect(runtime.log).toHaveBeenCalledWith(
      expect.stringContaining("Token auto-auth is disabled for SecretRef-managed"),
    );
    expect(runtime.log).not.toHaveBeenCalledWith(
      expect.stringContaining("Token auto-auth unavailable"),
    );
  });

  it("keeps URL non-tokenized when env-template gateway.auth.token is unresolved", async () => {
    mockSnapshot("${CUSTOM_GATEWAY_TOKEN}");
    copyToClipboardMock.mockResolvedValue(true);
    detectBrowserOpenSupportMock.mockResolvedValue({ ok: true });
    openUrlMock.mockResolvedValue(true);

    await dashboardCommand(runtime);

    expect(copyToClipboardMock).toHaveBeenCalledWith("http://127.0.0.1:18789/");
    expect(openUrlMock).toHaveBeenCalledWith("http://127.0.0.1:18789/");
    expect(runtime.log).toHaveBeenCalledWith(
      expect.stringContaining(
        "Token auto-auth unavailable: gateway.auth.token SecretRef is unresolved (env:default:CUSTOM_GATEWAY_TOKEN).",
      ),
    );
    expect(runtime.log).not.toHaveBeenCalledWith(
      expect.stringContaining("Token auto-auth is disabled for SecretRef-managed"),
    );
  });
});

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge