Skip to content

Commit bd2ba38

Browse files
committed
Add comprehensive tests for adapters, context, and middleware
Introduce unit tests for Cloudflare and Node adapters (cache, database, environment), context creation, logger, GitHub error handling, and middleware integration. Also includes a minor fix in node cache test to remove unnecessary await on `close()`.
1 parent cdb8b41 commit bd2ba38

File tree

11 files changed

+1765
-1
lines changed

11 files changed

+1765
-1
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2+
import { CloudflareKVAdapter } from "../../../../src/lib/adapters/cloudflare/cache";
3+
4+
describe("CloudflareKVAdapter", () => {
5+
let mockKV: KVNamespace;
6+
let adapter: CloudflareKVAdapter;
7+
8+
beforeEach(() => {
9+
mockKV = {
10+
get: vi.fn(),
11+
put: vi.fn(),
12+
delete: vi.fn()
13+
} as unknown as KVNamespace;
14+
15+
adapter = new CloudflareKVAdapter(mockKV);
16+
});
17+
18+
afterEach(() => {
19+
vi.clearAllMocks();
20+
});
21+
22+
describe("get", () => {
23+
it("should get value from KV store", async () => {
24+
mockKV.get = vi.fn().mockResolvedValue("test-value");
25+
26+
const result = await adapter.get("test-key");
27+
28+
expect(mockKV.get).toHaveBeenCalledWith("test-key");
29+
expect(result).toBe("test-value");
30+
});
31+
32+
it("should return null for non-existent keys", async () => {
33+
mockKV.get = vi.fn().mockResolvedValue(null);
34+
35+
const result = await adapter.get("nonexistent");
36+
37+
expect(result).toBeNull();
38+
});
39+
});
40+
41+
describe("put", () => {
42+
it("should put value to KV store", async () => {
43+
mockKV.put = vi.fn().mockResolvedValue(undefined);
44+
45+
await adapter.put("test-key", "test-value");
46+
47+
expect(mockKV.put).toHaveBeenCalledWith(
48+
"test-key",
49+
"test-value",
50+
undefined
51+
);
52+
});
53+
54+
it("should put value with expirationTtl option", async () => {
55+
mockKV.put = vi.fn().mockResolvedValue(undefined);
56+
57+
await adapter.put("test-key", "test-value", { expirationTtl: 3600 });
58+
59+
expect(mockKV.put).toHaveBeenCalledWith("test-key", "test-value", {
60+
expirationTtl: 3600
61+
});
62+
});
63+
64+
it("should put value with expiration option", async () => {
65+
mockKV.put = vi.fn().mockResolvedValue(undefined);
66+
67+
const expirationTime = Math.floor(Date.now() / 1000) + 3600;
68+
await adapter.put("test-key", "test-value", {
69+
expiration: expirationTime
70+
});
71+
72+
expect(mockKV.put).toHaveBeenCalledWith("test-key", "test-value", {
73+
expiration: expirationTime
74+
});
75+
});
76+
77+
it("should put value with both expiration options", async () => {
78+
mockKV.put = vi.fn().mockResolvedValue(undefined);
79+
80+
await adapter.put("test-key", "test-value", {
81+
expirationTtl: 3600,
82+
expiration: 1234567890
83+
});
84+
85+
expect(mockKV.put).toHaveBeenCalledWith("test-key", "test-value", {
86+
expirationTtl: 3600,
87+
expiration: 1234567890
88+
});
89+
});
90+
});
91+
92+
describe("delete", () => {
93+
it("should delete key from KV store", async () => {
94+
mockKV.delete = vi.fn().mockResolvedValue(undefined);
95+
96+
await adapter.delete("test-key");
97+
98+
expect(mockKV.delete).toHaveBeenCalledWith("test-key");
99+
});
100+
});
101+
102+
describe("Multiple Operations", () => {
103+
it("should handle sequence of get and put operations", async () => {
104+
mockKV.get = vi.fn().mockResolvedValue(null);
105+
mockKV.put = vi.fn().mockResolvedValue(undefined);
106+
107+
await adapter.put("key1", "value1");
108+
await adapter.put("key2", "value2");
109+
110+
mockKV.get = vi.fn().mockResolvedValue("value1");
111+
const result1 = await adapter.get("key1");
112+
113+
mockKV.get = vi.fn().mockResolvedValue("value2");
114+
const result2 = await adapter.get("key2");
115+
116+
expect(result1).toBe("value1");
117+
expect(result2).toBe("value2");
118+
});
119+
120+
it("should handle delete after put", async () => {
121+
mockKV.put = vi.fn().mockResolvedValue(undefined);
122+
mockKV.get = vi.fn().mockResolvedValue("test-value");
123+
mockKV.delete = vi.fn().mockResolvedValue(undefined);
124+
125+
await adapter.put("test-key", "test-value");
126+
await adapter.delete("test-key");
127+
128+
mockKV.get = vi.fn().mockResolvedValue(null);
129+
const result = await adapter.get("test-key");
130+
131+
expect(result).toBeNull();
132+
});
133+
});
134+
});
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { describe, it, expect, beforeEach, vi } from "vitest";
2+
import { CloudflareD1Adapter } from "../../../../src/lib/adapters/cloudflare/database";
3+
import { IPreparedStatement } from "../../../../src/lib/interfaces";
4+
5+
describe("CloudflareD1Adapter", () => {
6+
let mockD1: D1Database;
7+
let mockStatement: D1PreparedStatement;
8+
let adapter: CloudflareD1Adapter;
9+
10+
beforeEach(() => {
11+
mockStatement = {
12+
bind: vi.fn().mockReturnThis(),
13+
all: vi.fn().mockResolvedValue({ results: [], success: true }),
14+
first: vi.fn().mockResolvedValue(null),
15+
run: vi.fn().mockResolvedValue({ success: true, meta: {} })
16+
} as unknown as D1PreparedStatement;
17+
18+
mockD1 = {
19+
prepare: vi.fn().mockReturnValue(mockStatement),
20+
batch: vi.fn().mockResolvedValue([])
21+
} as unknown as D1Database;
22+
23+
adapter = new CloudflareD1Adapter(mockD1);
24+
});
25+
26+
describe("prepare", () => {
27+
it("should prepare a statement", () => {
28+
const result = adapter.prepare("SELECT * FROM table");
29+
30+
expect(mockD1.prepare).toHaveBeenCalledWith("SELECT * FROM table");
31+
expect(result).toBeDefined();
32+
expect(result.all).toBeDefined();
33+
expect(result.first).toBeDefined();
34+
expect(result.run).toBeDefined();
35+
});
36+
});
37+
38+
describe("bind", () => {
39+
it("should bind parameters to statement", async () => {
40+
const stmt = adapter.prepare("SELECT * FROM table WHERE id = ?");
41+
42+
stmt.bind(1, 2, 3);
43+
44+
expect(mockStatement.bind).toHaveBeenCalledWith(1, 2, 3);
45+
});
46+
47+
it("should return statement after binding", async () => {
48+
const stmt = adapter.prepare("SELECT * FROM table");
49+
const boundStmt = stmt.bind("param1");
50+
51+
expect(boundStmt).toBe(stmt);
52+
});
53+
});
54+
55+
describe("all", () => {
56+
it("should return all results", async () => {
57+
mockStatement.all = vi.fn().mockResolvedValue({
58+
results: [{ id: 1 }, { id: 2 }],
59+
success: true
60+
});
61+
62+
const stmt = adapter.prepare("SELECT * FROM table");
63+
const result = await stmt.all();
64+
65+
expect(result.results).toEqual([{ id: 1 }, { id: 2 }]);
66+
expect(result.success).toBe(true);
67+
});
68+
69+
it("should return empty results array when no results", async () => {
70+
mockStatement.all = vi.fn().mockResolvedValue({
71+
results: undefined,
72+
success: true
73+
});
74+
75+
const stmt = adapter.prepare("SELECT * FROM table");
76+
const result = await stmt.all();
77+
78+
expect(result.results).toBeUndefined();
79+
});
80+
});
81+
82+
describe("first", () => {
83+
it("should return first result", async () => {
84+
const mockData = { id: 1, name: "test" };
85+
mockStatement.first = vi.fn().mockResolvedValue(mockData);
86+
87+
const stmt = adapter.prepare("SELECT * FROM table LIMIT 1");
88+
const result = await stmt.first();
89+
90+
expect(result).toEqual(mockData);
91+
});
92+
93+
it("should return null when no results", async () => {
94+
mockStatement.first = vi.fn().mockResolvedValue(null);
95+
96+
const stmt = adapter.prepare("SELECT * FROM table LIMIT 1");
97+
const result = await stmt.first();
98+
99+
expect(result).toBeNull();
100+
});
101+
});
102+
103+
describe("run", () => {
104+
it("should execute statement and return result", async () => {
105+
mockStatement.run = vi.fn().mockResolvedValue({
106+
success: true,
107+
meta: { changes: 1, last_row_id: 123 }
108+
});
109+
110+
const stmt = adapter.prepare("INSERT INTO table (name) VALUES (?)");
111+
const result = await stmt.run();
112+
113+
expect(result.success).toBe(true);
114+
expect(result.meta?.changes).toBe(1);
115+
expect(result.meta?.last_row_id).toBe(123);
116+
});
117+
118+
it("should return success false on failure", async () => {
119+
const mockError = new Error("Database error");
120+
mockStatement.run = vi.fn().mockResolvedValue({
121+
success: false,
122+
error: mockError
123+
});
124+
125+
const stmt = adapter.prepare("INSERT INTO table (name) VALUES (?)");
126+
const result = await stmt.run();
127+
128+
expect(result.success).toBe(false);
129+
expect(result.error).toBeDefined();
130+
});
131+
132+
it("should return success false when error is string", async () => {
133+
mockStatement.run = vi.fn().mockResolvedValue({
134+
success: false,
135+
error: "SQL error"
136+
});
137+
138+
const stmt = adapter.prepare("INSERT INTO table (name) VALUES (?)");
139+
const result = await stmt.run();
140+
141+
expect(result.success).toBe(false);
142+
expect(result.error).toBe("SQL error");
143+
});
144+
});
145+
146+
describe("batch", () => {
147+
it("should execute multiple statements", async () => {
148+
const stmt1 = adapter.prepare("INSERT INTO table (name) VALUES (?)");
149+
const stmt2 = adapter.prepare("UPDATE table SET name = ? WHERE id = ?");
150+
151+
const results: unknown[] = [{ success: true }, { success: true }];
152+
mockD1.batch = vi.fn().mockResolvedValue(results);
153+
154+
const result = await adapter.batch([stmt1, stmt2]);
155+
156+
expect(mockD1.batch).toHaveBeenCalledWith([
157+
expect.objectContaining({}),
158+
expect.objectContaining({})
159+
]);
160+
expect(result).toEqual(results);
161+
});
162+
163+
it("should throw error when statement is not CloudflareD1Statement", async () => {
164+
const stmt1 = adapter.prepare("SELECT * FROM table");
165+
const invalidStmt = {
166+
bind: vi.fn(),
167+
all: vi.fn(),
168+
first: vi.fn(),
169+
run: vi.fn()
170+
} as IPreparedStatement;
171+
172+
await expect(adapter.batch([stmt1, invalidStmt])).rejects.toThrow(
173+
"Invalid statement type for D1 batch"
174+
);
175+
});
176+
177+
it("should handle empty batch array", async () => {
178+
const results: unknown[] = [];
179+
mockD1.batch = vi.fn().mockResolvedValue(results);
180+
181+
const result = await adapter.batch([]);
182+
183+
expect(mockD1.batch).toHaveBeenCalledWith([]);
184+
expect(result).toEqual([]);
185+
});
186+
});
187+
});

0 commit comments

Comments
 (0)