mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
- Add RedisClient with connection management and AOF config - Add RedisStorage implementing full IStorage interface - Add Redis key schema for project and session data - Add generateProjectName() utility - Add 68 unit tests for Redis module (159 total) - Update ESLint: no-unnecessary-type-parameters as warn
178 lines
5.7 KiB
TypeScript
178 lines
5.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
import type { RedisConfig } from "../../../../src/shared/constants/config.js"
|
|
import { IpuaroError } from "../../../../src/shared/errors/IpuaroError.js"
|
|
|
|
const mockRedisInstance = {
|
|
connect: vi.fn(),
|
|
quit: vi.fn(),
|
|
ping: vi.fn(),
|
|
config: vi.fn(),
|
|
status: "ready" as string,
|
|
}
|
|
|
|
vi.mock("ioredis", () => {
|
|
return {
|
|
Redis: vi.fn(() => mockRedisInstance),
|
|
}
|
|
})
|
|
|
|
const { RedisClient } = await import("../../../../src/infrastructure/storage/RedisClient.js")
|
|
|
|
describe("RedisClient", () => {
|
|
const defaultConfig: RedisConfig = {
|
|
host: "localhost",
|
|
port: 6379,
|
|
db: 0,
|
|
keyPrefix: "ipuaro:",
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockRedisInstance.status = "ready"
|
|
mockRedisInstance.connect.mockResolvedValue(undefined)
|
|
mockRedisInstance.quit.mockResolvedValue(undefined)
|
|
mockRedisInstance.ping.mockResolvedValue("PONG")
|
|
mockRedisInstance.config.mockResolvedValue(undefined)
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
describe("constructor", () => {
|
|
it("should create instance with config", () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
expect(client).toBeDefined()
|
|
expect(client.isConnected()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("connect", () => {
|
|
it("should connect to Redis", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
|
|
expect(mockRedisInstance.connect).toHaveBeenCalled()
|
|
expect(client.isConnected()).toBe(true)
|
|
})
|
|
|
|
it("should configure AOF on connect", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
|
|
expect(mockRedisInstance.config).toHaveBeenCalledWith("SET", "appendonly", "yes")
|
|
expect(mockRedisInstance.config).toHaveBeenCalledWith("SET", "appendfsync", "everysec")
|
|
})
|
|
|
|
it("should not reconnect if already connected", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
await client.connect()
|
|
|
|
expect(mockRedisInstance.connect).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("should throw IpuaroError on connection failure", async () => {
|
|
mockRedisInstance.connect.mockRejectedValue(new Error("Connection refused"))
|
|
|
|
const client = new RedisClient(defaultConfig)
|
|
|
|
await expect(client.connect()).rejects.toThrow(IpuaroError)
|
|
await expect(client.connect()).rejects.toMatchObject({
|
|
type: "redis",
|
|
})
|
|
})
|
|
|
|
it("should handle AOF config failure gracefully", async () => {
|
|
mockRedisInstance.config.mockRejectedValue(new Error("CONFIG disabled"))
|
|
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
|
|
expect(client.isConnected()).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("disconnect", () => {
|
|
it("should disconnect from Redis", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
await client.disconnect()
|
|
|
|
expect(mockRedisInstance.quit).toHaveBeenCalled()
|
|
expect(client.isConnected()).toBe(false)
|
|
})
|
|
|
|
it("should handle disconnect when not connected", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.disconnect()
|
|
|
|
expect(mockRedisInstance.quit).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe("isConnected", () => {
|
|
it("should return false when not connected", () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
expect(client.isConnected()).toBe(false)
|
|
})
|
|
|
|
it("should return true when connected and ready", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
expect(client.isConnected()).toBe(true)
|
|
})
|
|
|
|
it("should return false when client status is not ready", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
mockRedisInstance.status = "connecting"
|
|
expect(client.isConnected()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("getClient", () => {
|
|
it("should return Redis client when connected", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
|
|
const redis = client.getClient()
|
|
expect(redis).toBe(mockRedisInstance)
|
|
})
|
|
|
|
it("should throw when not connected", () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
|
|
expect(() => client.getClient()).toThrow(IpuaroError)
|
|
expect(() => client.getClient()).toThrow("not connected")
|
|
})
|
|
})
|
|
|
|
describe("ping", () => {
|
|
it("should return true on successful ping", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
|
|
const result = await client.ping()
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it("should return false when not connected", async () => {
|
|
const client = new RedisClient(defaultConfig)
|
|
|
|
const result = await client.ping()
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it("should return false on ping failure", async () => {
|
|
mockRedisInstance.ping.mockRejectedValue(new Error("Timeout"))
|
|
|
|
const client = new RedisClient(defaultConfig)
|
|
await client.connect()
|
|
|
|
const result = await client.ping()
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
})
|