Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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
```
Expand All @@ -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.
More test cases, utilities, and scenarios will be added over time.
19 changes: 17 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
import { startServer } from "./src/server";
import { HttpServer } from "./src/http/server.ts";
import { RedisService } from "./src/services/redisService.ts";

await startServer();
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();
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -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;
export default app;
22 changes: 22 additions & 0 deletions src/http/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { serve } from "bun";
import app from "../app.ts";

export class HttpServer {
private static server: ReturnType<typeof serve> | 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");
}
}
54 changes: 27 additions & 27 deletions src/routes/messages.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { Hono } from 'hono'
import { redis } from '../services/redisService'
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 redis.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 redis.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);
}
});
28 changes: 0 additions & 28 deletions src/server.ts

This file was deleted.

64 changes: 27 additions & 37 deletions src/services/redisService.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,34 @@
import { createClient } from 'redis'
import type { RedisLike } from "../types/redisTypes.ts";
import { createClient } from "redis";

export class RedisService {
constructor(private client: RedisLike) {}

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;
}
private static client: ReturnType<typeof createClient> | 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 stop() {
if (this.client?.isOpen) {
await this.client.quit();
console.log("\x1b[31m 🔴 Redis stopped \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 op() {
if (!this.client) {
throw new Error("RedisService not initialized");
}
}

export const redisClient = createClient({
url: 'redis://localhost:6379' // TODO: Podría pasarse esto a un .env
})
return this.client;
}

redisClient.on('error', (err) => {
console.error('[Redis] Connection error:', err);
});
static lpush(key: string, value: string) {
return this.client!.lPush(key, value);
}

await redisClient.connect();

console.log('[Redis] Client connected successfully.');

export const redis = new RedisService(redisClient);
static lrange(key: string, start: number, stop: number) {
return this.client!.lRange(key, start, stop);
}
}
4 changes: 2 additions & 2 deletions src/types/redisTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface RedisLike {
lPush(key: string, value: string): Promise<number>
lRange(key: string, start: number, stop: number): Promise<string[]>
lPush(key: string, value: string): Promise<number>;
lRange(key: string, start: number, stop: number): Promise<string[]>;
}
12 changes: 6 additions & 6 deletions test/example.test.ts
Original file line number Diff line number Diff line change
@@ -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);
})
})
test("Simple sum", async () => {
const sum = 2 + 2;
expect(sum).toBe(4);
});
});
Loading