Initial commit: Discord-Claude Gateway with event-driven agent runtime
This commit is contained in:
@@ -2,6 +2,17 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { CronScheduler, type CronJob } from "../../src/cron-scheduler.js";
|
||||
import type { Event } from "../../src/event-queue.js";
|
||||
|
||||
vi.mock("../../src/logger.js", () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { logger } from "../../src/logger.js";
|
||||
|
||||
// Mock node-cron
|
||||
vi.mock("node-cron", () => {
|
||||
const tasks: Array<{ expression: string; callback: () => void; stopped: boolean }> = [];
|
||||
@@ -57,6 +68,7 @@ describe("CronScheduler", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockCron._clearTasks();
|
||||
vi.mocked(logger.warn).mockClear();
|
||||
scheduler = new CronScheduler();
|
||||
});
|
||||
|
||||
@@ -185,7 +197,6 @@ Instruction: This should not be parsed either`;
|
||||
});
|
||||
|
||||
it("skips jobs with invalid cron expressions and logs a warning", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const enqueue: EnqueueFn = (event) =>
|
||||
({ ...event, id: 1, timestamp: new Date() }) as Event;
|
||||
|
||||
@@ -195,14 +206,10 @@ Instruction: This should not be parsed either`;
|
||||
|
||||
scheduler.start(jobs, enqueue);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("bad-job")
|
||||
);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: "bad-job" }),
|
||||
expect.stringContaining("invalid cron expression")
|
||||
);
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("schedules valid jobs and skips invalid ones in the same batch", () => {
|
||||
@@ -211,7 +218,6 @@ Instruction: This should not be parsed either`;
|
||||
enqueued.push(event);
|
||||
return { ...event, id: enqueued.length, timestamp: new Date() } as Event;
|
||||
};
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
const jobs: CronJob[] = [
|
||||
{ name: "bad-job", expression: "invalid", instruction: "Bad" },
|
||||
@@ -220,14 +226,12 @@ Instruction: This should not be parsed either`;
|
||||
|
||||
scheduler.start(jobs, enqueue);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Only the valid job should have been scheduled — fire all
|
||||
mockCron._fireAll();
|
||||
expect(enqueued).toHaveLength(1);
|
||||
expect(enqueued[0].payload).toEqual({ instruction: "Good", jobName: "good-job" });
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,17 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { HeartbeatScheduler, type HeartbeatCheck } from "../../src/heartbeat-scheduler.js";
|
||||
import type { Event } from "../../src/event-queue.js";
|
||||
|
||||
vi.mock("../../src/logger.js", () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { logger } from "../../src/logger.js";
|
||||
|
||||
type EnqueueFn = (event: Omit<Event, "id" | "timestamp">) => Event | null;
|
||||
|
||||
describe("HeartbeatScheduler", () => {
|
||||
@@ -9,6 +20,7 @@ describe("HeartbeatScheduler", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.mocked(logger.warn).mockClear();
|
||||
scheduler = new HeartbeatScheduler();
|
||||
});
|
||||
|
||||
@@ -95,7 +107,6 @@ Instruction: Do something`;
|
||||
});
|
||||
|
||||
it("rejects checks with interval < 60 seconds with a warning", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const enqueue: EnqueueFn = () => null;
|
||||
|
||||
const checks: HeartbeatCheck[] = [
|
||||
@@ -104,18 +115,14 @@ Instruction: Do something`;
|
||||
|
||||
scheduler.start(checks, enqueue);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("too-fast")
|
||||
);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("below the minimum")
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: "too-fast" }),
|
||||
expect.stringContaining("below minimum")
|
||||
);
|
||||
|
||||
// Advance time — no events should be enqueued
|
||||
vi.advanceTimersByTime(60_000);
|
||||
// No way to check enqueue wasn't called since it returns null, but the warn confirms rejection
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("starts valid checks and skips invalid ones in the same batch", () => {
|
||||
@@ -124,7 +131,6 @@ Instruction: Do something`;
|
||||
enqueued.push(event);
|
||||
return { ...event, id: enqueued.length, timestamp: new Date() } as Event;
|
||||
};
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
const checks: HeartbeatCheck[] = [
|
||||
{ name: "too-fast", instruction: "Bad check", intervalSeconds: 10 },
|
||||
@@ -133,13 +139,11 @@ Instruction: Do something`;
|
||||
|
||||
scheduler.start(checks, enqueue);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.advanceTimersByTime(60_000);
|
||||
expect(enqueued).toHaveLength(1);
|
||||
expect(enqueued[0].payload).toEqual({ instruction: "Good check", checkName: "valid-check" });
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { registerShutdownHandler } from "../../src/shutdown-handler.js";
|
||||
|
||||
vi.mock("../../src/logger.js", () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { logger } from "../../src/logger.js";
|
||||
|
||||
describe("registerShutdownHandler", () => {
|
||||
let mockGateway: { shutdown: ReturnType<typeof vi.fn> };
|
||||
let sigintListeners: Array<() => void>;
|
||||
@@ -16,7 +27,7 @@ describe("registerShutdownHandler", () => {
|
||||
if (event === "SIGTERM") sigtermListeners.push(listener as () => void);
|
||||
return process;
|
||||
});
|
||||
vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
vi.mocked(logger.info).mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -52,6 +63,9 @@ describe("registerShutdownHandler", () => {
|
||||
it("logs the signal name", () => {
|
||||
registerShutdownHandler(mockGateway as never);
|
||||
sigtermListeners[0]();
|
||||
expect(console.log).toHaveBeenCalledWith("Received SIGTERM, shutting down...");
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ signal: "SIGTERM" }),
|
||||
expect.stringContaining("shutting down")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user