Files
formbricks-formbricks/apps/web/lib/utils/logger-helpers.test.ts
2025-09-12 11:16:13 +00:00

213 lines
6.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { deepDiff, redactPII, sanitizeUrlForLogging } from "./logger-helpers";
// Patch cache before any imports
beforeEach(async () => {
// Mock the cache service for tests
vi.doMock("@/lib/cache", () => ({
cache: {
getRedisClient: vi.fn().mockResolvedValue({
multi: vi.fn().mockReturnValue({
set: vi.fn(),
exec: vi.fn().mockResolvedValue([["OK"]]),
}),
watch: vi.fn().mockResolvedValue("OK"),
get: vi.fn().mockResolvedValue(null),
}),
},
}));
});
vi.mock("@/modules/ee/license-check/lib/utils", () => ({
getIsAuditLogsEnabled: vi.fn().mockResolvedValue(true),
}));
// Move all relevant mocks to the very top
vi.mock("@formbricks/logger", () => ({
logger: { error: vi.fn() },
}));
vi.mock("@/lib/utils/helper", () => ({
getOrganizationIdFromEnvironmentId: vi.fn().mockResolvedValue("org-env-id"),
}));
// Mocks
vi.mock("@/lib/constants", () => ({
AUDIT_LOG_ENABLED: true,
AUDIT_LOG_GET_USER_IP: true,
ENCRYPTION_KEY: "testsecret",
}));
vi.mock("@/lib/utils/client-ip", () => ({
getClientIpFromHeaders: vi.fn().mockResolvedValue("127.0.0.1"),
}));
vi.mock("@/modules/ee/audit-logs/lib/service", () => ({
logAuditEvent: vi.fn().mockResolvedValue(undefined),
}));
// Cache mock is handled in beforeEach above
// Set ENCRYPTION_KEY for all tests unless explicitly testing its absence
process.env.ENCRYPTION_KEY = "testsecret";
describe("redactPII", () => {
test("redacts sensitive keys in objects", () => {
const input = { email: "test@example.com", name: "John", foo: "bar" };
expect(redactPII(input)).toEqual({ email: "********", name: "********", foo: "bar" });
});
test("redacts nested sensitive keys", () => {
const input = { user: { password: "secret", profile: { address: "123 St" } } };
expect(redactPII(input)).toEqual({ user: { password: "********", profile: { address: "********" } } });
});
test("redacts arrays of objects", () => {
const input = [{ email: "a@b.com" }, { name: "Jane" }];
expect(redactPII(input)).toEqual([{ email: "********" }, { name: "********" }]);
});
test("returns primitives as is", () => {
expect(redactPII(42)).toBe(42);
expect(redactPII("foo")).toBe("foo");
expect(redactPII(null)).toBe(null);
});
});
describe("deepDiff", () => {
test("returns undefined for equal primitives", () => {
expect(deepDiff(1, 1)).toBeUndefined();
expect(deepDiff("a", "a")).toBeUndefined();
});
test("returns new value for different primitives", () => {
expect(deepDiff(1, 2)).toBe(2);
expect(deepDiff("a", "b")).toBe("b");
});
test("returns diff for objects", () => {
expect(deepDiff({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
expect(deepDiff({ a: 1, b: 2 }, { a: 1, b: 3 })).toEqual({ b: 3 });
});
test("returns diff for nested objects", () => {
expect(deepDiff({ a: { b: 1 } }, { a: { b: 2 } })).toEqual({ a: { b: 2 } });
});
test("returns diff for added/removed keys", () => {
expect(deepDiff({ a: 1 }, { a: 1, b: 2 })).toEqual({ b: 2 });
// The following case should return undefined, as removed keys are not included in the diff
expect(deepDiff({ a: 1, b: 2 }, { a: 1 })).toBeUndefined();
});
});
describe("withAuditLogging", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test("logs audit event for successful handler", async () => {
const handler = vi.fn().mockResolvedValue("ok");
const { withAuditLogging } = await import("../../modules/ee/audit-logs/lib/handler");
const wrapped = withAuditLogging("created", "survey", handler);
const ctx = {
user: {
id: "u1",
name: "Test User",
email: "test@example.com",
emailVerified: null,
twoFactorEnabled: false,
identityProvider: "email" as const,
createdAt: new Date(),
updatedAt: new Date(),
role: null,
organizationId: "org1",
isActive: true,
lastLoginAt: null,
locale: "en-US" as const,
teams: [],
organizations: [],
objective: null,
notificationSettings: {
alert: {},
},
},
organizationId: "org1",
ipAddress: "127.0.0.1",
auditLoggingCtx: {
ipAddress: "127.0.0.1",
organizationId: "org1",
},
};
const parsedInput = {};
await wrapped({ ctx, parsedInput });
vi.runAllTimers();
expect(handler).toHaveBeenCalled();
});
test("logs audit event for failed handler and throws", async () => {
const handler = vi.fn().mockRejectedValue(new Error("fail"));
const { withAuditLogging } = await import("../../modules/ee/audit-logs/lib/handler");
const wrapped = withAuditLogging("created", "survey", handler);
const ctx = {
user: {
id: "u1",
name: "Test User",
email: "test@example.com",
emailVerified: null,
twoFactorEnabled: false,
identityProvider: "email" as const,
createdAt: new Date(),
updatedAt: new Date(),
role: null,
organizationId: "org1",
isActive: true,
lastLoginAt: null,
locale: "en-US" as const,
teams: [],
organizations: [],
objective: null,
notificationSettings: {
alert: {},
},
},
organizationId: "org1",
ipAddress: "127.0.0.1",
auditLoggingCtx: {
ipAddress: "127.0.0.1",
organizationId: "org1",
},
};
const parsedInput = {};
await expect(wrapped({ ctx, parsedInput })).rejects.toThrow("fail");
vi.runAllTimers();
expect(handler).toHaveBeenCalled();
});
});
describe("sanitizeUrlForLogging", () => {
test("returns sanitized URL with token", () => {
expect(sanitizeUrlForLogging("https://example.com?token=1234567890")).toBe(
"https://example.com/?token=********"
);
});
test("returns sanitized URL with code", () => {
expect(sanitizeUrlForLogging("https://example.com?code=1234567890")).toBe(
"https://example.com/?code=********"
);
});
test("returns sanitized URL with state", () => {
expect(sanitizeUrlForLogging("https://example.com?state=1234567890")).toBe(
"https://example.com/?state=********"
);
});
test("returns sanitized URL with multiple keys", () => {
expect(
sanitizeUrlForLogging("https://example.com?token=1234567890&code=1234567890&state=1234567890")
).toBe("https://example.com/?token=********&code=********&state=********");
});
test("returns sanitized URL without query params", () => {
expect(sanitizeUrlForLogging("https://example.com")).toBe("https://example.com/");
});
test("returns sanitized URL with invalid URL", () => {
expect(sanitizeUrlForLogging("not-a-valid-url")).toBe("[invalid-url]");
});
});