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


Quelle  diagnostic-support-export.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 JSZip from "jszip";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { emitDiagnosticEvent, resetDiagnosticEventsForTest } from "../infra/diagnostic-events.js";
import {
  resetDiagnosticStabilityBundleForTest,
  writeDiagnosticStabilityBundleSync,
} from "./diagnostic-stability-bundle.js";
import {
  resetDiagnosticStabilityRecorderForTest,
  startDiagnosticStabilityRecorder,
  stopDiagnosticStabilityRecorder,
} from "./diagnostic-stability.js";
import { writeDiagnosticSupportExport } from "./diagnostic-support-export.js";
import {
  redactSupportString,
  redactTextForSupport,
  sanitizeSupportConfigValue,
  sanitizeSupportSnapshotValue,
} from "./diagnostic-support-redaction.js";
import type { LogTailPayload } from "./log-tail.js";

async function readZipTextEntries(file: string): Promise<Record<string, string>> {
  const zip = await JSZip.loadAsync(fs.readFileSync(file));
  const entries: Record<string, string> = {};
  for (const [name, entry] of Object.entries(zip.files)) {
    if (!entry.dir) {
      entries[name] = await entry.async("string");
    }
  }
  return entries;
}

describe("diagnostic support export", () => {
  let tempDir: string;

  beforeEach(() => {
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-support-export-"));
    resetDiagnosticEventsForTest();
    resetDiagnosticStabilityRecorderForTest();
    resetDiagnosticStabilityBundleForTest();
  });

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

  it("writes a shareable zip without raw chats, webhook bodies, or secrets", async () => {
    const fakeToken = "sk-test-support-export-secret-token-1234567890";
    const fakeAwsKey = ["AKIA", "IOSFODNN7EXAMPLE"].join("");
    const fakeJwt = [
      "eyJhbGciOiJIUzI1NiIs",
      "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4i",
      "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
    ].join(".");
    const privateChat = "private user said diagnose my bank transfer";
    const webhookBody = "raw webhook body with message contents";
    const credentialUrl =
      "wss://support-user:support-password@gateway.example/ws?token=short-token&ok=1";
    const configPath = path.join(tempDir, "openclaw.json");
    fs.writeFileSync(
      configPath,
      JSON.stringify(
        {
          gateway: {
            mode: "local",
            bind: "loopback",
            port: 18789,
            auth: {
              mode: "token",
              token: fakeToken,
            },
          },
          logging: {
            redactSensitive: "off",
          },
          channels: {
            telegram: {
              accounts: {
                "15555551212": {
                  botToken: fakeToken,
                  allowFrom: [privateChat],
                  ownerId: 8675309001,
                },
              },
            },
          },
          agents: [{ name: "personal-agent", instructions: privateChat }],
        },
        null,
        2,
      ),
      "utf8",
    );

    startDiagnosticStabilityRecorder();
    emitDiagnosticEvent({
      type: "webhook.error",
      channel: "telegram",
      chatId: "15555551212",
      error: webhookBody,
    });
    emitDiagnosticEvent({
      type: "payload.large",
      surface: "gateway.http.json",
      action: "rejected",
      bytes: 2048,
      limitBytes: 1024,
      reason: "json_body_limit",
    });
    const bundle = writeDiagnosticStabilityBundleSync({
      reason: "gateway.restart_startup_failed",
      stateDir: tempDir,
      now: new Date("2026-04-22T12:00:00.000Z"),
    });
    expect(bundle.status).toBe("written");

    const logTail: LogTailPayload = {
      file: path.join(tempDir, "logs", "openclaw.log"),
      cursor: 200,
      size: 200,
      truncated: false,
      reset: false,
      lines: [
        JSON.stringify({
          time: "2026-04-22T12:00:00.000Z",
          level: "info",
          subsystem: "gateway",
          component: "gateway/server",
          channel: "telegram",
          sessionId: "gateway-session-15555551212",
          sessionKey: "matrix:!supportRoom:matrix.example.com:$supportEventSecret",
          msg: `gateway websocket listening at ${credentialUrl} Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== ${fakeAwsKey} ${fakeJwt} Cookie: sid=secret`,
          hostname: "support-host",
          message: privateChat,
          body: webhookBody,
          authorization: `Bearer ${fakeToken}`,
          statusCode: 200,
        }),
        JSON.stringify({
          "0": JSON.stringify({ module: "matrix-auto-reply" }),
          "1": "matrix logged in as @support-user:matrix.example.com",
          _meta: {
            logLevelName: "info",
            name: JSON.stringify({
              module: "matrix-auto-reply",
              storePath: path.join(tempDir, "cron", "jobs.json"),
            }),
            hostname: "support-host",
          },
          time: "2026-04-22T12:00:00.100Z",
        }),
        JSON.stringify({
          time: "2026-04-22T12:00:00.200Z",
          level: "info",
          component: "gateway/server",
          msg: "user said structured secret payload",
        }),
        JSON.stringify({
          "0": JSON.stringify({ subsystem: "gateway/channels/matrix" }),
          "1": privateChat,
          _meta: {
            logLevelName: "warn",
            name: "gateway-runtime",
            hostname: "support-host",
          },
          time: "2026-04-22T12:00:00.300Z",
        }),
        `plain fallback ${privateChat} ${fakeToken}`,
      ],
    };
    let requestedLogTail: { limit?: number; maxBytes?: number } | undefined;

    const outputPath = path.join(tempDir, "support.zip");
    const result = await writeDiagnosticSupportExport({
      env: {
        ...process.env,
        HOME: tempDir,
        OPENCLAW_STATE_DIR: tempDir,
      },
      stateDir: tempDir,
      outputPath,
      now: new Date("2026-04-22T12:00:01.000Z"),
      readLogTail: async (params) => {
        requestedLogTail = params;
        return logTail;
      },
      readStatusSnapshot: async () => ({
        service: {
          loaded: true,
          command: {
            programArguments: ["openclaw", "gateway", "run", "--token", fakeToken],
            environment: {
              HOME: tempDir,
              OPENCLAW_GATEWAY_TOKEN: fakeToken,
            },
          },
        },
        gateway: {
          probeUrl: credentialUrl,
        },
        warning: {
          chatId: 4444555566,
          message: privateChat,
        },
      }),
      readHealthSnapshot: async () => ({
        ok: true,
        channels: {
          telegram: {
            accounts: {
              "15555551212": {
                accountId: 15555551212,
                configured: true,
                phone: 4444555566,
                probe: {
                  ok: false,
                  error: webhookBody,
                },
              },
            },
          },
        },
      }),
    });

    expect(result.path).toBe(outputPath);
    expect(result.bytes).toBeGreaterThan(0);
    expect(requestedLogTail).toMatchObject({
      limit: 5000,
      maxBytes: 1_000_000,
    });

    const entries = await readZipTextEntries(outputPath);
    expect(Object.keys(entries).toSorted()).toEqual([
      "config/sanitized.json",
      "config/shape.json",
      "diagnostics.json",
      "health/gateway-health.json",
      "logs/openclaw-sanitized.jsonl",
      "manifest.json",
      "stability/latest.json",
      "status/gateway-status.json",
      "summary.md",
    ]);

    const combined = Object.values(entries).join("\n");
    expect(combined).not.toContain(fakeToken);
    expect(combined).not.toContain(privateChat);
    expect(combined).not.toContain(webhookBody);
    expect(combined).not.toContain("15555551212");
    expect(combined).not.toContain("4444555566");
    expect(combined).not.toContain("8675309001");
    expect(combined).not.toContain("support-password");
    expect(combined).not.toContain("short-token");
    expect(combined).not.toContain(tempDir);
    expect(combined).not.toContain("cron/jobs.json");
    expect(combined).not.toContain(os.hostname());
    expect(combined).not.toContain("QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
    expect(combined).not.toContain("sid=secret");
    expect(combined).not.toContain("structured secret payload");
    expect(combined).not.toContain("gateway-session-15555551212");
    expect(combined).not.toContain("supportEventSecret");
    expect(combined).not.toContain(fakeAwsKey);
    expect(combined).not.toContain(fakeJwt);
    expect(combined).toContain("payload.large");
    expect(combined).toContain("gateway.http.json");
    expect(combined).toContain("$OPENCLAW_STATE_DIR");
    expect(combined).toContain("<redacted-hostname>");
    expect(combined).toContain("gateway-status.json");
    expect(combined).toContain("gateway-health.json");
    expect(combined).toContain("Attach this zip to the bug report");

    const sanitizedLogs = entries["logs/openclaw-sanitized.jsonl"];
    expect(sanitizedLogs).toContain('"subsystem":"gateway"');
    expect(sanitizedLogs).toContain('"component":"gateway/server"');
    expect(sanitizedLogs).toContain('"channel":"telegram"');
    expect(sanitizedLogs).not.toContain("sessionId");
    expect(sanitizedLogs).not.toContain("sessionKey");
    expect(sanitizedLogs).toContain("gateway websocket listening");
    expect(sanitizedLogs).toContain(
      "wss://<redacted>:<redacted>@gateway.example/ws?token=<redacted>",
    );
    expect(sanitizedLogs).toContain("Basic <redacted>");
    expect(sanitizedLogs).toContain("Cookie: <redacted>");
    expect(sanitizedLogs).toContain("<redacted-aws-key>");
    expect(sanitizedLogs).toContain("<redacted-jwt>");
    expect(sanitizedLogs).toContain('"module":"matrix-auto-reply"');
    expect(sanitizedLogs).toContain('"subsystem":"gateway/channels/matrix"');
    expect(sanitizedLogs).toContain('"logger":"gateway-runtime"');
    expect(sanitizedLogs).toContain('"level":"warn"');
    expect(sanitizedLogs).toContain("matrix logged in as <redacted-matrix-user>");
    expect(sanitizedLogs).toContain('"omitted":"log-message"');
    expect(sanitizedLogs).toContain('"omittedLogMessageBytes"');
    expect(sanitizedLogs).toContain('"omittedLogMessageCount"');
    expect(sanitizedLogs).not.toContain("private user said");
    expect(sanitizedLogs).not.toContain("@support-user:matrix.example.com");
    expect(sanitizedLogs).not.toContain("support-host");
    expect(sanitizedLogs).toContain('"omitted":"unparsed"');

    const status = JSON.parse(entries["status/gateway-status.json"] ?? "{}") as {
      data?: {
        service?: {
          command?: {
            programArguments?: string[];
            environment?: Record<string, string>;
          };
        };
      };
    };
    expect(status.data?.service?.command?.programArguments).toEqual([
      "openclaw",
      "gateway",
      "run",
      "--token",
      "<redacted>",
    ]);
    expect(status.data?.service?.command?.environment?.OPENCLAW_GATEWAY_TOKEN).toBe("<redacted>");
    expect(JSON.stringify(status)).toContain(
      "wss://<redacted>:<redacted>@gateway.example/ws?token=<redacted>",
    );

    const health = JSON.parse(entries["health/gateway-health.json"] ?? "{}") as {
      data?: {
        channels?: {
          telegram?: {
            accounts?: { count?: number };
          };
        };
      };
    };
    expect(health.data?.channels?.telegram?.accounts).toEqual({ count: 1 });

    const configShape = JSON.parse(entries["config/shape.json"] ?? "{}") as {
      gateway?: { mode?: string; authMode?: string };
      channels?: { ids?: string[] };
    };
    expect(configShape.gateway).toMatchObject({
      mode: "local",
      authMode: "token",
    });
    expect(configShape.channels?.ids).toEqual(["telegram"]);

    const sanitizedConfig = JSON.parse(entries["config/sanitized.json"] ?? "{}") as {
      gateway?: {
        mode?: string;
        port?: number;
        auth?: {
          mode?: string;
          token?: string;
        };
      };
      channels?: {
        telegram?: {
          accounts?: Record<
            string,
            { botToken?: string; allowFrom?: { redacted?: boolean }; ownerId?: string }
          >;
        };
      };
      logging?: {
        redactSensitive?: string;
      };
      agents?: Array<{ name?: string; instructions?: string }>;
    };
    expect(sanitizedConfig.gateway).toMatchObject({
      mode: "local",
      port: 18789,
      auth: {
        mode: "token",
        token: "<redacted>",
      },
    });
    expect(sanitizedConfig.logging).toMatchObject({
      redactSensitive: "off",
    });
    expect(Object.keys(sanitizedConfig.channels?.telegram?.accounts ?? {})).toEqual([
      "<redacted-account-1>",
    ]);
    const sanitizedTelegramAccount =
      sanitizedConfig.channels?.telegram?.accounts?.["<redacted-account-1>"];
    expect(sanitizedTelegramAccount?.botToken).toBe("<redacted>");
    expect(sanitizedTelegramAccount?.allowFrom).toEqual({ redacted: true, count: 1 });
    expect(sanitizedTelegramAccount?.ownerId).toBe("<redacted>");
    expect(sanitizedConfig.agents?.[0]?.name).toBe("personal-agent");
    expect(sanitizedConfig.agents?.[0]?.instructions).toBe("<redacted>");
  });

  it("sanitizes imported stability bundles before adding them to support exports", async () => {
    const bundlePath = path.join(tempDir, "imported-stability.json");
    const outputPath = path.join(tempDir, "support-imported-stability.zip");
    const importedBundle = {
      version: 1,
      generatedAt: "2026-04-22T12:00:00.000Z",
      reason: "private reason token=secret",
      process: { pid: 123, platform: "darwin", arch: "arm64", node: "24.14.1", uptimeMs: 1000 },
      host: { hostname: "private-hostname" },
      error: { name: "private error name", code: "ERR_TEST" },
      snapshot: {
        generatedAt: "2026-04-22T12:00:00.000Z",
        capacity: 1000,
        count: 1,
        dropped: 0,
        events: [
          {
            seq: 1,
            ts: 1,
            type: "webhook.error",
            channel: "telegram",
            reason: "private event reason",
            error: "event-error-secret",
          },
        ],
        summary: {
          byType: {
            "webhook.error": 1,
            "private summary type": 1,
          },
          privateSummary: "summary-secret",
        },
      },
    };
    fs.writeFileSync(bundlePath, `${JSON.stringify(importedBundle, null, 2)}\n`, "utf8");

    await writeDiagnosticSupportExport({
      env: {
        ...process.env,
        HOME: tempDir,
        OPENCLAW_STATE_DIR: tempDir,
      },
      stateDir: tempDir,
      outputPath,
      stabilityBundle: bundlePath,
      now: new Date("2026-04-22T12:00:01.000Z"),
      readLogTail: async () => ({
        file: path.join(tempDir, "logs", "openclaw.log"),
        cursor: 0,
        size: 0,
        truncated: false,
        reset: false,
        lines: [],
      }),
    });

    const entries = await readZipTextEntries(outputPath);
    const stability = JSON.parse(entries["stability/latest.json"] ?? "{}") as {
      reason?: string;
      host?: { hostname?: string };
      error?: { code?: string; name?: string };
      snapshot?: {
        events?: Array<Record<string, unknown>>;
        summary?: { byType?: Record<string, number> };
      };
    };
    expect(stability.reason).toBe("unknown");
    expect(stability.host).toEqual({ hostname: "<redacted-hostname>" });
    expect(stability.error).toEqual({ code: "ERR_TEST" });
    expect(stability.snapshot?.events?.[0]).toEqual({
      seq: 1,
      ts: 1,
      type: "webhook.error",
      channel: "telegram",
    });
    expect(stability.snapshot?.summary?.byType).toEqual({ "webhook.error": 1 });

    const combined = Object.values(entries).join("\n");
    for (const secret of [
      "private reason",
      "private-hostname",
      "private error name",
      "private event reason",
      "event-error-secret",
      "private summary type",
      "summary-secret",
    ]) {
      expect(combined).not.toContain(secret);
    }
  });

  it("redacts numeric private fields in support snapshots and config", () => {
    const redaction = {
      env: {
        HOME: tempDir,
        OPENCLAW_STATE_DIR: tempDir,
      },
      stateDir: tempDir,
    };

    expect(sanitizeSupportSnapshotValue(15555551212, redaction, "chatId")).toBe("<redacted>");
    expect(sanitizeSupportSnapshotValue(15555551212, redaction, "messageId")).toBe("<redacted>");
    expect(sanitizeSupportSnapshotValue(200, redaction, "statusCode")).toBe(200);
    expect(sanitizeSupportConfigValue(15555551212, redaction, "ownerId")).toBe("<redacted>");
    expect(sanitizeSupportConfigValue(18789, redaction, "port")).toBe(18789);
  });

  it("blocks prototype keys and caps support sanitizer width", () => {
    const redaction = {
      env: {
        HOME: tempDir,
        OPENCLAW_STATE_DIR: tempDir,
      },
      stateDir: tempDir,
    };
    const wideSnapshot: Record<string, unknown> = {
      ["__proto__"]: "polluted",
      constructor: "polluted",
      prototype: "polluted",
    };
    for (let index = 0; index < 1005; index += 1) {
      wideSnapshot[`field${String(index).padStart(4, "0")}`] = index;
    }

    const snapshot = sanitizeSupportSnapshotValue(wideSnapshot, redaction) as Record<
      string,
      unknown
    >;

    expect(Object.getPrototypeOf(snapshot)).toBe(null);
    expect(Object.hasOwn(snapshot, "__proto__")).toBe(false);
    expect(snapshot.constructor).toBeUndefined();
    expect(snapshot.prototype).toBeUndefined();
    expect(snapshot.field0000).toBe(0);
    expect(snapshot.field0999).toBe(999);
    expect(snapshot.field1000).toBeUndefined();
    expect(snapshot["<truncated>"]).toEqual({
      truncated: true,
      count: 1008,
      limit: 1000,
    });

    const array = sanitizeSupportConfigValue(
      Array.from({ length: 1005 }, (_entry, index) => ({ name: `item-${index}` })),
      redaction,
    ) as Record<string, unknown>;

    expect(Array.isArray(array)).toBe(false);
    expect((array.items as unknown[]).length).toBe(1000);
    expect(array.truncated).toBe(true);
    expect(array.count).toBe(1005);
    expect(array.limit).toBe(1000);
  });

  it("redacts support text identifiers without hiding useful URL hosts", () => {
    const fakeAwsKey = ["ASIA", "IOSFODNN7EXAMPLE"].join("");
    const fakeJwt = [
      "eyJhbGciOiJIUzI1NiIs",
      "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4i",
      "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
    ].join(".");
    const cases = [
      [
        "connect wss://support-user:support-password@gateway.example/ws?token=short-token&ok=1",
        "connect wss://<redacted>:<redacted>@gateway.example/ws?token=<redacted>",
      ],
      [
        "connect https://gateway.example/ws?access-token=short-token",
        "connect https://gateway.example/ws?access-token=<redacted>",
      ],
      [
        "connect https://gateway.example/ws?hook-token=hook-secret",
        "connect https://gateway.example/ws?hook-token=<redacted>",
      ],
      ["connect https://token@gateway.example/ws", "connect https://<redacted>@gateway.example/ws"],
      ["auth Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", "auth Basic <redacted>"],
      ["Cookie: sid=secret; theme=light", "Cookie: <redacted>"],
      [`aws ${fakeAwsKey}`, "aws <redacted-aws-key>"],
      [`jwt ${fakeJwt}`, "jwt <redacted-jwt>"],
      ["email alice@example.com", "email <redacted-email>"],
      ["matrix @support-user:matrix.example.com", "matrix <redacted-matrix-user>"],
      ["room !support-room:matrix.example.com", "room <redacted-matrix-room>"],
      ["event $F0Zlxky8bavuqH6MK75Av_c7UWFLp550WTQ1EA-F0KM", "event <redacted-matrix-event>"],
      ["notify @support_bot now", "notify <redacted-handle> now"],
      ["phone 15555551212", "phone <redacted-id>"],
    ] as const;

    for (const [input, expected] of cases) {
      expect(redactTextForSupport(input)).toBe(expected);
    }
  });

  it("redacts Windows USERPROFILE paths when HOME is unset", () => {
    const userProfile = "C:\\Users\\support-user";
    const stateDir = `${userProfile}\\AppData\\Roaming\\openclaw`;
    const redaction = {
      env: {
        USERPROFILE: userProfile,
        OPENCLAW_STATE_DIR: stateDir,
      },
      stateDir,
    };

    expect(redactSupportString(`${stateDir}\\logs\\gateway.log`, redaction)).toBe(
      "$OPENCLAW_STATE_DIR\\logs\\gateway.log",
    );
    expect(
      redactSupportString(`failed at ${userProfile}\\Documents\\snapshot-error.txt`, redaction),
    ).toBe("failed at ~\\Documents\\snapshot-error.txt");
    expect(
      redactSupportString(
        "failed at c:\\users\\support-user\\Documents\\snapshot-error.txt",
        redaction,
      ),
    ).toBe("failed at ~\\Documents\\snapshot-error.txt");

    const status = sanitizeSupportSnapshotValue(
      {
        service: {
          command: {
            programArguments: [
              "node",
              `${userProfile}\\openclaw\\dist\\index.js`,
              "--config",
              `${stateDir}\\openclaw.json`,
            ],
            sourcePath: "c:\\users\\support-user\\AppData\\Local\\openclaw\\gateway-service.json",
          },
        },
      },
      redaction,
    );
    const serialized = JSON.stringify(status);
    expect(serialized).not.toContain("support-user");
    expect(serialized).toContain("~\\\\openclaw\\\\dist\\\\index.js");
    expect(serialized).toContain("$OPENCLAW_STATE_DIR\\\\openclaw.json");
    expect(serialized).toContain("~\\\\AppData\\\\Local\\\\openclaw\\\\gateway-service.json");
  });

  it("keeps writing when status and health snapshots fail", async () => {
    const fakeToken = "sk-test-support-export-secret-token-1234567890";
    const outputPath = path.join(tempDir, "support-failed-snapshots.zip");

    await writeDiagnosticSupportExport({
      env: {
        ...process.env,
        HOME: tempDir,
        OPENCLAW_STATE_DIR: tempDir,
      },
      stateDir: tempDir,
      outputPath,
      now: new Date("2026-04-22T12:00:01.000Z"),
      readLogTail: async () => ({
        file: path.join(tempDir, "logs", "openclaw.log"),
        cursor: 0,
        size: 0,
        truncated: false,
        reset: false,
        lines: [],
      }),
      readStatusSnapshot: async () => {
        throw new Error(`status failed with token ${fakeToken}`);
      },
      readHealthSnapshot: async () => {
        throw new Error("health failed with PASSWORD=hunter2");
      },
    });

    const entries = await readZipTextEntries(outputPath);
    expect(Object.keys(entries).toSorted()).toContain("status/gateway-status.json");
    expect(Object.keys(entries).toSorted()).toContain("health/gateway-health.json");

    const combined = Object.values(entries).join("\n");
    expect(combined).not.toContain(fakeToken);
    expect(combined).not.toContain("hunter2");
    expect(combined).toContain('"status": "failed"');
    expect(combined).toContain("status snapshot failed");
    expect(combined).toContain("health snapshot failed");
  });

  it("keeps writing when log tail collection fails", async () => {
    const fakeToken = "sk-test-log-tail-secret-token-1234567890";
    const outputPath = path.join(tempDir, "support-failed-log-tail.zip");

    await writeDiagnosticSupportExport({
      env: {
        ...process.env,
        HOME: tempDir,
        OPENCLAW_STATE_DIR: tempDir,
      },
      stateDir: tempDir,
      outputPath,
      now: new Date("2026-04-22T12:00:02.000Z"),
      readLogTail: async () => {
        throw new Error(`log tail failed at ${tempDir}/openclaw.log with token ${fakeToken}`);
      },
    });

    const entries = await readZipTextEntries(outputPath);
    expect(Object.keys(entries).toSorted()).toContain("logs/openclaw-sanitized.jsonl");

    const combined = Object.values(entries).join("\n");
    expect(combined).not.toContain(fakeToken);
    expect(combined).not.toContain(tempDir);
    expect(combined).toContain("log-tail-read-failed");
    expect(combined).toContain("sanitized log tail unavailable");
  });

  it("keeps writing when config stat fails", async () => {
    const fakeToken = "sk-test-config-stat-secret-token-1234567890";
    const configPath = path.join(tempDir, "openclaw.json");
    const outputPath = path.join(tempDir, "support-failed-config-stat.zip");
    fs.writeFileSync(configPath, "{}\n", "utf8");

    const originalStatSync = fs.statSync.bind(fs);
    const statSpy = vi.spyOn(fs, "statSync").mockImplementation((target, options) => {
      if (target === configPath) {
        throw new Error(`config stat failed with token ${fakeToken}`);
      }
      return originalStatSync(target, options as never);
    });

    try {
      await writeDiagnosticSupportExport({
        env: {
          ...process.env,
          HOME: tempDir,
          OPENCLAW_CONFIG_PATH: configPath,
          OPENCLAW_STATE_DIR: tempDir,
        },
        stateDir: tempDir,
        outputPath,
        now: new Date("2026-04-22T12:00:03.000Z"),
        readLogTail: async () => ({
          file: path.join(tempDir, "logs", "openclaw.log"),
          cursor: 0,
          size: 0,
          truncated: false,
          reset: false,
          lines: [],
        }),
      });
    } finally {
      statSpy.mockRestore();
    }

    const entries = await readZipTextEntries(outputPath);
    const combined = Object.values(entries).join("\n");
    expect(Object.keys(entries).toSorted()).toContain("config/shape.json");
    expect(combined).not.toContain(fakeToken);
    expect(combined).toContain('"parseOk": false');
    expect(combined).toContain("config stat failed with token");
    expect(combined).toContain("Attach this zip to the bug report");
  });
});

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