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


Quelle  diagnostic-stability-bundle.test.ts

  Sprache: JAVA
 

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

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { emitDiagnosticEvent, resetDiagnosticEventsForTest } from "../infra/diagnostic-events.js";
import { resetFatalErrorHooksForTest, runFatalErrorHooks } from "../infra/fatal-error-hooks.js";
import {
  installDiagnosticStabilityFatalHook,
  MAX_DIAGNOSTIC_STABILITY_BUNDLE_BYTES,
  readDiagnosticStabilityBundleFileSync,
  readLatestDiagnosticStabilityBundleSync,
  resetDiagnosticStabilityBundleForTest,
  writeDiagnosticStabilityBundleForFailureSync,
  writeDiagnosticStabilityBundleSync,
  type DiagnosticStabilityBundle,
} from "./diagnostic-stability-bundle.js";
import {
  resetDiagnosticStabilityRecorderForTest,
  startDiagnosticStabilityRecorder,
  stopDiagnosticStabilityRecorder,
} from "./diagnostic-stability.js";

describe("diagnostic stability bundles", () => {
  let tempDir: string;

  function resetStabilityBundleTestState(): void {
    resetDiagnosticEventsForTest();
    resetDiagnosticStabilityRecorderForTest();
    resetDiagnosticStabilityBundleForTest();
    resetFatalErrorHooksForTest();
  }

  beforeEach(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-stability-bundle-"));
    resetStabilityBundleTestState();
  });

  afterEach(() => {
    stopDiagnosticStabilityRecorder();
    resetStabilityBundleTestState();
    fs.rmSync(tempDir, { recursive: true, force: true });
  });

  function readBundle(file: string): DiagnosticStabilityBundle {
    return JSON.parse(fs.readFileSync(file, "utf8")) as DiagnosticStabilityBundle;
  }

  function createImportedBundle(): Record<string, unknown> {
    return {
      version: 1,
      generatedAt: "2026-04-22T12:00:00.000Z",
      reason: "gateway.restart_startup_failed",
      process: {
        pid: 123,
        platform: "darwin",
        arch: "arm64",
        node: "24.14.1",
        uptimeMs: 1000,
      },
      host: {
        hostname: "<redacted-hostname>",
      },
      snapshot: {
        generatedAt: "2026-04-22T12:00:00.000Z",
        capacity: 1000,
        count: 1,
        dropped: 0,
        events: [{ seq: 1, ts: 1, type: "webhook.received" }],
        summary: { byType: { "webhook.received": 1 } },
      },
    };
  }

  it("writes a payload-free bundle with safe failure metadata", () => {
    startDiagnosticStabilityRecorder();
    emitDiagnosticEvent({
      type: "webhook.error",
      channel: "telegram",
      chatId: "chat-secret",
      error: "raw diagnostic error with message body",
    });
    emitDiagnosticEvent({
      type: "payload.large",
      surface: "gateway.http.json",
      action: "rejected",
      bytes: 2048,
      limitBytes: 1024,
      reason: "json_body_limit",
    });

    const error = Object.assign(new Error("contains secret message"), { code: "ERR_TEST" });
    const result = writeDiagnosticStabilityBundleSync({
      reason: "gateway.restart_startup_failed",
      error,
      stateDir: tempDir,
      now: new Date("2026-04-22T12:00:00.000Z"),
    });

    expect(result.status).toBe("written");
    const file = result.status === "written" ? result.path : "";
    const bundle = readBundle(file);
    const raw = fs.readFileSync(file, "utf8");

    expect(bundle).toMatchObject({
      version: 1,
      generatedAt: "2026-04-22T12:00:00.000Z",
      reason: "gateway.restart_startup_failed",
      error: {
        name: "Error",
        code: "ERR_TEST",
      },
      host: {
        hostname: "<redacted-hostname>",
      },
      snapshot: {
        count: 2,
      },
    });
    expect(bundle.snapshot.events[0]).toMatchObject({
      type: "webhook.error",
      channel: "telegram",
    });
    expect(bundle.snapshot.events[0]).not.toHaveProperty("chatId");
    expect(bundle.snapshot.events[0]).not.toHaveProperty("error");
    expect(raw).not.toContain("chat-secret");
    expect(raw).not.toContain("message body");
    expect(raw).not.toContain("contains secret message");
    expect(raw).not.toContain(os.hostname());
  });

  it("skips empty recorder snapshots by default", () => {
    const result = writeDiagnosticStabilityBundleSync({
      reason: "uncaught_exception",
      stateDir: tempDir,
    });

    expect(result).toEqual({ status: "skipped", reason: "empty" });
    expect(fs.existsSync(path.join(tempDir, "logs", "stability"))).toBe(false);
  });

  it("writes failure bundles even when the recorder snapshot is empty", () => {
    const result = writeDiagnosticStabilityBundleForFailureSync(
      "gateway.restart_startup_failed",
      Object.assign(new Error("raw startup config payload"), { code: "ERR_CONFIG_PARSE" }),
      {
        stateDir: tempDir,
        now: new Date("2026-04-22T12:00:00.000Z"),
      },
    );

    if (result.status !== "written") {
      throw new Error(`expected written bundle, got ${result.status}`);
    }
    const bundle = readBundle(result.path);
    const raw = fs.readFileSync(result.path, "utf8");
    expect(bundle).toMatchObject({
      reason: "gateway.restart_startup_failed",
      error: {
        name: "Error",
        code: "ERR_CONFIG_PARSE",
      },
      snapshot: {
        count: 0,
        events: [],
      },
    });
    expect(raw).not.toContain("raw startup config payload");
  });

  it("registers a fatal hook only while installed", () => {
    startDiagnosticStabilityRecorder();
    emitDiagnosticEvent({ type: "webhook.received", channel: "telegram" });
    installDiagnosticStabilityFatalHook({ stateDir: tempDir });

    const messages = runFatalErrorHooks({
      reason: "fatal_unhandled_rejection",
      error: Object.assign(new Error("raw text"), { code: "ERR_OUT_OF_MEMORY" }),
    });

    expect(messages).toHaveLength(1);
    expect(messages[0]).toContain("wrote stability bundle:");
    expect(messages[0]).toContain(tempDir);

    resetDiagnosticStabilityBundleForTest();
    expect(runFatalErrorHooks({ reason: "uncaught_exception" })).toEqual([]);
  });

  it("retains only the newest bundle files", () => {
    startDiagnosticStabilityRecorder();
    emitDiagnosticEvent({ type: "webhook.received", channel: "telegram" });

    for (let index = 0; index < 4; index += 1) {
      const result = writeDiagnosticStabilityBundleSync({
        reason: "gateway.restart_respawn_failed",
        stateDir: tempDir,
        now: new Date(`2026-04-22T12:00:0${index}.000Z`),
        retention: 2,
      });
      expect(result.status).toBe("written");
    }

    const bundleDir = path.join(tempDir, "logs", "stability");
    const files = fs.readdirSync(bundleDir).toSorted();
    expect(files).toHaveLength(2);
    expect(files[0]).toContain("12-00-02");
    expect(files[1]).toContain("12-00-03");
  });

  it("reads the newest retained bundle", () => {
    startDiagnosticStabilityRecorder();
    emitDiagnosticEvent({ type: "webhook.received", channel: "telegram" });

    const older = writeDiagnosticStabilityBundleSync({
      reason: "gateway.restart_startup_failed",
      stateDir: tempDir,
      now: new Date("2026-04-22T12:00:00.000Z"),
    });
    const newer = writeDiagnosticStabilityBundleSync({
      reason: "gateway.restart_respawn_failed",
      stateDir: tempDir,
      now: new Date("2026-04-22T12:00:01.000Z"),
    });

    expect(older.status).toBe("written");
    expect(newer.status).toBe("written");

    const latest = readLatestDiagnosticStabilityBundleSync({ stateDir: tempDir });

    expect(latest.status).toBe("found");
    expect(latest.status === "found" ? latest.path : "").toContain("12-00-01");
    expect(latest.status === "found" ? latest.bundle.reason : "").toBe(
      "gateway.restart_respawn_failed",
    );
  });

  it("sanitizes imported bundles before returning them", () => {
    const file = path.join(tempDir, "imported.json");
    const bundle = createImportedBundle();
    Object.assign(bundle, {
      reason: "private reason token=secret",
      privateTopLevel: "top-level-secret",
      error: {
        name: "private error name",
        code: "ERR_TEST",
        message: "error-message-secret",
      },
    });
    Object.assign(bundle.process as Record<string, unknown>, {
      command: "process-command-secret",
    });
    Object.assign(bundle.host as Record<string, unknown>, {
      hostname: "private-hostname",
      fqdn: "host-extra-secret",
    });
    const snapshot = bundle.snapshot as Record<string, unknown>;
    Object.assign(snapshot, {
      privateSnapshot: "snapshot-secret",
      events: [
        {
          seq: 1,
          ts: 1,
          type: "webhook.error",
          channel: "telegram",
          reason: "private event reason",
          chatId: "chat-id-secret",
          error: "event-error-secret",
        },
      ],
      summary: {
        byType: {
          "webhook.error": 1,
          "private summary type": 1,
        },
        privateSummary: "summary-secret",
      },
    });
    fs.writeFileSync(file, `${JSON.stringify(bundle, null, 2)}\n`, "utf8");

    const result = readDiagnosticStabilityBundleFileSync(file);

    expect(result.status).toBe("found");
    if (result.status !== "found") {
      return;
    }
    expect(result.bundle.reason).toBe("unknown");
    expect(result.bundle.host).toEqual({ hostname: "<redacted-hostname>" });
    expect(result.bundle.error).toEqual({ code: "ERR_TEST" });
    expect(result.bundle.snapshot.events[0]).toEqual({
      seq: 1,
      ts: 1,
      type: "webhook.error",
      channel: "telegram",
    });
    expect(result.bundle.snapshot.summary.byType).toEqual({ "webhook.error": 1 });
    const sanitized = JSON.stringify(result.bundle);
    for (const secret of [
      "private reason",
      "top-level-secret",
      "private error name",
      "error-message-secret",
      "process-command-secret",
      "private-hostname",
      "host-extra-secret",
      "snapshot-secret",
      "private event reason",
      "chat-id-secret",
      "event-error-secret",
      "private summary type",
      "summary-secret",
    ]) {
      expect(sanitized).not.toContain(secret);
    }
  });

  it("rejects malformed bundle files", () => {
    const file = path.join(tempDir, "invalid.json");
    fs.writeFileSync(file, "{}\n", "utf8");

    const result = readDiagnosticStabilityBundleFileSync(file);

    expect(result.status).toBe("failed");
    expect(result.status === "failed" ? String(result.error) : "").toContain(
      "Unsupported stability bundle version",
    );
  });

  it("rejects oversized bundle files before reading them", () => {
    const file = path.join(tempDir, "oversized.json");
    fs.closeSync(fs.openSync(file, "w"));
    fs.truncateSync(file, MAX_DIAGNOSTIC_STABILITY_BUNDLE_BYTES + 1);

    const result = readDiagnosticStabilityBundleFileSync(file);

    expect(result.status).toBe("failed");
    expect(result.status === "failed" ? String(result.error) : "").toContain(
      "Stability bundle is too large",
    );
  });

  it("rejects malformed bundle snapshots before returning them", () => {
    const baseBundle = createImportedBundle();
    const baseSnapshot = baseBundle.snapshot as Record<string, unknown>;
    const cases = [
      {
        name: "malformed-event",
        bundle: {
          ...baseBundle,
          snapshot: {
            ...baseSnapshot,
            events: [{ type: "webhook.received", ts: 1 }],
          },
        },
        error: "snapshot.events[0].seq",
      },
      {
        name: "out-of-range-event-timestamp",
        bundle: {
          ...baseBundle,
          snapshot: {
            ...baseSnapshot,
            events: [{ seq: 1, ts: 9e15, type: "webhook.received" }],
          },
        },
        error: "snapshot.events[0].ts",
      },
      {
        name: "null-summary",
        bundle: {
          ...baseBundle,
          snapshot: {
            ...baseSnapshot,
            summary: null,
          },
        },
        error: "snapshot.summary",
      },
    ];

    for (const testCase of cases) {
      const file = path.join(tempDir, `${testCase.name}.json`);
      fs.writeFileSync(file, `${JSON.stringify(testCase.bundle, null, 2)}\n`, "utf8");

      const result = readDiagnosticStabilityBundleFileSync(file);

      expect(result.status).toBe("failed");
      expect(result.status === "failed" ? String(result.error) : "").toContain(testCase.error);
    }
  });
});

¤ Dauer der Verarbeitung: 0.17 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