mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-26 16:06:16 +00:00
feat: add update channel support
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
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 type { UpdateRunResult } from "../infra/update-runner.js";
|
||||
|
||||
@@ -7,6 +10,25 @@ vi.mock("../infra/update-runner.js", () => ({
|
||||
runGatewayUpdate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/clawdbot-root.js", () => ({
|
||||
resolveClawdbotPackageRoot: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
writeConfigFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/update-check.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../infra/update-check.js")>(
|
||||
"../infra/update-check.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
fetchNpmTagVersion: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock doctor (heavy module; should not run in unit tests)
|
||||
vi.mock("../commands/doctor.js", () => ({
|
||||
doctorCommand: vi.fn(),
|
||||
@@ -26,6 +48,41 @@ vi.mock("../runtime.js", () => ({
|
||||
}));
|
||||
|
||||
describe("update-cli", () => {
|
||||
const baseSnapshot = {
|
||||
valid: true,
|
||||
config: {},
|
||||
issues: [],
|
||||
} as const;
|
||||
|
||||
const setTty = (value: boolean | undefined) => {
|
||||
Object.defineProperty(process.stdin, "isTTY", {
|
||||
value,
|
||||
configurable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const setStdoutTty = (value: boolean | undefined) => {
|
||||
Object.defineProperty(process.stdout, "isTTY", {
|
||||
value,
|
||||
configurable: true,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
const { resolveClawdbotPackageRoot } = await import("../infra/clawdbot-root.js");
|
||||
const { readConfigFileSnapshot } = await import("../config/config.js");
|
||||
const { fetchNpmTagVersion } = await import("../infra/update-check.js");
|
||||
vi.mocked(resolveClawdbotPackageRoot).mockResolvedValue(process.cwd());
|
||||
vi.mocked(readConfigFileSnapshot).mockResolvedValue(baseSnapshot);
|
||||
vi.mocked(fetchNpmTagVersion).mockResolvedValue({
|
||||
tag: "latest",
|
||||
version: "9999.0.0",
|
||||
});
|
||||
setTty(false);
|
||||
setStdoutTty(false);
|
||||
});
|
||||
|
||||
it("exports updateCommand and registerUpdateCli", async () => {
|
||||
const { updateCommand, registerUpdateCli } = await import("./update-cli.js");
|
||||
expect(typeof updateCommand).toBe("function");
|
||||
@@ -63,6 +120,62 @@ describe("update-cli", () => {
|
||||
expect(defaultRuntime.log).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("defaults to stable channel when unset", async () => {
|
||||
const { runGatewayUpdate } = await import("../infra/update-runner.js");
|
||||
const { updateCommand } = await import("./update-cli.js");
|
||||
|
||||
vi.mocked(runGatewayUpdate).mockResolvedValue({
|
||||
status: "ok",
|
||||
mode: "git",
|
||||
steps: [],
|
||||
durationMs: 100,
|
||||
});
|
||||
|
||||
await updateCommand({});
|
||||
|
||||
const call = vi.mocked(runGatewayUpdate).mock.calls[0]?.[0];
|
||||
expect(call?.tag).toBe("latest");
|
||||
});
|
||||
|
||||
it("uses stored beta channel when configured", async () => {
|
||||
const { readConfigFileSnapshot } = await import("../config/config.js");
|
||||
const { runGatewayUpdate } = await import("../infra/update-runner.js");
|
||||
const { updateCommand } = await import("./update-cli.js");
|
||||
|
||||
vi.mocked(readConfigFileSnapshot).mockResolvedValue({
|
||||
...baseSnapshot,
|
||||
config: { update: { channel: "beta" } },
|
||||
});
|
||||
vi.mocked(runGatewayUpdate).mockResolvedValue({
|
||||
status: "ok",
|
||||
mode: "git",
|
||||
steps: [],
|
||||
durationMs: 100,
|
||||
});
|
||||
|
||||
await updateCommand({});
|
||||
|
||||
const call = vi.mocked(runGatewayUpdate).mock.calls[0]?.[0];
|
||||
expect(call?.tag).toBe("beta");
|
||||
});
|
||||
|
||||
it("honors --tag override", async () => {
|
||||
const { runGatewayUpdate } = await import("../infra/update-runner.js");
|
||||
const { updateCommand } = await import("./update-cli.js");
|
||||
|
||||
vi.mocked(runGatewayUpdate).mockResolvedValue({
|
||||
status: "ok",
|
||||
mode: "git",
|
||||
steps: [],
|
||||
durationMs: 100,
|
||||
});
|
||||
|
||||
await updateCommand({ tag: "next" });
|
||||
|
||||
const call = vi.mocked(runGatewayUpdate).mock.calls[0]?.[0];
|
||||
expect(call?.tag).toBe("next");
|
||||
});
|
||||
|
||||
it("updateCommand outputs JSON when --json is set", async () => {
|
||||
const { runGatewayUpdate } = await import("../infra/update-runner.js");
|
||||
const { defaultRuntime } = await import("../runtime.js");
|
||||
@@ -168,4 +281,67 @@ describe("update-cli", () => {
|
||||
expect(defaultRuntime.error).toHaveBeenCalledWith(expect.stringContaining("timeout"));
|
||||
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("persists update channel when --channel is set", async () => {
|
||||
const { writeConfigFile } = await import("../config/config.js");
|
||||
const { runGatewayUpdate } = await import("../infra/update-runner.js");
|
||||
const { updateCommand } = await import("./update-cli.js");
|
||||
|
||||
const mockResult: UpdateRunResult = {
|
||||
status: "ok",
|
||||
mode: "git",
|
||||
steps: [],
|
||||
durationMs: 100,
|
||||
};
|
||||
|
||||
vi.mocked(runGatewayUpdate).mockResolvedValue(mockResult);
|
||||
|
||||
await updateCommand({ channel: "beta" });
|
||||
|
||||
expect(writeConfigFile).toHaveBeenCalled();
|
||||
const call = vi.mocked(writeConfigFile).mock.calls[0]?.[0] as {
|
||||
update?: { channel?: string };
|
||||
};
|
||||
expect(call?.update?.channel).toBe("beta");
|
||||
});
|
||||
|
||||
it("requires confirmation on downgrade when non-interactive", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-update-"));
|
||||
try {
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, "package.json"),
|
||||
JSON.stringify({ name: "clawdbot", version: "2.0.0" }),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const { resolveClawdbotPackageRoot } = await import("../infra/clawdbot-root.js");
|
||||
const { fetchNpmTagVersion } = await import("../infra/update-check.js");
|
||||
const { runGatewayUpdate } = await import("../infra/update-runner.js");
|
||||
const { defaultRuntime } = await import("../runtime.js");
|
||||
const { updateCommand } = await import("./update-cli.js");
|
||||
|
||||
vi.mocked(resolveClawdbotPackageRoot).mockResolvedValue(tempDir);
|
||||
vi.mocked(fetchNpmTagVersion).mockResolvedValue({
|
||||
tag: "latest",
|
||||
version: "0.0.1",
|
||||
});
|
||||
vi.mocked(runGatewayUpdate).mockResolvedValue({
|
||||
status: "ok",
|
||||
mode: "npm",
|
||||
steps: [],
|
||||
durationMs: 100,
|
||||
});
|
||||
vi.mocked(defaultRuntime.error).mockClear();
|
||||
vi.mocked(defaultRuntime.exit).mockClear();
|
||||
|
||||
await updateCommand({});
|
||||
|
||||
expect(defaultRuntime.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Downgrade confirmation required."),
|
||||
);
|
||||
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user