154 lines
4.0 KiB
TypeScript
154 lines
4.0 KiB
TypeScript
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
|
const mockState = {
|
|
warnings: [] as Array<{ message: string; data?: Record<string, unknown> }>,
|
|
};
|
|
const defaultRedis = { status: "ready" };
|
|
|
|
// Stub the full `Logger` shape — Bun's `mock.module` is process-wide, so
|
|
// later unit tests inherit this stub. Missing methods crash unrelated code.
|
|
const mockLogger = {
|
|
debug: () => undefined,
|
|
info: () => undefined,
|
|
warn: (data: Record<string, unknown> | string, message?: string) => {
|
|
if (typeof data === "string") {
|
|
mockState.warnings.push({ message: data });
|
|
return;
|
|
}
|
|
|
|
mockState.warnings.push({
|
|
message: message || "",
|
|
data,
|
|
});
|
|
},
|
|
error: () => undefined,
|
|
child: () => mockLogger,
|
|
};
|
|
|
|
mock.module("@/external/logtail/logtailUtils.js", () => ({
|
|
logger: mockLogger,
|
|
}));
|
|
|
|
mock.module("@/external/redis/initUtils/redisConfig.js", () => ({
|
|
hasRedisConfig: true,
|
|
}));
|
|
mock.module("@/external/redis/initUtils/redisClientRegistry.js", () => ({
|
|
redis: defaultRedis,
|
|
}));
|
|
mock.module("@/external/redis/initRedis.js", () => ({
|
|
redis: defaultRedis,
|
|
}));
|
|
|
|
import { RedisUnavailableError } from "@/external/redis/utils/errors.js";
|
|
import {
|
|
tryRedisNx,
|
|
tryRedisRead,
|
|
tryRedisWrite,
|
|
} from "@/utils/cacheUtils/cacheUtils.js";
|
|
|
|
describe("cache utils", () => {
|
|
beforeEach(() => {
|
|
mockState.warnings = [];
|
|
});
|
|
|
|
test("tryRedisRead returns null and warns when Redis operation throws", async () => {
|
|
const result = await tryRedisRead(
|
|
async () => {
|
|
throw new Error("boom");
|
|
},
|
|
{ status: "ready" } as never,
|
|
);
|
|
|
|
expect(result).toBeNull();
|
|
expect(mockState.warnings).toHaveLength(1);
|
|
expect(mockState.warnings[0]).toEqual({
|
|
message: "[redis] operation unavailable",
|
|
data: {
|
|
source: "tryRedisRead:error",
|
|
error: "boom",
|
|
},
|
|
});
|
|
});
|
|
|
|
test("tryRedisWrite throws RedisUnavailableError when Redis is not ready", async () => {
|
|
const error = await tryRedisWrite(async () => "OK", {
|
|
status: "connecting",
|
|
} as never).catch((caught) => caught);
|
|
|
|
expect(error).toBeInstanceOf(RedisUnavailableError);
|
|
expect(error.reason).toBe("not_ready");
|
|
expect(mockState.warnings).toHaveLength(1);
|
|
expect(mockState.warnings[0]).toEqual({
|
|
message: "[redis] operation unavailable",
|
|
data: {
|
|
source: "tryRedisWrite:not-ready",
|
|
error: undefined,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("tryRedisRead throws RedisUnavailableError when Redis command times out", async () => {
|
|
const error = await tryRedisRead(
|
|
async () => {
|
|
throw new Error("Command timed out");
|
|
},
|
|
{ status: "ready" } as never,
|
|
).catch((caught) => caught);
|
|
|
|
expect(error).toBeInstanceOf(RedisUnavailableError);
|
|
expect(error.reason).toBe("timeout");
|
|
});
|
|
|
|
test("tryRedisNx throws RedisUnavailableError on connection failures", async () => {
|
|
const error = await tryRedisNx({
|
|
operation: async () => {
|
|
throw new Error("Connection is closed.");
|
|
},
|
|
redisInstance: { status: "ready" } as never,
|
|
onRedisUnavailable: () => "fallback",
|
|
onSuccess: () => "success",
|
|
onKeyAlreadyExists: () => "exists",
|
|
}).catch((caught) => caught);
|
|
|
|
expect(error).toBeInstanceOf(RedisUnavailableError);
|
|
expect(error.reason).toBe("connection");
|
|
});
|
|
|
|
test("tryRedisNx falls back and warns when Redis operation throws", async () => {
|
|
const result = await tryRedisNx({
|
|
operation: async () => {
|
|
throw new Error("boom");
|
|
},
|
|
redisInstance: { status: "ready" } as never,
|
|
onRedisUnavailable: () => "fallback",
|
|
onSuccess: () => "success",
|
|
onKeyAlreadyExists: () => "exists",
|
|
});
|
|
|
|
expect(result).toBe("fallback");
|
|
});
|
|
|
|
test("custom Redis failures do not mark default Redis unavailable", async () => {
|
|
const result = await tryRedisRead(
|
|
async () => {
|
|
throw new Error("custom redis failed");
|
|
},
|
|
{ status: "ready" } as never,
|
|
);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
test("Redis command errors do not mark Redis unavailable", async () => {
|
|
await tryRedisWrite(async () => {
|
|
throw new Error("ERR user_script:2: unexpected symbol near '#'");
|
|
});
|
|
|
|
expect(mockState.warnings).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
afterAll(() => {
|
|
mock.restore();
|
|
});
|