@critters/next
Version:
Secure bug reporting library for Next.js applications
213 lines (212 loc) • 10.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Unit tests for Critters reporter
*/
const reporter_1 = require("../src/reporter");
const config_1 = require("../src/config");
// Mock config module
jest.mock("../src/config", () => {
const originalModule = jest.requireActual("../src/config");
return Object.assign(Object.assign({}, originalModule), { BugTrackerConfig: {
init: jest.fn(),
getInstance: jest.fn().mockImplementation(() => ({
isEnabled: jest.fn().mockReturnValue(true),
isDebugMode: jest.fn().mockReturnValue(false),
getApiKey: jest.fn().mockReturnValue("mock-api-key"),
getSecretKey: jest.fn().mockReturnValue("mock-secret-key"),
getEndpoint: jest
.fn()
.mockReturnValue("https://mock-endpoint.example.com"),
getProjectId: jest.fn().mockReturnValue("mock-project-id"),
getEnvironment: jest.fn().mockReturnValue("test"),
getOnBeforeReport: jest.fn().mockReturnValue(null),
getOnAfterReport: jest.fn().mockReturnValue(null),
})),
} });
});
// Mock utils module
jest.mock("../src/utils", () => ({
isBrowser: jest.fn().mockReturnValue(true),
isServer: jest.fn().mockReturnValue(false),
getBrowserInfo: jest.fn().mockReturnValue({
name: "chrome",
version: "100.0.0",
os: "windows",
device: "desktop",
viewport: { width: 1920, height: 1080 },
}),
formatError: jest.fn().mockImplementation((error) => ({
name: error.name || "Error",
message: error.message || "Unknown error",
stack: error.stack || "mock-stack-trace",
})),
generateSignature: jest.fn().mockReturnValue("mock-signature"),
safeStringify: jest.fn().mockImplementation(JSON.stringify),
sanitizeData: jest.fn().mockImplementation((data) => data),
getTimestamp: jest.fn().mockReturnValue("2023-01-01T12:00:00.000Z"),
debugLog: jest.fn(),
}));
// Mock fetch
global.fetch = jest.fn();
describe("Critters Reporter", () => {
let reporter;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Mock successful fetch response
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: () => __awaiter(void 0, void 0, void 0, function* () {
return ({
success: true,
message: "Bug report submitted successfully",
reportId: "mock-report-id",
});
}),
});
// Reset singleton instance between tests
// @ts-ignore - Accessing private property for testing
reporter_1.Critters.instance = undefined;
// Get reporter instance
reporter = reporter_1.Critters.getInstance();
});
describe("getInstance", () => {
it("should create a singleton instance", () => {
const instance1 = reporter_1.Critters.getInstance();
const instance2 = reporter_1.Critters.getInstance();
expect(instance1).toBe(instance2);
});
});
describe("catch", () => {
it("should format and send error report", () => __awaiter(void 0, void 0, void 0, function* () {
const error = new Error("Test error");
const metadata = { context: "test-context" };
const result = yield reporter.catch(error, metadata);
// Verify report was formatted correctly
expect(global.fetch).toHaveBeenCalledTimes(1);
const [url, options] = global.fetch.mock.calls[0];
expect(url).toBe("https://mock-endpoint.example.com");
expect(options.method).toBe("POST");
expect(options.headers).toEqual({
"Content-Type": "application/json",
"X-API-Key": "mock-api-key",
"X-Signature": "mock-signature",
});
// Parse the sent payload
const payload = JSON.parse(options.body);
// Verify key fields were set
expect(payload.title).toBe("Error: Test error");
expect(payload.error_message).toBe("Test error");
expect(payload.source).toBe("CLIENT");
expect(payload.stack_trace).toBe("mock-stack-trace");
expect(payload.metadata).toEqual({ context: "test-context" });
expect(payload.browser_info).toBeDefined();
expect(payload.timestamp).toBe("2023-01-01T12:00:00.000Z");
expect(payload.projectId).toBe("mock-project-id");
// Verify result
expect(result).toEqual({
success: true,
message: "Bug report submitted successfully",
reportId: "mock-report-id",
timestamp: "2023-01-01T12:00:00.000Z",
});
}));
it("should handle API errors", () => __awaiter(void 0, void 0, void 0, function* () {
// Mock failed API response
global.fetch.mockResolvedValue({
ok: false,
status: 500,
statusText: "Internal Server Error",
});
const error = new Error("Test error");
const result = yield reporter.catch(error);
expect(result).toEqual({
success: false,
message: "Failed to submit bug report: 500 Internal Server Error",
timestamp: "2023-01-01T12:00:00.000Z",
});
}));
it("should handle network errors", () => __awaiter(void 0, void 0, void 0, function* () {
// Mock network error
global.fetch.mockRejectedValue(new Error("Network error"));
const error = new Error("Test error");
const result = yield reporter.catch(error);
expect(result).toEqual({
success: false,
message: "Failed to submit bug report: Network error",
timestamp: "2023-01-01T12:00:00.000Z",
});
}));
it("should not send report when bug tracker is disabled", () => __awaiter(void 0, void 0, void 0, function* () {
// Mock disabled config
jest
.spyOn(config_1.BugTrackerConfig.getInstance(), "isEnabled")
.mockReturnValue(false);
const error = new Error("Test error");
const result = yield reporter.catch(error);
expect(global.fetch).not.toHaveBeenCalled();
expect(result).toEqual({
success: false,
message: "Bug tracker is disabled",
timestamp: "2023-01-01T12:00:00.000Z",
});
}));
it("should handle onBeforeReport hook", () => __awaiter(void 0, void 0, void 0, function* () {
// Mock onBeforeReport hook
const mockOnBeforeReport = jest.fn().mockImplementation((report) => (Object.assign(Object.assign({}, report), { severity: "HIGH", title: "Modified title" })));
jest
.spyOn(config_1.BugTrackerConfig.getInstance(), "getOnBeforeReport")
.mockReturnValue(mockOnBeforeReport);
const error = new Error("Test error");
yield reporter.catch(error);
// Verify hook was called
expect(mockOnBeforeReport).toHaveBeenCalledTimes(1);
// Check that the modified report was sent
const [, options] = global.fetch.mock.calls[0];
const payload = JSON.parse(options.body);
expect(payload.severity).toBe("HIGH");
expect(payload.title).toBe("Modified title");
}));
it("should cancel report when onBeforeReport returns false", () => __awaiter(void 0, void 0, void 0, function* () {
// Mock onBeforeReport hook that cancels report
const mockOnBeforeReport = jest.fn().mockReturnValue(false);
jest
.spyOn(config_1.BugTrackerConfig.getInstance(), "getOnBeforeReport")
.mockReturnValue(mockOnBeforeReport);
const error = new Error("Test error");
const result = yield reporter.catch(error);
// Verify hook was called
expect(mockOnBeforeReport).toHaveBeenCalledTimes(1);
// Check that fetch was not called
expect(global.fetch).not.toHaveBeenCalled();
expect(result).toEqual({
success: false,
message: "Report cancelled by onBeforeReport hook",
timestamp: "2023-01-01T12:00:00.000Z",
});
}));
it("should call onAfterReport hook with report and result", () => __awaiter(void 0, void 0, void 0, function* () {
// Mock onAfterReport hook
const mockOnAfterReport = jest.fn();
jest
.spyOn(config_1.BugTrackerConfig.getInstance(), "getOnAfterReport")
.mockReturnValue(mockOnAfterReport);
const error = new Error("Test error");
const result = yield reporter.catch(error);
// Verify hook was called with report and result
expect(mockOnAfterReport).toHaveBeenCalledTimes(1);
expect(mockOnAfterReport.mock.calls[0][1]).toEqual(result);
}));
});
});