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, describe, expect, it, vi } from "vitest";
import { CLAUDE_CLI_PROFILE_ID } from "../agents/auth-profiles/constants.js";
import type { AuthProfileStore } from "../agents/auth-profiles/types.js";
import {
noteClaudeCliHealth,
resolveClaudeCliProjectDirForWorkspace,
} from "./doctor-claude-cli.js";
function createStore(profiles: AuthProfileStore["profiles"] = {}): AuthProfileStore {
return {
version: 1,
profiles,
};
}
async function withTempHome<T>(
run: (params: { homeDir: string; workspaceDir: string }) => Promise<T> | T,
): Promise<T> {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-claude-cli-"));
const homeDir = path.join(root, "home");
const workspaceDir = path.join(root, "workspace");
fs.mkdirSync(homeDir, { recursive: true });
fs.mkdirSync(workspaceDir, { recursive: true });
try {
return await run({ homeDir, workspaceDir });
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
}
describe("resolveClaudeCliProjectDirForWorkspace", () => {
it("matches Claude's sanitized workspace project dir shape", () => {
expect(
resolveClaudeCliProjectDirForWorkspace({
workspaceDir: "/Users/vincentkoc/GIT/_Perso/openclaw/.openclaw/workspace",
homeDir: "/Users/vincentkoc",
}),
).toBe(
"/Users/vincentkoc/.claude/projects/-Users-vincentkoc-GIT--Perso-openclaw--openclaw-workspace",
);
});
});
describe("noteClaudeCliHealth", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("stays quiet when Claude CLI is not configured or detected", () => {
const noteFn = vi.fn();
noteClaudeCliHealth(
{},
{
noteFn,
store: createStore(),
readClaudeCliCredentials: () => null,
},
);
expect(noteFn).not.toHaveBeenCalled();
});
it("reports a healthy claude-cli setup with the resolved Claude project dir", async () => {
await withTempHome(({ homeDir, workspaceDir }) => {
const projectDir = resolveClaudeCliProjectDirForWorkspace({ workspaceDir, homeDir });
fs.mkdirSync(projectDir, { recursive: true });
const noteFn = vi.fn();
noteClaudeCliHealth(
{
agents: {
defaults: {
model: { primary: "claude-cli/claude-sonnet-4-6" },
},
},
},
{
homeDir,
workspaceDir,
noteFn,
store: createStore({
[CLAUDE_CLI_PROFILE_ID]: {
type: "oauth",
provider: "claude-cli",
access: "token-a",
refresh: "token-r",
expires: Date.now() + 60_000,
},
}),
readClaudeCliCredentials: () => ({
type: "oauth",
expires: Date.now() + 60_000,
}),
resolveCommandPath: () => "/opt/homebrew/bin/claude",
},
);
expect(noteFn).toHaveBeenCalledTimes(1);
expect(noteFn.mock.calls[0]?.[1]).toBe("Claude CLI");
const body = String(noteFn.mock.calls[0]?.[0]);
expect(body).toContain("Binary: /opt/homebrew/bin/claude.");
expect(body).toContain("Headless Claude auth: OK (oauth).");
expect(body).toContain(
`OpenClaw auth profile: ${CLAUDE_CLI_PROFILE_ID} (provider claude-cli).`,
);
expect(body).toContain("Workspace:");
expect(body).toContain("(writable).");
expect(body).toContain("Claude project dir:");
expect(body).toContain("(present).");
});
});
it("explains the exact bad wiring when the claude-cli auth profile is missing", async () => {
await withTempHome(({ homeDir, workspaceDir }) => {
const noteFn = vi.fn();
noteClaudeCliHealth(
{
agents: {
defaults: {
model: { primary: "claude-cli/claude-sonnet-4-6" },
},
},
},
{
homeDir,
workspaceDir,
noteFn,
store: createStore(),
readClaudeCliCredentials: () => ({
type: "oauth",
expires: Date.now() + 60_000,
}),
resolveCommandPath: () => "/opt/homebrew/bin/claude",
},
);
const body = String(noteFn.mock.calls[0]?.[0]);
expect(body).toContain("Headless Claude auth: OK (oauth).");
expect(body).toContain(`OpenClaw auth profile: missing (${CLAUDE_CLI_PROFILE_ID})`);
expect(body).toContain(
"openclaw models auth login --provider anthropic --method cli --set-default",
);
expect(body).toContain(
"not created yet; it appears after the first Claude CLI turn in this workspace",
);
});
});
it("warns when Claude auth is not readable headlessly", async () => {
await withTempHome(({ homeDir, workspaceDir }) => {
const noteFn = vi.fn();
noteClaudeCliHealth(
{
agents: {
defaults: {
model: { primary: "claude-cli/claude-sonnet-4-6" },
},
},
},
{
homeDir,
workspaceDir,
noteFn,
store: createStore(),
readClaudeCliCredentials: () => null,
resolveCommandPath: () => undefined,
},
);
const body = String(noteFn.mock.calls[0]?.[0]);
expect(body).toContain('Binary: command "claude" was not found on PATH.');
expect(body).toContain("Headless Claude auth: unavailable without interactive prompting.");
expect(body).toContain("claude auth login");
});
});
});
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|