From c54db64d21a4642845df6da94f0cb8284ed46215 Mon Sep 17 00:00:00 2001 From: PatricioPoncini Date: Mon, 24 Nov 2025 11:46:17 -0300 Subject: [PATCH 1/4] feat: refactor to singleton --- index.ts | 20 ++++++++++++-- src/http/server.ts | 20 ++++++++++++++ src/routes/messages.ts | 6 ++-- src/server.ts | 28 ------------------- src/services/redisService.ts | 53 ++++++++++++++---------------------- 5 files changed, 61 insertions(+), 66 deletions(-) create mode 100644 src/http/server.ts delete mode 100644 src/server.ts diff --git a/index.ts b/index.ts index e54f8c2..2220aee 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,19 @@ -import { startServer } from "./src/server"; +import { HttpServer } from "./src/http/server.ts"; +import { RedisService } from "./src/services/redisService.ts"; -await startServer(); +// TODO: Esto podria pasar a una clase Application que sea general para todos los servicios +async function main() { + await RedisService.start(); + await HttpServer.start(); + + const gracefulShutdown = async () => { + await HttpServer.stop(); + await RedisService.stop(); + process.exit(0); + }; + + process.on("SIGINT", gracefulShutdown); + process.on("SIGTERM", gracefulShutdown); +} + +await main(); diff --git a/src/http/server.ts b/src/http/server.ts new file mode 100644 index 0000000..c7e6c30 --- /dev/null +++ b/src/http/server.ts @@ -0,0 +1,20 @@ +import { serve } from "bun"; +import app from "../app.ts"; + +export class HttpServer { + private static server: ReturnType | null = null; + private static port = 3000; + + static async start() { + this.server = serve({ + port: this.port, + fetch: app.fetch, + }); + console.log(`\x1b[32m ๐Ÿ“ก Server up and running on port :${this.port} \x1b[0m`); + } + + static async stop() { + this.server?.stop(); + console.log('\x1b[31m ๐Ÿ”ด HTTP server stopped \x1b[0m'); + } +} diff --git a/src/routes/messages.ts b/src/routes/messages.ts index 6226c06..42792f8 100644 --- a/src/routes/messages.ts +++ b/src/routes/messages.ts @@ -1,5 +1,5 @@ import { Hono } from 'hono' -import { redis } from '../services/redisService' +import {RedisService} from "../services/redisService.ts"; export const messageRoutes = new Hono() @@ -12,7 +12,7 @@ messageRoutes.post('/', async (c) => { return c.json({ error: 'Invalid body: expected { text: string }' }, 400) } - await redis.lpush('messages', body.text) + await RedisService.op().lpush('messages', body.text) console.log('[HTTP] Message stored successfully') return c.json({ ok: true }) @@ -24,7 +24,7 @@ messageRoutes.post('/', async (c) => { messageRoutes.get('/', async (c) => { try { - const messages = await redis.lrange('messages', 0, 9) + const messages = await RedisService.op().lrange('messages', 0, 9) return c.json({ count: messages.length, messages }) } catch (err) { console.error('[HTTP] Error in GET /messages:', err) diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 9f9876e..0000000 --- a/src/server.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { serve } from "bun"; -import app from "./app"; -import { redisClient } from "./services/redisService"; - -export async function startServer(port = 0) { - const server = serve({ - port, - fetch: app.fetch, - }); - - console.log(`[HTTP] Server running on port ${server.port}`); - - return server; -} - -export async function stopServer(server: ReturnType) { - try { - if (redisClient?.isOpen) { - await redisClient.quit(); - console.log("[Redis] Connection closed."); - } - } catch (err) { - console.error("[Redis] Error while closing client:", err); - } - - server.stop(); - console.log("[HTTP] Shutdown complete."); -} diff --git a/src/services/redisService.ts b/src/services/redisService.ts index 9f34828..bc54369 100644 --- a/src/services/redisService.ts +++ b/src/services/redisService.ts @@ -1,44 +1,31 @@ import { createClient } from 'redis' -import type { RedisLike } from "../types/redisTypes.ts"; export class RedisService { - constructor(private client: RedisLike) {} + private static client: ReturnType | null = null; - async lpush(key: string, value: string) { - console.log(`[Redis] LPUSH -> key="${key}", value="${value}"`); - try { - const result = await this.client.lPush(key, value); - console.log(`[Redis] LPUSH result ->`, result); - return result; - } catch (err) { - console.error(`[Redis] LPUSH error:`, err); - throw err; - } + static async start() { + const c = createClient({ url: "redis://localhost:6379" }); + await c.connect(); + this.client = c; + console.log('\x1b[32m ๐Ÿ’พ Redis connected successfully \x1b[0m'); } - async lrange(key: string, start: number, stop: number) { - console.log(`[Redis] LRANGE -> key="${key}", start=${start}, stop=${stop}`); - try { - const result = await this.client.lRange(key, start, stop); - console.log(`[Redis] LRANGE result ->`, result); - return result; - } catch (err) { - console.error(`[Redis] LRANGE error:`, err); - throw err; + static async stop() { + if (this.client?.isOpen) { + await this.client.quit(); + console.log('\x1b[31m ๐Ÿ”ด Redis stopped \x1b[0m'); } } -} - -export const redisClient = createClient({ - url: 'redis://localhost:6379' // TODO: Podrรญa pasarse esto a un .env -}) - -redisClient.on('error', (err) => { - console.error('[Redis] Connection error:', err); -}); -await redisClient.connect(); + static op() { + return this.client; + } -console.log('[Redis] Client connected successfully.'); + static lpush(key: string, value: string) { + return this.client!.lPush(key, value); + } -export const redis = new RedisService(redisClient); + static lrange(key: string, start: number, stop: number) { + return this.client!.lRange(key, start, stop); + } +} \ No newline at end of file From 736851e3a37d175fd719cfc83c558144474c23a3 Mon Sep 17 00:00:00 2001 From: PatricioPoncini Date: Mon, 24 Nov 2025 11:57:23 -0300 Subject: [PATCH 2/4] fix: test cases to adapt to singleton --- src/routes/messages.ts | 4 ++-- src/services/redisService.ts | 5 ++++- test/integration/messageRoutes.test.ts | 11 ++++++--- test/unit/redisService.test.ts | 31 ++++++++++++++------------ 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/routes/messages.ts b/src/routes/messages.ts index 42792f8..1c97189 100644 --- a/src/routes/messages.ts +++ b/src/routes/messages.ts @@ -12,7 +12,7 @@ messageRoutes.post('/', async (c) => { return c.json({ error: 'Invalid body: expected { text: string }' }, 400) } - await RedisService.op().lpush('messages', body.text) + await RedisService.lpush('messages', body.text) console.log('[HTTP] Message stored successfully') return c.json({ ok: true }) @@ -24,7 +24,7 @@ messageRoutes.post('/', async (c) => { messageRoutes.get('/', async (c) => { try { - const messages = await RedisService.op().lrange('messages', 0, 9) + const messages = await RedisService.lrange('messages', 0, 9) return c.json({ count: messages.length, messages }) } catch (err) { console.error('[HTTP] Error in GET /messages:', err) diff --git a/src/services/redisService.ts b/src/services/redisService.ts index bc54369..d6fc7c5 100644 --- a/src/services/redisService.ts +++ b/src/services/redisService.ts @@ -18,7 +18,10 @@ export class RedisService { } static op() { - return this.client; + if (!this.client) { + throw new Error("RedisService not initialized") + } + return this.client } static lpush(key: string, value: string) { diff --git a/test/integration/messageRoutes.test.ts b/test/integration/messageRoutes.test.ts index b223be6..0f8bfbe 100644 --- a/test/integration/messageRoutes.test.ts +++ b/test/integration/messageRoutes.test.ts @@ -1,6 +1,6 @@ -import { describe, it, expect, beforeEach, beforeAll } from "bun:test" +import { describe, it, expect, afterAll, beforeAll } from "bun:test" import { messageRoutes } from "../../src/routes/messages" -import { redisClient } from "../../src/services/redisService" +import {RedisService} from "../../src/services/redisService.ts"; interface GetMessagesResponse { count: number; @@ -13,7 +13,12 @@ interface ErrorResponse { describe("Message Routes โ€“ Integration Tests", () => { beforeAll(async () => { - await redisClient.del("messages") + await RedisService.start(); + await RedisService.op().del("messages"); + }) + + afterAll(async () => { + await RedisService.stop(); }) describe("Success cases", () => { diff --git a/test/unit/redisService.test.ts b/test/unit/redisService.test.ts index ab24e96..4957948 100644 --- a/test/unit/redisService.test.ts +++ b/test/unit/redisService.test.ts @@ -1,33 +1,36 @@ -import { describe, it, expect, mock } from "bun:test" +import { describe, it, expect, mock, afterEach } from "bun:test" import { RedisService } from "../../src/services/redisService" -import type {RedisClientType} from "redis"; describe("RedisService - unit", () => { + afterEach(() => { + (RedisService as any).client = null + }) + it("should call lPush on the client", async () => { const fakeClient = { - lPush: mock(async () => 1) - } + lPush: mock(async () => 1) + } - const redis = new RedisService(fakeClient as unknown as RedisClientType) + ;(RedisService as any).client = fakeClient - await redis.lpush("messages", "hello") + await RedisService.lpush("messages", "hello") expect(fakeClient.lPush).toHaveBeenCalled() expect(fakeClient.lPush).toHaveBeenCalledWith("messages", "hello") }) - it("should call lrange on the client", async () => { + it("should call lRange on the client", async () => { const fakeClient = { - lRange: mock(async () => ["hi"]) - } + lRange: mock(async () => ["hi"]) + } - const redis = new RedisService(fakeClient as unknown as RedisClientType) + ;(RedisService as any).client = fakeClient - const range = await redis.lrange("messages", 0, 1) + const result = await RedisService.lrange("messages", 0, 1) expect(fakeClient.lRange).toHaveBeenCalled() expect(fakeClient.lRange).toHaveBeenCalledWith("messages", 0, 1) - expect(range).toEqual(["hi"]) + expect(result).toEqual(["hi"]) }) it("should return 15 elementos from 0 to 14", async () => { @@ -35,9 +38,9 @@ describe("RedisService - unit", () => { lRange: mock(async () => Array.from({ length: 15 }, (_, i) => `msg-${i}`)) } - const redis = new RedisService(fakeClient as unknown as RedisClientType) + ;(RedisService as any).client = fakeClient - const range = await redis.lrange("messages", 0, 14) + const range = await RedisService.lrange("messages", 0, 14) expect(fakeClient.lRange).toHaveBeenCalled() expect(fakeClient.lRange).toHaveBeenCalledWith("messages", 0, 14) From 8bf15e8e153c759ff9009cc01824daab4f96f87d Mon Sep 17 00:00:00 2001 From: PatricioPoncini Date: Mon, 24 Nov 2025 11:59:24 -0300 Subject: [PATCH 3/4] feat: remove comment --- index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/index.ts b/index.ts index 2220aee..95615bf 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,6 @@ import { HttpServer } from "./src/http/server.ts"; import { RedisService } from "./src/services/redisService.ts"; -// TODO: Esto podria pasar a una clase Application que sea general para todos los servicios async function main() { await RedisService.start(); await HttpServer.start(); From 5396cf8334f51d8e3b18bc09b622db0ea2591ddb Mon Sep 17 00:00:00 2001 From: PatricioPoncini Date: Mon, 24 Nov 2025 12:00:26 -0300 Subject: [PATCH 4/4] feat: format code --- README.md | 6 +- index.ts | 18 +-- package.json | 3 +- src/app.ts | 10 +- src/http/server.ts | 28 ++-- src/routes/messages.ts | 54 +++---- src/services/redisService.ts | 50 +++---- src/types/redisTypes.ts | 4 +- test/example.test.ts | 12 +- test/integration/messageRoutes.test.ts | 189 +++++++++++++------------ test/unit/redisService.test.ts | 72 +++++----- 11 files changed, 228 insertions(+), 218 deletions(-) diff --git a/README.md b/README.md index 2dd94a2..f0dff46 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `Testing Lab` -This repository contains a small backend server built with **Bun**, **Hono**, **Redis**, and **TypeScript**, designed as a playground for exploring different testing strategies. +This repository contains a small backend server built with **Bun**, **Hono**, **Redis**, and **TypeScript**, designed as a playground for exploring different testing strategies. The goal of this project is to provide clear, practical examples of **unit tests**, **integration tests**, and **end-to-end (E2E) tests** while experimenting with modern backend tools. @@ -25,9 +25,11 @@ This project is intended as a **testing playground**, where you can: ```sh bun run dev ``` + The server will automatically reload when files change. ## `๐Ÿงช Running tests` + ```shell bun test ``` @@ -39,4 +41,4 @@ Tests will be progressively added across all categories (unit, integration, and ### `๐Ÿ“ Notes` This project is intentionally minimal and modular to make testing patterns easy to understand and extend. -More test cases, utilities, and scenarios will be added over time. \ No newline at end of file +More test cases, utilities, and scenarios will be added over time. diff --git a/index.ts b/index.ts index 95615bf..2ff8562 100644 --- a/index.ts +++ b/index.ts @@ -2,17 +2,17 @@ import { HttpServer } from "./src/http/server.ts"; import { RedisService } from "./src/services/redisService.ts"; async function main() { - await RedisService.start(); - await HttpServer.start(); + await RedisService.start(); + await HttpServer.start(); - const gracefulShutdown = async () => { - await HttpServer.stop(); - await RedisService.stop(); - process.exit(0); - }; + const gracefulShutdown = async () => { + await HttpServer.stop(); + await RedisService.stop(); + process.exit(0); + }; - process.on("SIGINT", gracefulShutdown); - process.on("SIGTERM", gracefulShutdown); + process.on("SIGINT", gracefulShutdown); + process.on("SIGTERM", gracefulShutdown); } await main(); diff --git a/package.json b/package.json index db8c4fa..3c88644 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "bun --hot run index.ts", "start": "bun index.ts", "test": "bun test", - "build": "bun build index.ts --outdir dist --minify --target bun" + "build": "bun build index.ts --outdir dist --minify --target bun", + "format": "npx prettier --write ." }, "devDependencies": { "@types/bun": "latest", diff --git a/src/app.ts b/src/app.ts index d6f5f12..53cfb31 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,8 +1,8 @@ -import {Hono} from "hono"; -import {messageRoutes} from "./routes/messages.ts"; +import { Hono } from "hono"; +import { messageRoutes } from "./routes/messages.ts"; -const app = new Hono() +const app = new Hono(); -app.route('/messages', messageRoutes) +app.route("/messages", messageRoutes); -export default app; \ No newline at end of file +export default app; diff --git a/src/http/server.ts b/src/http/server.ts index c7e6c30..83a2c30 100644 --- a/src/http/server.ts +++ b/src/http/server.ts @@ -2,19 +2,21 @@ import { serve } from "bun"; import app from "../app.ts"; export class HttpServer { - private static server: ReturnType | null = null; - private static port = 3000; + private static server: ReturnType | null = null; + private static port = 3000; - static async start() { - this.server = serve({ - port: this.port, - fetch: app.fetch, - }); - console.log(`\x1b[32m ๐Ÿ“ก Server up and running on port :${this.port} \x1b[0m`); - } + static async start() { + this.server = serve({ + port: this.port, + fetch: app.fetch, + }); + console.log( + `\x1b[32m ๐Ÿ“ก Server up and running on port :${this.port} \x1b[0m`, + ); + } - static async stop() { - this.server?.stop(); - console.log('\x1b[31m ๐Ÿ”ด HTTP server stopped \x1b[0m'); - } + static async stop() { + this.server?.stop(); + console.log("\x1b[31m ๐Ÿ”ด HTTP server stopped \x1b[0m"); + } } diff --git a/src/routes/messages.ts b/src/routes/messages.ts index 1c97189..b407d49 100644 --- a/src/routes/messages.ts +++ b/src/routes/messages.ts @@ -1,33 +1,33 @@ -import { Hono } from 'hono' -import {RedisService} from "../services/redisService.ts"; +import { Hono } from "hono"; +import { RedisService } from "../services/redisService.ts"; -export const messageRoutes = new Hono() +export const messageRoutes = new Hono(); -messageRoutes.post('/', async (c) => { - try { - const body = await c.req.json().catch(() => null) +messageRoutes.post("/", async (c) => { + try { + const body = await c.req.json().catch(() => null); - if (!body?.text || typeof body.text !== 'string') { - console.warn('[HTTP] Invalid payload received') - return c.json({ error: 'Invalid body: expected { text: string }' }, 400) - } + if (!body?.text || typeof body.text !== "string") { + console.warn("[HTTP] Invalid payload received"); + return c.json({ error: "Invalid body: expected { text: string }" }, 400); + } - await RedisService.lpush('messages', body.text) + await RedisService.lpush("messages", body.text); - console.log('[HTTP] Message stored successfully') - return c.json({ ok: true }) - } catch (err) { - console.error('[HTTP] Error in POST /messages:', err) - return c.json({ error: 'Internal server error' }, 500) - } -}) + console.log("[HTTP] Message stored successfully"); + return c.json({ ok: true }); + } catch (err) { + console.error("[HTTP] Error in POST /messages:", err); + return c.json({ error: "Internal server error" }, 500); + } +}); -messageRoutes.get('/', async (c) => { - try { - const messages = await RedisService.lrange('messages', 0, 9) - return c.json({ count: messages.length, messages }) - } catch (err) { - console.error('[HTTP] Error in GET /messages:', err) - return c.json({ error: 'Internal server error' }, 500) - } -}) +messageRoutes.get("/", async (c) => { + try { + const messages = await RedisService.lrange("messages", 0, 9); + return c.json({ count: messages.length, messages }); + } catch (err) { + console.error("[HTTP] Error in GET /messages:", err); + return c.json({ error: "Internal server error" }, 500); + } +}); diff --git a/src/services/redisService.ts b/src/services/redisService.ts index d6fc7c5..d4095a8 100644 --- a/src/services/redisService.ts +++ b/src/services/redisService.ts @@ -1,34 +1,34 @@ -import { createClient } from 'redis' +import { createClient } from "redis"; export class RedisService { - private static client: ReturnType | null = null; + private static client: ReturnType | null = null; - static async start() { - const c = createClient({ url: "redis://localhost:6379" }); - await c.connect(); - this.client = c; - console.log('\x1b[32m ๐Ÿ’พ Redis connected successfully \x1b[0m'); - } + static async start() { + const c = createClient({ url: "redis://localhost:6379" }); + await c.connect(); + this.client = c; + console.log("\x1b[32m ๐Ÿ’พ Redis connected successfully \x1b[0m"); + } - static async stop() { - if (this.client?.isOpen) { - await this.client.quit(); - console.log('\x1b[31m ๐Ÿ”ด Redis stopped \x1b[0m'); - } + static async stop() { + if (this.client?.isOpen) { + await this.client.quit(); + console.log("\x1b[31m ๐Ÿ”ด Redis stopped \x1b[0m"); } + } - static op() { - if (!this.client) { - throw new Error("RedisService not initialized") - } - return this.client + static op() { + if (!this.client) { + throw new Error("RedisService not initialized"); } + return this.client; + } - static lpush(key: string, value: string) { - return this.client!.lPush(key, value); - } + static lpush(key: string, value: string) { + return this.client!.lPush(key, value); + } - static lrange(key: string, start: number, stop: number) { - return this.client!.lRange(key, start, stop); - } -} \ No newline at end of file + static lrange(key: string, start: number, stop: number) { + return this.client!.lRange(key, start, stop); + } +} diff --git a/src/types/redisTypes.ts b/src/types/redisTypes.ts index 985e639..1c4a6d0 100644 --- a/src/types/redisTypes.ts +++ b/src/types/redisTypes.ts @@ -1,4 +1,4 @@ export interface RedisLike { - lPush(key: string, value: string): Promise - lRange(key: string, start: number, stop: number): Promise + lPush(key: string, value: string): Promise; + lRange(key: string, start: number, stop: number): Promise; } diff --git a/test/example.test.ts b/test/example.test.ts index a975969..0921d27 100644 --- a/test/example.test.ts +++ b/test/example.test.ts @@ -1,8 +1,8 @@ -import {describe, test, expect} from "bun:test"; +import { describe, test, expect } from "bun:test"; describe("Example test case", () => { - test("Simple sum", async () => { - const sum = 2 + 2; - expect(sum).toBe(4); - }) -}) \ No newline at end of file + test("Simple sum", async () => { + const sum = 2 + 2; + expect(sum).toBe(4); + }); +}); diff --git a/test/integration/messageRoutes.test.ts b/test/integration/messageRoutes.test.ts index 0f8bfbe..68aef19 100644 --- a/test/integration/messageRoutes.test.ts +++ b/test/integration/messageRoutes.test.ts @@ -1,101 +1,104 @@ -import { describe, it, expect, afterAll, beforeAll } from "bun:test" -import { messageRoutes } from "../../src/routes/messages" -import {RedisService} from "../../src/services/redisService.ts"; +import { describe, it, expect, afterAll, beforeAll } from "bun:test"; +import { messageRoutes } from "../../src/routes/messages"; +import { RedisService } from "../../src/services/redisService.ts"; interface GetMessagesResponse { - count: number; - messages: string[]; + count: number; + messages: string[]; } interface ErrorResponse { - error: string; + error: string; } describe("Message Routes โ€“ Integration Tests", () => { - beforeAll(async () => { - await RedisService.start(); - await RedisService.op().del("messages"); - }) - - afterAll(async () => { - await RedisService.stop(); - }) - - describe("Success cases", () => { - it("GET /messages โ†’ returns an empty list when no messages exist", async () => { - const req = new Request("http://localhost/messages") - const res = await messageRoutes.request("/", req) - - const data = await res.json() as GetMessagesResponse - expect(data.messages.length).toBe(0) - expect(data.count).toBe(0) - }) - - it("POST /messages โ†’ stores a new message successfully", async () => { - const req = new Request("http://localhost/messages", { - method: "POST", - body: JSON.stringify({ text: "hi" }) - }) - - const res = await messageRoutes.request("/", req) - expect(res.status).toBe(200) - }) - - it("GET /messages โ†’ returns the previously stored message", async () => { - const req = new Request("http://localhost/messages") - const res = await messageRoutes.request("/", req) - - const data = await res.json() as GetMessagesResponse - expect(data.messages.length).toBeGreaterThan(0) - expect(data.count).toBe(1) - }) - - it("POST /messages โ†’ returns only the first 10 messages after inserting 15", async () => { - for (let i = 0; i < 15; i++) { - const req = new Request("http://localhost/messages", { - method: "POST", - body: JSON.stringify({ text: `msg-${i}` }) - }) - - const res = await messageRoutes.request("/", req) - expect(res.status).toBe(200) - } - - const getReq = new Request("http://localhost/messages") - const getRes = await messageRoutes.request("/", getReq) - - const data = await getRes.json() as { count: number; messages: string[] } - - expect(getRes.status).toBe(200) - expect(data.messages.length).toBe(10) - expect(data.count).toBe(10) - }) - }) - - describe("Error cases", () => { - it("POST /messages โ†’ returns 400 when body is missing", async () => { - const req = new Request("http://localhost/messages", { - method: "POST", - }) - - const res = await messageRoutes.request("/", req) - const data = await res.json() as ErrorResponse - - expect(res.status).toBe(400) - expect(data.error).toEqual("Invalid body: expected { text: string }") - }) - - it("POST /messages โ†’ returns 400 when text is not a string", async () => { - const req = new Request("http://localhost/messages", { - method: "POST", - body: JSON.stringify({ text: 10 }) - }) - - const res = await messageRoutes.request("/", req) - const data = await res.json() as ErrorResponse - - expect(res.status).toBe(400) - expect(data.error).toEqual("Invalid body: expected { text: string }") - }) - }) -}) + beforeAll(async () => { + await RedisService.start(); + await RedisService.op().del("messages"); + }); + + afterAll(async () => { + await RedisService.stop(); + }); + + describe("Success cases", () => { + it("GET /messages โ†’ returns an empty list when no messages exist", async () => { + const req = new Request("http://localhost/messages"); + const res = await messageRoutes.request("/", req); + + const data = (await res.json()) as GetMessagesResponse; + expect(data.messages.length).toBe(0); + expect(data.count).toBe(0); + }); + + it("POST /messages โ†’ stores a new message successfully", async () => { + const req = new Request("http://localhost/messages", { + method: "POST", + body: JSON.stringify({ text: "hi" }), + }); + + const res = await messageRoutes.request("/", req); + expect(res.status).toBe(200); + }); + + it("GET /messages โ†’ returns the previously stored message", async () => { + const req = new Request("http://localhost/messages"); + const res = await messageRoutes.request("/", req); + + const data = (await res.json()) as GetMessagesResponse; + expect(data.messages.length).toBeGreaterThan(0); + expect(data.count).toBe(1); + }); + + it("POST /messages โ†’ returns only the first 10 messages after inserting 15", async () => { + for (let i = 0; i < 15; i++) { + const req = new Request("http://localhost/messages", { + method: "POST", + body: JSON.stringify({ text: `msg-${i}` }), + }); + + const res = await messageRoutes.request("/", req); + expect(res.status).toBe(200); + } + + const getReq = new Request("http://localhost/messages"); + const getRes = await messageRoutes.request("/", getReq); + + const data = (await getRes.json()) as { + count: number; + messages: string[]; + }; + + expect(getRes.status).toBe(200); + expect(data.messages.length).toBe(10); + expect(data.count).toBe(10); + }); + }); + + describe("Error cases", () => { + it("POST /messages โ†’ returns 400 when body is missing", async () => { + const req = new Request("http://localhost/messages", { + method: "POST", + }); + + const res = await messageRoutes.request("/", req); + const data = (await res.json()) as ErrorResponse; + + expect(res.status).toBe(400); + expect(data.error).toEqual("Invalid body: expected { text: string }"); + }); + + it("POST /messages โ†’ returns 400 when text is not a string", async () => { + const req = new Request("http://localhost/messages", { + method: "POST", + body: JSON.stringify({ text: 10 }), + }); + + const res = await messageRoutes.request("/", req); + const data = (await res.json()) as ErrorResponse; + + expect(res.status).toBe(400); + expect(data.error).toEqual("Invalid body: expected { text: string }"); + }); + }); +}); diff --git a/test/unit/redisService.test.ts b/test/unit/redisService.test.ts index 4957948..bcdc052 100644 --- a/test/unit/redisService.test.ts +++ b/test/unit/redisService.test.ts @@ -1,49 +1,51 @@ -import { describe, it, expect, mock, afterEach } from "bun:test" -import { RedisService } from "../../src/services/redisService" +import { describe, it, expect, mock, afterEach } from "bun:test"; +import { RedisService } from "../../src/services/redisService"; describe("RedisService - unit", () => { - afterEach(() => { - (RedisService as any).client = null - }) + afterEach(() => { + (RedisService as any).client = null; + }); - it("should call lPush on the client", async () => { - const fakeClient = { - lPush: mock(async () => 1) - } + it("should call lPush on the client", async () => { + const fakeClient = { + lPush: mock(async () => 1), + }; - ;(RedisService as any).client = fakeClient + (RedisService as any).client = fakeClient; - await RedisService.lpush("messages", "hello") + await RedisService.lpush("messages", "hello"); - expect(fakeClient.lPush).toHaveBeenCalled() - expect(fakeClient.lPush).toHaveBeenCalledWith("messages", "hello") - }) + expect(fakeClient.lPush).toHaveBeenCalled(); + expect(fakeClient.lPush).toHaveBeenCalledWith("messages", "hello"); + }); - it("should call lRange on the client", async () => { - const fakeClient = { - lRange: mock(async () => ["hi"]) - } + it("should call lRange on the client", async () => { + const fakeClient = { + lRange: mock(async () => ["hi"]), + }; - ;(RedisService as any).client = fakeClient + (RedisService as any).client = fakeClient; - const result = await RedisService.lrange("messages", 0, 1) + const result = await RedisService.lrange("messages", 0, 1); - expect(fakeClient.lRange).toHaveBeenCalled() - expect(fakeClient.lRange).toHaveBeenCalledWith("messages", 0, 1) - expect(result).toEqual(["hi"]) - }) + expect(fakeClient.lRange).toHaveBeenCalled(); + expect(fakeClient.lRange).toHaveBeenCalledWith("messages", 0, 1); + expect(result).toEqual(["hi"]); + }); - it("should return 15 elementos from 0 to 14", async () => { - const fakeClient = { - lRange: mock(async () => Array.from({ length: 15 }, (_, i) => `msg-${i}`)) - } + it("should return 15 elementos from 0 to 14", async () => { + const fakeClient = { + lRange: mock(async () => + Array.from({ length: 15 }, (_, i) => `msg-${i}`), + ), + }; - ;(RedisService as any).client = fakeClient + (RedisService as any).client = fakeClient; - const range = await RedisService.lrange("messages", 0, 14) + const range = await RedisService.lrange("messages", 0, 14); - expect(fakeClient.lRange).toHaveBeenCalled() - expect(fakeClient.lRange).toHaveBeenCalledWith("messages", 0, 14) - expect(range).toHaveLength(15) - }) -}) + expect(fakeClient.lRange).toHaveBeenCalled(); + expect(fakeClient.lRange).toHaveBeenCalledWith("messages", 0, 14); + expect(range).toHaveLength(15); + }); +});