mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 04:22:43 +00:00
refactor(core): dedupe command, hook, and cron fixtures
This commit is contained in:
@@ -128,6 +128,28 @@ function emitJsonPayload(params: {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function resolveConfigAndTargetAgentIdOrExit(params: {
|
||||
runtime: RuntimeEnv;
|
||||
agentInput: string | undefined;
|
||||
}): Promise<{
|
||||
cfg: NonNullable<Awaited<ReturnType<typeof requireValidConfig>>>;
|
||||
agentId: string;
|
||||
} | null> {
|
||||
const cfg = await requireValidConfig(params.runtime);
|
||||
if (!cfg) {
|
||||
return null;
|
||||
}
|
||||
const agentId = resolveTargetAgentIdOrExit({
|
||||
cfg,
|
||||
runtime: params.runtime,
|
||||
agentInput: params.agentInput,
|
||||
});
|
||||
if (!agentId) {
|
||||
return null;
|
||||
}
|
||||
return { cfg, agentId };
|
||||
}
|
||||
|
||||
export async function agentsBindingsCommand(
|
||||
opts: AgentsBindingsListOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
@@ -186,15 +208,14 @@ export async function agentsBindCommand(
|
||||
opts: AgentsBindOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const agentId = resolveTargetAgentIdOrExit({ cfg, runtime, agentInput: opts.agent });
|
||||
if (!agentId) {
|
||||
const resolved = await resolveConfigAndTargetAgentIdOrExit({
|
||||
runtime,
|
||||
agentInput: opts.agent,
|
||||
});
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
const { cfg, agentId } = resolved;
|
||||
|
||||
const parsed = resolveParsedBindingsOrExit({
|
||||
runtime,
|
||||
@@ -264,15 +285,14 @@ export async function agentsUnbindCommand(
|
||||
opts: AgentsUnbindOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const agentId = resolveTargetAgentIdOrExit({ cfg, runtime, agentInput: opts.agent });
|
||||
if (!agentId) {
|
||||
const resolved = await resolveConfigAndTargetAgentIdOrExit({
|
||||
runtime,
|
||||
agentInput: opts.agent,
|
||||
});
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
const { cfg, agentId } = resolved;
|
||||
if (opts.all && (opts.bind?.length ?? 0) > 0) {
|
||||
runtime.error("Use either --all or --bind, not both.");
|
||||
runtime.exit(1);
|
||||
|
||||
@@ -44,6 +44,26 @@ function createPromptSpies(params?: { confirmResult?: boolean; textResult?: stri
|
||||
return { confirm, note, text };
|
||||
}
|
||||
|
||||
async function ensureMinimaxApiKey(params: {
|
||||
confirm: WizardPrompter["confirm"];
|
||||
text: WizardPrompter["text"];
|
||||
setCredential: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["setCredential"];
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
secretInputMode?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["secretInputMode"];
|
||||
}) {
|
||||
return await ensureApiKeyFromEnvOrPrompt({
|
||||
config: params.config ?? {},
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm: params.confirm, text: params.text }),
|
||||
secretInputMode: params.secretInputMode,
|
||||
setCredential: params.setCredential,
|
||||
});
|
||||
}
|
||||
|
||||
async function runEnsureMinimaxApiKeyFlow(params: { confirmResult: boolean; textResult: string }) {
|
||||
process.env.MINIMAX_API_KEY = "env-key";
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
@@ -53,15 +73,9 @@ async function runEnsureMinimaxApiKeyFlow(params: { confirmResult: boolean; text
|
||||
textResult: params.textResult,
|
||||
});
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await ensureApiKeyFromEnvOrPrompt({
|
||||
config: {},
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm, text }),
|
||||
const result = await ensureMinimaxApiKey({
|
||||
confirm,
|
||||
text,
|
||||
setCredential,
|
||||
});
|
||||
|
||||
@@ -164,14 +178,9 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
});
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await ensureApiKeyFromEnvOrPrompt({
|
||||
config: {},
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm, text }),
|
||||
const result = await ensureMinimaxApiKey({
|
||||
confirm,
|
||||
text,
|
||||
secretInputMode: "ref",
|
||||
setCredential,
|
||||
});
|
||||
@@ -195,14 +204,9 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
await expect(
|
||||
ensureApiKeyFromEnvOrPrompt({
|
||||
config: {},
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm, text }),
|
||||
ensureMinimaxApiKey({
|
||||
confirm,
|
||||
text,
|
||||
secretInputMode: "ref",
|
||||
setCredential,
|
||||
}),
|
||||
|
||||
@@ -29,6 +29,19 @@ function createHuggingfacePrompter(params: {
|
||||
return createWizardPrompter(overrides, { defaultSelect: "" });
|
||||
}
|
||||
|
||||
type ApplyHuggingfaceParams = Parameters<typeof applyAuthChoiceHuggingface>[0];
|
||||
|
||||
async function runHuggingfaceApply(
|
||||
params: Omit<ApplyHuggingfaceParams, "authChoice" | "setDefaultModel"> &
|
||||
Partial<Pick<ApplyHuggingfaceParams, "setDefaultModel">>,
|
||||
) {
|
||||
return await applyAuthChoiceHuggingface({
|
||||
authChoice: "huggingface-api-key",
|
||||
setDefaultModel: params.setDefaultModel ?? true,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
describe("applyAuthChoiceHuggingface", () => {
|
||||
const lifecycle = createAuthTestLifecycle([
|
||||
"OPENCLAW_STATE_DIR",
|
||||
@@ -75,12 +88,10 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
const prompter = createHuggingfacePrompter({ text, select });
|
||||
const runtime = createExitThrowingRuntime();
|
||||
|
||||
const result = await applyAuthChoiceHuggingface({
|
||||
authChoice: "huggingface-api-key",
|
||||
const result = await runHuggingfaceApply({
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
@@ -132,12 +143,10 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
const prompter = createHuggingfacePrompter({ text, select, confirm });
|
||||
const runtime = createExitThrowingRuntime();
|
||||
|
||||
const result = await applyAuthChoiceHuggingface({
|
||||
authChoice: "huggingface-api-key",
|
||||
const result = await runHuggingfaceApply({
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider,
|
||||
token,
|
||||
@@ -167,12 +176,10 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
const prompter = createHuggingfacePrompter({ text, select, note });
|
||||
const runtime = createExitThrowingRuntime();
|
||||
|
||||
const result = await applyAuthChoiceHuggingface({
|
||||
authChoice: "huggingface-api-key",
|
||||
const result = await runHuggingfaceApply({
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { withTempHomeConfig } from "../config/test-helpers.js";
|
||||
|
||||
const { noteSpy } = vi.hoisted(() => ({
|
||||
noteSpy: vi.fn(),
|
||||
}));
|
||||
import { note } from "../terminal/note.js";
|
||||
|
||||
vi.mock("../terminal/note.js", () => ({
|
||||
note: noteSpy,
|
||||
note: vi.fn(),
|
||||
}));
|
||||
|
||||
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
||||
|
||||
const noteSpy = vi.mocked(note);
|
||||
|
||||
describe("doctor include warning", () => {
|
||||
it("surfaces include confinement hint for escaped include paths", async () => {
|
||||
await withTempHomeConfig({ $include: "/etc/passwd" }, async () => {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { runDoctorConfigWithInput } from "./doctor-config-flow.test-utils.js";
|
||||
|
||||
const { noteSpy } = vi.hoisted(() => ({
|
||||
noteSpy: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../terminal/note.js", () => ({
|
||||
note: noteSpy,
|
||||
note: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./doctor-legacy-config.js", async (importOriginal) => {
|
||||
@@ -23,6 +20,8 @@ vi.mock("./doctor-legacy-config.js", async (importOriginal) => {
|
||||
|
||||
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
||||
|
||||
const noteSpy = vi.mocked(note);
|
||||
|
||||
describe("doctor missing default account binding warning", () => {
|
||||
it("emits a doctor warning when named accounts have no valid account-scoped bindings", async () => {
|
||||
await withEnvAsync(
|
||||
|
||||
@@ -2,20 +2,19 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { runDoctorConfigWithInput } from "./doctor-config-flow.test-utils.js";
|
||||
|
||||
const { noteSpy } = vi.hoisted(() => ({
|
||||
noteSpy: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../terminal/note.js", () => ({
|
||||
note: noteSpy,
|
||||
note: vi.fn(),
|
||||
}));
|
||||
|
||||
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
||||
|
||||
describe("doctor config flow safe bins", () => {
|
||||
const noteSpy = vi.mocked(note);
|
||||
|
||||
beforeEach(() => {
|
||||
noteSpy.mockClear();
|
||||
});
|
||||
|
||||
@@ -65,6 +65,20 @@ async function runStateIntegrity(cfg: OpenClawConfig) {
|
||||
return confirmSkipInNonInteractive;
|
||||
}
|
||||
|
||||
function writeSessionStore(
|
||||
cfg: OpenClawConfig,
|
||||
sessions: Record<string, { sessionId: string; updatedAt: number }>,
|
||||
) {
|
||||
setupSessionState(cfg, process.env, process.env.HOME ?? "");
|
||||
const storePath = resolveStorePath(cfg.session?.store, { agentId: "main" });
|
||||
fs.writeFileSync(storePath, JSON.stringify(sessions, null, 2));
|
||||
}
|
||||
|
||||
async function runStateIntegrityText(cfg: OpenClawConfig): Promise<string> {
|
||||
await noteStateIntegrity(cfg, { confirmSkipInNonInteractive: vi.fn(async () => false) });
|
||||
return stateIntegrityText();
|
||||
}
|
||||
|
||||
describe("doctor state integrity oauth dir checks", () => {
|
||||
let envSnapshot: EnvSnapshot;
|
||||
let tempHome = "";
|
||||
@@ -146,25 +160,13 @@ describe("doctor state integrity oauth dir checks", () => {
|
||||
|
||||
it("prints openclaw-only verification hints when recent sessions are missing transcripts", async () => {
|
||||
const cfg: OpenClawConfig = {};
|
||||
setupSessionState(cfg, process.env, process.env.HOME ?? "");
|
||||
const storePath = resolveStorePath(cfg.session?.store, { agentId: "main" });
|
||||
fs.writeFileSync(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
"agent:main:main": {
|
||||
sessionId: "missing-transcript",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
await noteStateIntegrity(cfg, { confirmSkipInNonInteractive: vi.fn(async () => false) });
|
||||
|
||||
const text = stateIntegrityText();
|
||||
writeSessionStore(cfg, {
|
||||
"agent:main:main": {
|
||||
sessionId: "missing-transcript",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
const text = await runStateIntegrityText(cfg);
|
||||
expect(text).toContain("recent sessions are missing transcripts");
|
||||
expect(text).toMatch(/openclaw sessions --store ".*sessions\.json"/);
|
||||
expect(text).toMatch(/openclaw sessions cleanup --store ".*sessions\.json" --dry-run/);
|
||||
@@ -177,25 +179,13 @@ describe("doctor state integrity oauth dir checks", () => {
|
||||
|
||||
it("ignores slash-routing sessions for recent missing transcript warnings", async () => {
|
||||
const cfg: OpenClawConfig = {};
|
||||
setupSessionState(cfg, process.env, process.env.HOME ?? "");
|
||||
const storePath = resolveStorePath(cfg.session?.store, { agentId: "main" });
|
||||
fs.writeFileSync(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
"agent:main:telegram:slash:6790081233": {
|
||||
sessionId: "missing-slash-transcript",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
await noteStateIntegrity(cfg, { confirmSkipInNonInteractive: vi.fn(async () => false) });
|
||||
|
||||
const text = stateIntegrityText();
|
||||
writeSessionStore(cfg, {
|
||||
"agent:main:telegram:slash:6790081233": {
|
||||
sessionId: "missing-slash-transcript",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
const text = await runStateIntegrityText(cfg);
|
||||
expect(text).not.toContain("recent sessions are missing transcripts");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user