/**
* mysql-mcp - Progress Reporter Unit Tests
*
* Tests for ProgressReporter and ProgressReporterFactory functionality
* including progress notifications, completion states, and error handling.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { ProgressReporter } from "../ProgressReporter.js";
describe("ProgressReporter", () => {
let mockServer: {
server: {
notification: ReturnType<typeof vi.fn>;
};
};
beforeEach(() => {
vi.clearAllMocks();
mockServer = {
server: {
notification: vi.fn(),
},
};
});
describe("Construction", () => {
it("should create reporter with token", () => {
const reporter = new ProgressReporter(mockServer as never, "token-123");
expect(reporter.getToken()).toBe("token-123");
});
it("should accept numeric token", () => {
const reporter = new ProgressReporter(mockServer as never, 42);
expect(reporter.getToken()).toBe(42);
});
it("should start in active state", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
expect(reporter.isActive()).toBe(true);
});
});
describe("report()", () => {
it("should send progress notification", () => {
const reporter = new ProgressReporter(mockServer as never, "token-123");
reporter.report(50, 100, "Processing...");
expect(mockServer.server.notification).toHaveBeenCalledWith({
method: "notifications/progress",
params: {
progressToken: "token-123",
progress: 50,
total: 100,
message: "Processing...",
},
});
});
it("should work without total", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.report(25);
expect(mockServer.server.notification).toHaveBeenCalledWith({
method: "notifications/progress",
params: expect.objectContaining({
progress: 25,
total: undefined,
}),
});
});
it("should work without message", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.report(75, 100);
expect(mockServer.server.notification).toHaveBeenCalledWith({
method: "notifications/progress",
params: expect.objectContaining({
progress: 75,
total: 100,
message: undefined,
}),
});
});
it("should not report after completion", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.complete();
mockServer.server.notification.mockClear();
reporter.report(50);
expect(mockServer.server.notification).not.toHaveBeenCalled();
});
});
describe("complete()", () => {
it("should send completion notification with default message", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.complete();
expect(mockServer.server.notification).toHaveBeenCalledWith({
method: "notifications/progress",
params: {
progressToken: "token",
progress: 1,
total: 1,
message: "Complete",
},
});
});
it("should send completion notification with custom message", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.complete("All done!");
expect(mockServer.server.notification).toHaveBeenCalledWith({
method: "notifications/progress",
params: expect.objectContaining({
message: "All done!",
}),
});
});
it("should set reporter to inactive", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
expect(reporter.isActive()).toBe(true);
reporter.complete();
expect(reporter.isActive()).toBe(false);
});
it("should only complete once", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.complete("First");
reporter.complete("Second");
expect(mockServer.server.notification).toHaveBeenCalledTimes(1);
});
});
describe("error()", () => {
it("should send error notification", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.error("Something went wrong");
expect(mockServer.server.notification).toHaveBeenCalledWith({
method: "notifications/progress",
params: {
progressToken: "token",
progress: 0,
message: "Error: Something went wrong",
},
});
});
it("should set reporter to inactive", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.error("Failed");
expect(reporter.isActive()).toBe(false);
});
it("should not error after completion", () => {
const reporter = new ProgressReporter(mockServer as never, "token");
reporter.complete();
mockServer.server.notification.mockClear();
reporter.error("Should not send");
expect(mockServer.server.notification).not.toHaveBeenCalled();
});
});
describe("Error Handling", () => {
it("should handle notification errors gracefully", () => {
mockServer.server.notification.mockImplementation(() => {
throw new Error("Transport error");
});
const reporter = new ProgressReporter(mockServer as never, "token");
// Should not throw
expect(() => reporter.report(50)).not.toThrow();
expect(() => reporter.complete()).not.toThrow();
});
});
});
describe("ProgressReporterFactory", () => {
let mockServer: {
server: {
notification: ReturnType<typeof vi.fn>;
};
};
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
mockServer = {
server: {
notification: vi.fn(),
},
};
});
afterEach(() => {
vi.resetModules();
});
describe("Availability", () => {
it("should report unavailable before server set", async () => {
const { progressFactory: factory } =
await import("../ProgressReporter.js");
expect(factory.isAvailable()).toBe(false);
});
it("should report available after server set", async () => {
const { progressFactory: factory } =
await import("../ProgressReporter.js");
factory.setServer(mockServer as never);
expect(factory.isAvailable()).toBe(true);
});
});
describe("create()", () => {
it("should return null when no server set", async () => {
const { progressFactory: factory } =
await import("../ProgressReporter.js");
const reporter = factory.create("token");
expect(reporter).toBeNull();
});
it("should return null when no token provided", async () => {
const { progressFactory: factory } =
await import("../ProgressReporter.js");
factory.setServer(mockServer as never);
const reporter = factory.create(undefined);
expect(reporter).toBeNull();
});
it("should create reporter when server and token available", async () => {
const { progressFactory: factory } =
await import("../ProgressReporter.js");
factory.setServer(mockServer as never);
const reporter = factory.create("my-token");
expect(reporter).not.toBeNull();
expect(reporter?.getToken()).toBe("my-token");
});
it("should create functional reporter", async () => {
const { progressFactory: factory } =
await import("../ProgressReporter.js");
factory.setServer(mockServer as never);
const reporter = factory.create("test-token");
reporter?.report(10, 100, "Testing");
expect(mockServer.server.notification).toHaveBeenCalledWith(
expect.objectContaining({
method: "notifications/progress",
}),
);
});
});
});