182 lines
4.9 KiB
TypeScript
182 lines
4.9 KiB
TypeScript
import { describe, test, expect, mock, beforeEach, afterEach } from "bun:test";
|
|
import { BunHttpClient, HttpClientError, TimeoutError } from "../src";
|
|
|
|
// Mock GlobalFetch to avoid making real network requests
|
|
const mockFetchSuccess = mock(() =>
|
|
Promise.resolve(new Response(
|
|
JSON.stringify({ result: "success" }),
|
|
{ status: 200, headers: { "content-type": "application/json" } }
|
|
))
|
|
);
|
|
|
|
const mockFetchFailure = mock(() =>
|
|
Promise.resolve(new Response(
|
|
JSON.stringify({ error: "Not found" }),
|
|
{ status: 404, headers: { "content-type": "application/json" } }
|
|
))
|
|
);
|
|
|
|
const mockFetchTimeout = mock(() => {
|
|
return new Promise((_, reject) => {
|
|
setTimeout(() => {
|
|
const error = new Error("Timeout");
|
|
error.name = "AbortError";
|
|
reject(error);
|
|
}, 10);
|
|
});
|
|
});
|
|
|
|
describe("BunHttpClient", () => {
|
|
let client: BunHttpClient;
|
|
const originalFetch = global.fetch;
|
|
|
|
beforeEach(() => {
|
|
// Create a fresh client for each test
|
|
client = new BunHttpClient({
|
|
baseURL: "https://api.example.com",
|
|
timeout: 1000,
|
|
retries: 1
|
|
});
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Cleanup after each test
|
|
await client.close();
|
|
global.fetch = originalFetch;
|
|
});
|
|
|
|
test("should make successful GET requests", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
const response = await client.get("/users");
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.data).toEqual({ result: "success" });
|
|
expect(mockFetchSuccess).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("should handle failed requests", async () => {
|
|
global.fetch = mockFetchFailure;
|
|
|
|
try {
|
|
await client.get("/missing");
|
|
expect("Should have thrown").toBe("But didn't");
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(HttpClientError);
|
|
expect(error.status).toBe(404);
|
|
}
|
|
|
|
expect(mockFetchFailure).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("should handle request timeouts", async () => {
|
|
global.fetch = mockFetchTimeout;
|
|
|
|
try {
|
|
await client.get("/slow");
|
|
expect("Should have thrown").toBe("But didn't");
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(TimeoutError);
|
|
}
|
|
});
|
|
|
|
test("should build full URLs properly", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
await client.get("/users/123");
|
|
expect(mockFetchSuccess).toHaveBeenCalledWith(
|
|
"https://api.example.com/users/123",
|
|
expect.objectContaining({
|
|
method: "GET"
|
|
})
|
|
);
|
|
});
|
|
|
|
test("should make POST requests with body", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
const data = { name: "John", email: "john@example.com" };
|
|
await client.post("/users", data);
|
|
|
|
expect(mockFetchSuccess).toHaveBeenCalledWith(
|
|
"https://api.example.com/users",
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify(data)
|
|
})
|
|
);
|
|
});
|
|
|
|
test("should provide convenience methods for all HTTP verbs", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
await client.get("/users");
|
|
await client.post("/users", { name: "Test" });
|
|
await client.put("/users/1", { name: "Updated" });
|
|
await client.patch("/users/1", { status: "active" });
|
|
await client.delete("/users/1");
|
|
await client.head("/users");
|
|
await client.options("/users");
|
|
|
|
expect(mockFetchSuccess).toHaveBeenCalledTimes(7);
|
|
});
|
|
|
|
test("should merge config options correctly", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
await client.get("/users", {
|
|
headers: { "X-Custom": "Value" },
|
|
timeout: 5000
|
|
});
|
|
|
|
expect(mockFetchSuccess).toHaveBeenCalledWith(
|
|
"https://api.example.com/users",
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({
|
|
"X-Custom": "Value"
|
|
})
|
|
})
|
|
);
|
|
});
|
|
|
|
test("should handle absolute URLs", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
await client.get("https://other-api.com/endpoint");
|
|
|
|
expect(mockFetchSuccess).toHaveBeenCalledWith(
|
|
"https://other-api.com/endpoint",
|
|
expect.anything()
|
|
);
|
|
});
|
|
|
|
test("should update configuration", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
client.setBaseURL("https://new-api.com");
|
|
client.setDefaultHeaders({ "Authorization": "Bearer token" });
|
|
client.setTimeout(2000);
|
|
|
|
await client.get("/resource");
|
|
|
|
expect(mockFetchSuccess).toHaveBeenCalledWith(
|
|
"https://new-api.com/resource",
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({
|
|
"Authorization": "Bearer token"
|
|
})
|
|
})
|
|
);
|
|
});
|
|
|
|
test("should get connection stats", async () => {
|
|
global.fetch = mockFetchSuccess;
|
|
|
|
await client.get("/users");
|
|
const stats = client.getStats();
|
|
|
|
expect(stats).toHaveProperty("successfulRequests", 1);
|
|
expect(stats).toHaveProperty("activeConnections");
|
|
expect(stats).toHaveProperty("averageResponseTime");
|
|
});
|
|
});
|