Skip to content

Commit b90c078

Browse files
committed
refactor(testing): centralize mock-db utilities in common package
- Add TestableDb interface to common/types/contracts/database.ts for type-safe database mocking - Move mock-db.ts from web/src/testing to common/src/testing - Add createSelectOnlyMockDb for version-utils query patterns - Add createMockDb, createMockDbWithErrors for API route testing - Update API routes to use TestableDb interface instead of CodebuffPgDatabase - Refactor test files to use shared mock-db utilities without type assertions - All 1,647+ tests passing across packages
1 parent 18ea77a commit b90c078

File tree

10 files changed

+594
-350
lines changed

10 files changed

+594
-350
lines changed

common/src/testing/mock-db.ts

Lines changed: 481 additions & 0 deletions
Large diffs are not rendered by default.

common/src/types/contracts/database.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,49 @@ export type AddAgentStepFn = (params: {
9292
}) => Promise<string | null>
9393

9494
export type DatabaseAgentCache = Map<string, AgentTemplate | null>
95+
96+
// ============================================================================
97+
// Testable Database Interface
98+
// ============================================================================
99+
100+
/* eslint-disable @typescript-eslint/no-explicit-any */
101+
/**
102+
* Minimal database interface for dependency injection in API routes.
103+
* Both the real CodebuffPgDatabase and test mocks can satisfy this interface.
104+
*
105+
* This allows tests to provide mock implementations without type casting.
106+
* Uses `any` for table/column parameters to be compatible with Drizzle ORM's
107+
* specific table types while remaining flexible for mocks.
108+
*/
109+
export interface TestableDb {
110+
insert: (table: any) => {
111+
values: (data: any) => PromiseLike<any>
112+
}
113+
update: (table: any) => {
114+
set: (data: any) => {
115+
where: (condition: any) => PromiseLike<any>
116+
}
117+
}
118+
select: (columns?: any) => {
119+
from: (table: any) => {
120+
where: (condition: any) => TestableDbWhereResult
121+
}
122+
}
123+
}
124+
125+
/**
126+
* Result type for where() that supports multiple query patterns:
127+
* - .limit(n) for simple queries
128+
* - .orderBy(...).limit(n) for sorted queries
129+
* - .then() for promise-like resolution
130+
*/
131+
export interface TestableDbWhereResult {
132+
then: <TResult = any[]>(
133+
onfulfilled?: ((value: any[]) => TResult | PromiseLike<TResult>) | null | undefined,
134+
) => PromiseLike<TResult>
135+
limit: (n: number) => PromiseLike<any[]>
136+
orderBy: (...columns: any[]) => {
137+
limit: (n: number) => PromiseLike<any[]>
138+
}
139+
}
140+
/* eslint-enable @typescript-eslint/no-explicit-any */

packages/internal/src/utils/__tests__/version-utils.test.ts

Lines changed: 12 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { describe, expect, it, afterEach, mock } from 'bun:test'
22

3-
import * as versionUtils from '../version-utils'
3+
import { createSelectOnlyMockDb } from '@codebuff/common/testing/mock-db'
44

5-
import type { CodebuffPgDatabase } from '../../db/types'
5+
import * as versionUtils from '../version-utils'
66

77
const {
88
versionOne,
@@ -124,18 +124,7 @@ describe('version-utils', () => {
124124

125125
describe('getLatestAgentVersion', () => {
126126
it('should return version 0.0.0 when no agent exists', async () => {
127-
// Mock the database to return empty result
128-
const mockDb = {
129-
select: mock(() => ({
130-
from: mock(() => ({
131-
where: mock(() => ({
132-
orderBy: mock(() => ({
133-
limit: mock(() => Promise.resolve([])),
134-
})),
135-
})),
136-
})),
137-
})),
138-
} as unknown as CodebuffPgDatabase
127+
const mockDb = createSelectOnlyMockDb([])
139128

140129
const result = await getLatestAgentVersion({
141130
agentId: 'test-agent',
@@ -146,20 +135,7 @@ describe('version-utils', () => {
146135
})
147136

148137
it('should return latest version when agent exists', async () => {
149-
// Mock the database to return a version
150-
const mockDb = {
151-
select: mock(() => ({
152-
from: mock(() => ({
153-
where: mock(() => ({
154-
orderBy: mock(() => ({
155-
limit: mock(() =>
156-
Promise.resolve([{ major: 1, minor: 2, patch: 3 }]),
157-
),
158-
})),
159-
})),
160-
})),
161-
})),
162-
} as unknown as CodebuffPgDatabase
138+
const mockDb = createSelectOnlyMockDb([{ major: 1, minor: 2, patch: 3 }])
163139

164140
const result = await getLatestAgentVersion({
165141
agentId: 'test-agent',
@@ -170,20 +146,7 @@ describe('version-utils', () => {
170146
})
171147

172148
it('should handle null values in database response', async () => {
173-
// Mock the database to return null values
174-
const mockDb = {
175-
select: mock(() => ({
176-
from: mock(() => ({
177-
where: mock(() => ({
178-
orderBy: mock(() => ({
179-
limit: mock(() =>
180-
Promise.resolve([{ major: null, minor: null, patch: null }]),
181-
),
182-
})),
183-
})),
184-
})),
185-
})),
186-
} as unknown as CodebuffPgDatabase
149+
const mockDb = createSelectOnlyMockDb([{ major: null, minor: null, patch: null }])
187150

188151
const result = await getLatestAgentVersion({
189152
agentId: 'test-agent',
@@ -196,19 +159,7 @@ describe('version-utils', () => {
196159

197160
describe('determineNextVersion', () => {
198161
it('should increment patch of latest version when no version provided', async () => {
199-
const mockDb = {
200-
select: mock(() => ({
201-
from: mock(() => ({
202-
where: mock(() => ({
203-
orderBy: mock(() => ({
204-
limit: mock(() =>
205-
Promise.resolve([{ major: 1, minor: 2, patch: 3 }]),
206-
),
207-
})),
208-
})),
209-
})),
210-
})),
211-
} as unknown as CodebuffPgDatabase
162+
const mockDb = createSelectOnlyMockDb([{ major: 1, minor: 2, patch: 3 }])
212163

213164
const result = await determineNextVersion({
214165
agentId: 'test-agent',
@@ -219,17 +170,7 @@ describe('version-utils', () => {
219170
})
220171

221172
it('should use provided version when higher than latest', async () => {
222-
const mockDb = {
223-
select: mock(() => ({
224-
from: mock(() => ({
225-
where: mock(() => ({
226-
orderBy: mock(() => ({
227-
limit: mock(() => Promise.resolve([])),
228-
})),
229-
})),
230-
})),
231-
})),
232-
} as unknown as CodebuffPgDatabase
173+
const mockDb = createSelectOnlyMockDb([])
233174

234175
const result = await determineNextVersion({
235176
agentId: 'test-agent',
@@ -241,19 +182,7 @@ describe('version-utils', () => {
241182
})
242183

243184
it('should throw error when provided version is not greater than latest', async () => {
244-
const mockDb = {
245-
select: mock(() => ({
246-
from: mock(() => ({
247-
where: mock(() => ({
248-
orderBy: mock(() => ({
249-
limit: mock(() =>
250-
Promise.resolve([{ major: 2, minor: 0, patch: 0 }]),
251-
),
252-
})),
253-
})),
254-
})),
255-
})),
256-
} as unknown as CodebuffPgDatabase
185+
const mockDb = createSelectOnlyMockDb([{ major: 2, minor: 0, patch: 0 }])
257186

258187
await expect(
259188
determineNextVersion({
@@ -268,19 +197,7 @@ describe('version-utils', () => {
268197
})
269198

270199
it('should throw error when provided version equals latest', async () => {
271-
const mockDb = {
272-
select: mock(() => ({
273-
from: mock(() => ({
274-
where: mock(() => ({
275-
orderBy: mock(() => ({
276-
limit: mock(() =>
277-
Promise.resolve([{ major: 1, minor: 5, patch: 0 }]),
278-
),
279-
})),
280-
})),
281-
})),
282-
})),
283-
} as unknown as CodebuffPgDatabase
200+
const mockDb = createSelectOnlyMockDb([{ major: 1, minor: 5, patch: 0 }])
284201

285202
await expect(
286203
determineNextVersion({
@@ -295,17 +212,7 @@ describe('version-utils', () => {
295212
})
296213

297214
it('should throw error for invalid provided version', async () => {
298-
const mockDb = {
299-
select: mock(() => ({
300-
from: mock(() => ({
301-
where: mock(() => ({
302-
orderBy: mock(() => ({
303-
limit: mock(() => Promise.resolve([])),
304-
})),
305-
})),
306-
})),
307-
})),
308-
} as unknown as CodebuffPgDatabase
215+
const mockDb = createSelectOnlyMockDb([])
309216

310217
await expect(
311218
determineNextVersion({
@@ -322,14 +229,7 @@ describe('version-utils', () => {
322229

323230
describe('versionExists', () => {
324231
it('should return true when version exists', async () => {
325-
// Mock the database to return a result
326-
const mockDb = {
327-
select: mock(() => ({
328-
from: mock(() => ({
329-
where: mock(() => Promise.resolve([{ id: 'test-agent' }])),
330-
})),
331-
})),
332-
} as unknown as CodebuffPgDatabase
232+
const mockDb = createSelectOnlyMockDb([{ id: 'test-agent' }])
333233

334234
const result = await versionExists({
335235
agentId: 'test-agent',
@@ -341,14 +241,7 @@ describe('version-utils', () => {
341241
})
342242

343243
it('should return false when version does not exist', async () => {
344-
// Mock the database to return empty result
345-
const mockDb = {
346-
select: mock(() => ({
347-
from: mock(() => ({
348-
where: mock(() => Promise.resolve([])),
349-
})),
350-
})),
351-
} as unknown as CodebuffPgDatabase
244+
const mockDb = createSelectOnlyMockDb([])
352245

353246
const result = await versionExists({
354247
agentId: 'test-agent',

packages/internal/src/utils/version-utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { and, desc, eq } from 'drizzle-orm'
22

33
import * as schema from '@codebuff/internal/db/schema'
44

5-
import type { CodebuffPgDatabase } from '../db/types'
5+
import type { TestableDb } from '@codebuff/common/types/contracts/database'
66

77
export type Version = { major: number; minor: number; patch: number }
88

@@ -54,7 +54,7 @@ export function isGreater(v1: Version, v2: Version): boolean {
5454
export async function getLatestAgentVersion(params: {
5555
agentId: string
5656
publisherId: string
57-
db: CodebuffPgDatabase
57+
db: TestableDb
5858
}): Promise<Version> {
5959
const { agentId, publisherId, db } = params
6060

@@ -96,7 +96,7 @@ export async function determineNextVersion(params: {
9696
agentId: string
9797
publisherId: string
9898
providedVersion?: string
99-
db: CodebuffPgDatabase
99+
db: TestableDb
100100
}): Promise<Version> {
101101
const { agentId, publisherId, providedVersion, db } = params
102102

@@ -137,7 +137,7 @@ export async function versionExists(params: {
137137
agentId: string
138138
version: Version
139139
publisherId: string
140-
db: CodebuffPgDatabase
140+
db: TestableDb
141141
}): Promise<boolean> {
142142
const { agentId, version, publisherId, db } = params
143143

web/src/app/api/agents/[publisherId]/[agentId]/[version]/dependencies/__tests__/dependencies.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
createMockDbSelect,
77
createMockLogger,
88
mockDbSchema,
9-
} from '@/testing/mock-db'
9+
} from '@codebuff/common/testing/mock-db'
1010

1111
// Mock the db module
1212
const mockDbSelect = mock(() => ({}))

0 commit comments

Comments
 (0)