Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 101 additions & 5 deletions packages/cli/src/commands/preview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import * as clack from "@clack/prompts";
import { afterEach, describe, expect, it, vi } from "vitest";
import { handlePreviewKillAll, handlePreviewList, studioLandingSearch } from "./preview.js";
import { runCommand } from "citty";
import {
default as previewCommand,
handlePreviewKillAll,
handlePreviewList,
studioLandingSearch,
} from "./preview.js";

const tempDirs: string[] = [];

Expand Down Expand Up @@ -67,9 +73,9 @@ describe("preview --kill-all", () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});
const warn = vi.spyOn(clack.log, "warn").mockImplementation(() => {});

await handlePreviewKillAll(3002, {
await handlePreviewKillAll(3002, false, {
listManaged: async () => [session(41402, "/tmp/unprovable"), session(41403, "/tmp/healthy")],
stopManaged: async (projectDir) => {
stopManaged: async (projectDir: string) => {
if (projectDir === "/tmp/unprovable") throw new Error("ownership failed");
return true;
},
Expand All @@ -85,7 +91,7 @@ describe("preview --kill-all", () => {
it("reports nothing to kill when no preview is running", async () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});

await handlePreviewKillAll(3002, {
await handlePreviewKillAll(3002, false, {
listManaged: async () => [],
killScanned: async () => ({ killed: 0, unverified: [] }),
});
Expand All @@ -99,7 +105,7 @@ describe("preview --list", () => {
it("prefers the managed record over the same server's own self-report", async () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});

await handlePreviewList(3002, {
await handlePreviewList(3002, false, {
listManaged: async () => [
{ pid: 99, port: 3002, projectDir: resolve("/tmp/demo"), logPath: "/tmp/demo.log" },
],
Expand All @@ -120,3 +126,93 @@ describe("preview --list", () => {
log.mockRestore();
});
});

describe("preview lifecycle JSON failures", () => {
it.each([
[
"list",
() =>
handlePreviewList(3002, true, {
scan: async () => {
throw new Error("list probe failed");
},
listManaged: async () => [],
}),
"preview-list-failed",
],
[
"kill-all",
() =>
handlePreviewKillAll(3002, true, {
listManaged: async () => [],
killScanned: async () => {
throw new Error("scan failed");
},
}),
"preview-kill-all-failed",
],
] as const)("wraps %s failures in one JSON document", async (operation, run, code) => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});
const error = vi.spyOn(console, "error").mockImplementation(() => {});

await run();

expect(log).toHaveBeenCalledOnce();
const [line] = log.mock.calls[0] as [string];
expect(JSON.parse(line)).toMatchObject({
schemaVersion: 1,
operation,
ok: false,
error: { code },
});
expect(error).not.toHaveBeenCalled();
});

it("keeps stopping after a record whose ownership cannot be proven", async () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});
const session = (port: number, projectDir: string) => ({
pid: 4321,
port,
projectDir,
logPath: `${projectDir}.log`,
});

await handlePreviewKillAll(3002, true, {
listManaged: async () => [session(41402, "/tmp/unprovable"), session(41403, "/tmp/healthy")],
stopManaged: async (projectDir) => {
if (projectDir === "/tmp/unprovable") throw new Error("ownership failed");
return true;
},
killScanned: async () => ({ killed: 0, unverified: [] }),
});

const [line] = log.mock.calls[0] as [string];
// The second record must still be stopped AND the first must be reported:
// propagating the first failure left every later preview running, unlisted.
expect(JSON.parse(line)).toMatchObject({
operation: "kill-all",
ok: true,
result: { state: "killed-all", stopped: 1, failed: ["/tmp/unprovable: ownership failed"] },
});
});

it("wraps stop failures in one JSON document", async () => {
const missing = join(tmpdir(), `hf-preview-missing-${process.pid}-${Date.now()}`);
const log = vi.spyOn(console, "log").mockImplementation(() => {});
const error = vi.spyOn(console, "error").mockImplementation(() => {});

await runCommand(previewCommand, {
rawArgs: [missing, "--stop", "--json"],
});

expect(log).toHaveBeenCalledOnce();
const [line] = log.mock.calls[0] as [string];
expect(JSON.parse(line)).toMatchObject({
schemaVersion: 1,
operation: "stop",
ok: false,
error: { code: "preview-stop-failed" },
});
expect(error).not.toHaveBeenCalled();
});
});
Loading
Loading