diff --git a/README.md b/README.md index 7783c5f..38f23ef 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Both RedisService and HttpServer are implemented as singletons: Example: Testing `RedisService.lpush` with a mocked Redis client. ```ts -test("should call lPush on the client", async () => { +test("Should call lPush on the client", async () => { const fakeClient = { lPush: mock(async () => 1), }; diff --git a/package.json b/package.json index 3c88644..68c0d8b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "start": "bun index.ts", "test": "bun test", "build": "bun build index.ts --outdir dist --minify --target bun", - "format": "npx prettier --write ." + "format": "npx prettier --write .", + "coverage": "bun test --coverage" }, "devDependencies": { "@types/bun": "latest", diff --git a/test/e2e/messagesApi.test.ts b/test/e2e/messagesApi.test.ts index eddd430..7562b90 100644 --- a/test/e2e/messagesApi.test.ts +++ b/test/e2e/messagesApi.test.ts @@ -3,8 +3,8 @@ import { test, expect, beforeAll, - afterAll, beforeEach, + afterAll, } from "bun:test"; import { RedisService } from "../../src/services/redisService.ts"; import { HttpServer } from "../../src/http/server.ts"; @@ -20,16 +20,16 @@ describe("Messages API – End-to-End Tests", () => { await HttpServer.start(); }); - beforeEach(async () => { - // Reset Redis database before each test - await RedisService.op().flushAll(); - }); - afterAll(async () => { await RedisService.stop(); await HttpServer.stop(); }); + beforeEach(async () => { + // Reset Redis database before each test + await RedisService.op().flushAll(); + }); + describe("GET /messages endpoint", async () => { test("Should return an empty list when no messages are stored", async () => { const response = await axios.get( @@ -124,7 +124,7 @@ describe("Messages API – End-to-End Tests", () => { ); }); - test("should handle multiple POST requests at the same time without losing messages", async () => { + test("Should handle multiple POST requests at the same time without losing messages", async () => { const messages = ["msg1", "msg2", "msg3", "msg4", "msg5"]; await Promise.all( diff --git a/test/example.test.ts b/test/example.test.ts index 56e0b87..d59d918 100644 --- a/test/example.test.ts +++ b/test/example.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "bun:test"; describe("Math utilities – basic operations", () => { - test("should correctly sum two positive integers", () => { + test("Should correctly sum two positive integers", () => { const a = 2; const b = 2; const result = a + b; diff --git a/test/integration/messageRoutes.test.ts b/test/integration/messageRoutes.test.ts index 826ab05..205fb2f 100644 --- a/test/integration/messageRoutes.test.ts +++ b/test/integration/messageRoutes.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect, afterAll, beforeAll } from "bun:test"; +import { describe, test, expect, beforeAll, afterAll } from "bun:test"; import { messageRoutes } from "../../src/routes/messages"; import { RedisService } from "../../src/services/redisService.ts"; import type { ErrorResponse, GetMessagesResponse } from "../utils/types.ts"; diff --git a/test/integration/redisService.test.ts b/test/integration/redisService.test.ts new file mode 100644 index 0000000..44b96d8 --- /dev/null +++ b/test/integration/redisService.test.ts @@ -0,0 +1,89 @@ +import { describe, test, expect, beforeAll } from "bun:test"; +import { RedisService } from "../../src/services/redisService"; + +describe("Redis Service – Integration Tests", () => { + beforeAll(async () => { + await RedisService.start(); + await RedisService.op().del("messages"); + }); + + test("Should connect and return a working Redis instance", async () => { + const pong = await RedisService.op().ping(); + expect(pong).toBe("PONG"); + }); + + test("Should push a message into Redis list", async () => { + const result = await RedisService.lpush("messages", "hello"); + expect(typeof result).toBe("number"); + expect(result).toBeGreaterThan(0); + + const stored = await RedisService.lrange("messages", 0, -1); + expect(stored).toContain("hello"); + }); + + test("Should push multiple messages and maintain order (LIFO)", async () => { + await RedisService.op().del("messages"); + + await RedisService.lpush("messages", "A"); + await RedisService.lpush("messages", "B"); + await RedisService.lpush("messages", "C"); + + const stored = await RedisService.lrange("messages", 0, -1); + expect(stored).toEqual(["C", "B", "A"]); + }); + + test("Should return empty array when key does not exist", async () => { + await RedisService.op().del("unknown"); + + const stored = await RedisService.lrange("unknown", 0, -1); + expect(stored).toEqual([]); + }); + + test("Should delete the list successfully", async () => { + await RedisService.lpush("messages", "to_delete"); + const del = await RedisService.op().del("messages"); + + expect(del).toBe(1); + + const after = await RedisService.lrange("messages", 0, -1); + expect(after).toEqual([]); + }); + + test("Should handle empty string pushes", async () => { + await RedisService.op().del("messages"); + + await RedisService.lpush("messages", ""); + const stored = await RedisService.lrange("messages", 0, -1); + + expect(stored).toEqual([""]); + }); + + test("Should throw an error when pushing without Redis started", async () => { + await RedisService.stop(); + + try { + await RedisService.lpush("messages", "fail"); + } catch (err) { + expect((err as Error).message.toLowerCase()).toContain( + "the client is closed", + ); + } finally { + await RedisService.start(); + } + }); + + test("Should allow multiple concurrent pushes without losing data", async () => { + await RedisService.op().del("messages"); + + const messages = Array.from({ length: 50 }, (_, i) => `msg_${i}`); + + await Promise.all(messages.map((m) => RedisService.lpush("messages", m))); + + const stored = await RedisService.lrange("messages", 0, -1); + + messages.forEach((m) => { + expect(stored).toContain(m); + }); + expect(stored.length).toBe(50); + }); +}); diff --git a/test/unit/redisService.test.ts b/test/unit/redisService.test.ts index c9f1ddd..0940ddd 100644 --- a/test/unit/redisService.test.ts +++ b/test/unit/redisService.test.ts @@ -6,7 +6,7 @@ describe("RedisService - Unit tests", () => { (RedisService as any).client = null; }); - test("should call lPush on the client", async () => { + test("Should call lPush on the client", async () => { const fakeClient = { lPush: mock(async () => 1), }; @@ -19,7 +19,7 @@ describe("RedisService - Unit tests", () => { expect(fakeClient.lPush).toHaveBeenCalledWith("messages", "hello"); }); - test("should call lRange on the client", async () => { + test("Should call lRange on the client", async () => { const fakeClient = { lRange: mock(async () => ["hi"]), }; @@ -33,7 +33,7 @@ describe("RedisService - Unit tests", () => { expect(result).toEqual(["hi"]); }); - test("should return 15 items from 0 to 14", async () => { + test("Should return 15 items from 0 to 14", async () => { const fakeClient = { lRange: mock(async () => Array.from({ length: 15 }, (_, i) => `msg-${i}`), diff --git a/tsconfig.json b/tsconfig.json index bfa0fea..c89d33b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - // Environment setup & latest features "lib": ["ESNext"], "target": "ESNext", "module": "Preserve", @@ -8,22 +7,21 @@ "jsx": "react-jsx", "allowJs": true, - // Bundler mode "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "noEmit": true, - // Best practices "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, - // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false - } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "tests"] }