test(adapter): comprehensive HomegrownAdapter unit tests#69
test(adapter): comprehensive HomegrownAdapter unit tests#69Jesssullivan merged 1 commit intomainfrom
Conversation
Mock Drizzle DB layer to test service resolution, provider lookup, client find-or-create, reservation lifecycle, booking cancellation, and Effect error wrapping. Covers 28 cases (up from 3 superficial constructor checks). Availability math delegation to availability-engine.ts is already covered by its own 39 tests.
Greptile SummaryThis PR replaces 3 superficial constructor checks with 28 behavioral unit tests for
Confidence Score: 5/5Test-only PR; all findings are P2 suggestions and do not block merge. No production code is changed. The 28 new tests are correctly structured, the mock chain faithfully mirrors the Drizzle fluent API, and all assertions align with the implementation. The only gaps (missing booking-method tests, unvalidated UUID branch, unused import) are style/coverage suggestions that don't indicate correctness problems in the tests that do exist. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[createMockDb] --> B[chainTerminals\nlimit · orderBy · returning]
A --> C[chain\nfrom · where · values · set]
C --> D[from returns\nwhere · orderBy · limit]
D -->|.where| B
subgraph getServices
E[select · from · where · orderBy] --> B
end
subgraph getService / getProvider / getClientByEmail
F[select · from · where · limit] --> B
end
subgraph findOrCreateClient
G[select · where · limit] --> B
H[insert · values · returning] --> B
I[update · set · where] -->|resolves undefined| J[void]
end
subgraph cancelBooking / releaseReservation
K[update · set · where] -->|resolves undefined| J
end
subgraph NOT TESTED
L[createBooking\nfindOrCreateClient + resolveService + insert]
M[getBooking\n4x select joins]
N[rescheduleBooking\nselect + update + getBooking]
end
Reviews (1): Last reviewed commit: "test(adapter): comprehensive HomegrownAd..." | Re-trigger Greptile |
| const mockGetDb = async () => { | ||
| throw new Error('No DB in unit tests'); | ||
| // --------------------------------------------------------------------------- | ||
| // Mock schemas — minimal shape matching what Drizzle ORM tables expose |
There was a problem hiding this comment.
| // ------------------------------------------------------------------------- | ||
|
|
||
| describe('createReservation', () => { | ||
| it('inserts a reservation and returns SlotReservation', async () => { | ||
| const mockDb = createMockDb({ insert: [RESERVATION_ROW] }); | ||
| const adapter = createHomegrownAdapter({ getDb: async () => mockDb }); | ||
|
|
||
| const result = await Effect.runPromise(adapter.createReservation({ | ||
| serviceId: 'svc-uuid-1', | ||
| providerId: 'prac-uuid-1', | ||
| datetime: '2026-04-20T14:00:00.000Z', | ||
| duration: 60, | ||
| expirationMinutes: 10, | ||
| })); | ||
|
|
||
| expect(result).toEqual({ | ||
| id: 'res-uuid-1', | ||
| datetime: '2026-04-20T14:00:00.000Z', | ||
| duration: 60, | ||
| expiresAt: '2026-04-20T14:10:00.000Z', | ||
| providerId: 'prac-uuid-1', | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Complex booking methods have no unit tests
createBooking, createBookingWithPaymentRef, getBooking, and rescheduleBooking — the four most structurally complex methods in the adapter — are absent from this suite. createBooking is particularly notable: it nests a live Effect.runPromise(adapter.findOrCreateClient(...)) call inside fromAsync, making it the only method that can surface an unexpected error from a nested Effect if the composition assumption ever changes. getBooking performs four separate select calls and reconstructs the full Booking shape including a conditional practitioner lookup.
Worth adding these tests to complete the scope, even with the same fluent-chain mock pattern used elsewhere.
| name: 'Deep Tissue Massage', | ||
| description: '60-minute therapeutic session', | ||
| duration: 60, | ||
| price: 9500, | ||
| currency: 'USD', | ||
| category: 'therapeutic', | ||
| active: true, | ||
| }]); | ||
| }); | ||
|
|
||
| it('returns empty array when no active services exist', async () => { | ||
| const mockDb = createMockDb(); | ||
| mockDb._terminals.orderBy.mockResolvedValue([]); | ||
|
|
||
| const adapter = createHomegrownAdapter({ getDb: async () => mockDb }); | ||
| const result = await Effect.runPromise(adapter.getServices()); | ||
|
|
||
| expect(result).toEqual([]); | ||
| }); | ||
|
|
||
| it('maps null description to undefined', async () => { |
There was a problem hiding this comment.
UUID routing condition not verified
getService with a full UUID and with a numeric acuityId take different code paths through the isUuid branch in resolveService — a UUID triggers or(eq(id,...), eq(acuityId,...)) while a non-UUID uses only eq(acuityId,...). Because the mock ignores the where argument, both tests resolve identically from the canned row and the branch logic is never exercised. Inspecting the where spy's call argument would let you assert that the UUID path actually builds the compound or(...) condition.
Summary
Coverage areas
Availability math (getAvailableDates, getAvailableSlots, checkSlotAvailability) delegates to availability-engine.ts which already has 39 dedicated tests.
Test plan
Tracker: TIN-104 (package quality)