116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
parseLogTailLines,
|
|
runLogs,
|
|
type LogApiEvent,
|
|
type LogEventWriter,
|
|
type LogsApiStream,
|
|
} from "../../command/logs";
|
|
|
|
describe("logs API command runner", () => {
|
|
test("uses the authenticated workspace NDJSON route and preserves prefixes", async () => {
|
|
const calls: Array<{
|
|
path: string;
|
|
signal?: AbortSignal;
|
|
timeout?: number;
|
|
}> = [];
|
|
const events: LogApiEvent[] = [
|
|
{ type: "heartbeat", timestamp: "now" },
|
|
{
|
|
type: "log",
|
|
targetName: "web",
|
|
pod: "web-1",
|
|
container: "web",
|
|
message: "ready",
|
|
},
|
|
{
|
|
type: "error",
|
|
pod: "web-1",
|
|
container: "web",
|
|
message: "disconnected",
|
|
retryable: true,
|
|
},
|
|
];
|
|
const stream: LogsApiStream = async function* (path, init, options) {
|
|
calls.push({
|
|
path,
|
|
signal: init?.signal ?? undefined,
|
|
timeout: options?.timeoutMs,
|
|
});
|
|
yield* events as never[];
|
|
};
|
|
const output: Parameters<LogEventWriter>[] = [];
|
|
const controller = new AbortController();
|
|
await runLogs(
|
|
"shop/demo",
|
|
"web api",
|
|
true,
|
|
controller.signal,
|
|
stream,
|
|
(...entry) => output.push(entry),
|
|
);
|
|
|
|
expect(calls).toEqual([
|
|
{
|
|
path: "/workspaces/shop%2Fdemo/logs?service=web+api&follow=true",
|
|
signal: controller.signal,
|
|
timeout: 0,
|
|
},
|
|
]);
|
|
expect(output).toEqual([
|
|
["web-1", "ready", false],
|
|
["web api", "disconnected", true],
|
|
]);
|
|
});
|
|
|
|
test("omits follow for finite collection and passes cancellation", async () => {
|
|
const controller = new AbortController();
|
|
let receivedSignal: AbortSignal | null | undefined;
|
|
const stream: LogsApiStream = async function* (path, init, options) {
|
|
expect(path).toBe("/workspaces/shop/logs");
|
|
expect(options?.timeoutMs).toBeUndefined();
|
|
receivedSignal = init?.signal;
|
|
yield* [];
|
|
};
|
|
await runLogs(
|
|
"shop",
|
|
undefined,
|
|
false,
|
|
controller.signal,
|
|
stream,
|
|
() => {},
|
|
);
|
|
expect(receivedSignal).toBe(controller.signal);
|
|
});
|
|
|
|
test.each([false, true])("sends -n with follow=%s", async (follow) => {
|
|
const paths: string[] = [];
|
|
const stream: LogsApiStream = async function* (path, _init, options) {
|
|
paths.push(path);
|
|
expect(options?.timeoutMs).toBe(follow ? 0 : undefined);
|
|
yield* [] as never[];
|
|
};
|
|
await runLogs("shop", "web", follow, undefined, stream, () => {}, 0);
|
|
expect(paths).toEqual([
|
|
`/workspaces/shop/logs?service=web${follow ? "&follow=true" : ""}&tailLines=0`,
|
|
]);
|
|
});
|
|
|
|
test("accepts only non-negative safe integer line counts", async () => {
|
|
expect(parseLogTailLines("25")).toBe(25);
|
|
expect(parseLogTailLines("0")).toBe(0);
|
|
for (const invalid of ["-1", "1.5", "2abc", "", "1e3", "9007199254740992"])
|
|
expect(() => parseLogTailLines(invalid)).toThrow("non-negative integer");
|
|
|
|
let called = false;
|
|
const stream: LogsApiStream = async function* () {
|
|
called = true;
|
|
yield* [] as never[];
|
|
};
|
|
await expect(
|
|
runLogs("shop", undefined, false, undefined, stream, () => {}, -1),
|
|
).rejects.toThrow("non-negative integer");
|
|
expect(called).toBe(false);
|
|
});
|
|
});
|