/**
* Tests for src/dynamo/client.ts
*
* Covers the DynamoDB Document Client singleton:
* - Creation path (lines 14-21): when no client is cached, getDocClient()
* creates a DynamoDBClient + DynamoDBDocumentClient.from()
* - Caching: second call returns the same instance
* - Injection via setDocClient()
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
// ─── Mocks (must be declared before dynamic imports) ───
const mockDocClientInstance = { send: vi.fn(), __mock: 'docClient' };
const mockFrom = vi.fn().mockReturnValue(mockDocClientInstance);
const mockDynamoDBClient = vi.fn().mockImplementation(() => ({ __mock: 'rawClient' }));
vi.mock('@aws-sdk/client-dynamodb', () => ({
DynamoDBClient: mockDynamoDBClient,
}));
vi.mock('@aws-sdk/lib-dynamodb', () => ({
DynamoDBDocumentClient: {
from: mockFrom,
},
}));
// ─── Helpers ───
/**
* Dynamically import a fresh copy of the module so that
* the module-scoped `docClient` variable starts as `undefined`.
*/
async function freshImport() {
const mod = await import('../../src/dynamo/client.js');
return mod;
}
// ─── Tests ───
describe('dynamo/client', () => {
beforeEach(() => {
vi.resetModules();
mockFrom.mockClear();
mockDynamoDBClient.mockClear();
mockFrom.mockReturnValue(mockDocClientInstance);
});
describe('getDocClient() creation path', () => {
it('creates a DynamoDBDocumentClient when none is cached', async () => {
const { getDocClient } = await freshImport();
const client = getDocClient();
// DynamoDBClient constructor should have been called once
expect(mockDynamoDBClient).toHaveBeenCalledTimes(1);
expect(mockDynamoDBClient).toHaveBeenCalledWith({});
// DynamoDBDocumentClient.from should have been called with the raw client + options
expect(mockFrom).toHaveBeenCalledTimes(1);
expect(mockFrom).toHaveBeenCalledWith(
expect.objectContaining({ __mock: 'rawClient' }),
{
marshallOptions: {
removeUndefinedValues: true,
convertClassInstanceToMap: true,
},
},
);
expect(client).toBe(mockDocClientInstance);
});
it('returns the same cached instance on the second call', async () => {
const { getDocClient } = await freshImport();
const first = getDocClient();
const second = getDocClient();
// Constructor and .from should only have been called once (first call)
expect(mockDynamoDBClient).toHaveBeenCalledTimes(1);
expect(mockFrom).toHaveBeenCalledTimes(1);
expect(second).toBe(first);
});
});
describe('setDocClient()', () => {
it('sets the client so getDocClient() returns the injected instance', async () => {
const { getDocClient, setDocClient } = await freshImport();
const injected = { send: vi.fn(), __injected: true } as unknown as Parameters<typeof setDocClient>[0];
setDocClient(injected);
const result = getDocClient();
// Should NOT have created a new client — the injected one is returned
expect(mockDynamoDBClient).not.toHaveBeenCalled();
expect(mockFrom).not.toHaveBeenCalled();
expect(result).toBe(injected);
});
it('overrides a previously created client', async () => {
const { getDocClient, setDocClient } = await freshImport();
// First call creates the client
const created = getDocClient();
expect(mockFrom).toHaveBeenCalledTimes(1);
expect(created).toBe(mockDocClientInstance);
// Now inject a different client
const injected = { send: vi.fn(), __override: true } as unknown as Parameters<typeof setDocClient>[0];
setDocClient(injected);
const result = getDocClient();
// .from should not have been called again
expect(mockFrom).toHaveBeenCalledTimes(1);
expect(result).toBe(injected);
});
});
});