qmemory
Version:
A comprehensive production-ready Node.js utility library with MongoDB document operations, user ownership enforcement, Express.js HTTP utilities, environment-aware logging, and in-memory storage. Features 96%+ test coverage with comprehensive error handli
507 lines (415 loc) • 16.9 kB
JavaScript
/**
* Enhanced Database Utilities Test Suite
* Comprehensive testing for advanced database operation helpers
*
* This test suite validates the enhanced database utilities including:
* - MongoDB error handling and classification
* - Safe database operation wrappers with timing
* - Retry logic with exponential backoff
* - Idempotency checking for critical operations
* - Query optimization patterns
* - Aggregation pipeline builders
*/
const {
ensureMongoDB,
ensureUnique,
handleMongoError,
safeDbOperation,
retryDbOperation,
ensureIdempotency,
optimizeQuery,
createAggregationPipeline
} = require('../../lib/database-utils');
describe('Enhanced Database Utilities', () => {
describe('handleMongoError', () => {
it('should handle duplicate key errors correctly', () => {
const error = {
code: 11000,
message: 'Duplicate key error',
keyValue: { email: 'test@example.com' }
};
const result = handleMongoError(error, 'createUser', { userId: '123' });
expect(result.type).toBe('DUPLICATE_KEY_ERROR');
expect(result.message).toBe('Resource already exists');
expect(result.recoverable).toBe(true);
expect(result.statusCode).toBe(409);
expect(result.details).toEqual({ email: 'test@example.com' });
});
it('should handle validation errors correctly', () => {
const error = {
name: 'ValidationError',
message: 'Validation failed',
errors: {
email: { message: 'Email is required' },
name: { message: 'Name is required' }
}
};
const result = handleMongoError(error, 'createUser', { userId: '123' });
expect(result.type).toBe('VALIDATION_ERROR');
expect(result.message).toBe('Data validation failed');
expect(result.recoverable).toBe(true);
expect(result.statusCode).toBe(400);
expect(result.details).toEqual(error.errors);
});
it('should handle connection errors correctly', () => {
const error = {
name: 'MongoNetworkError',
message: 'Connection failed',
code: null
};
const result = handleMongoError(error, 'findUser', { userId: '123' });
expect(result.type).toBe('CONNECTION_ERROR');
expect(result.message).toBe('Database connection failed');
expect(result.recoverable).toBe(false);
expect(result.statusCode).toBe(503);
});
it('should handle timeout errors correctly', () => {
const error = {
name: 'MongoServerSelectionError',
message: 'Server selection timeout',
code: null
};
const result = handleMongoError(error, 'findUser', { userId: '123' });
expect(result.type).toBe('TIMEOUT_ERROR');
expect(result.message).toBe('Database operation timed out');
expect(result.recoverable).toBe(true);
expect(result.statusCode).toBe(504);
});
it('should handle unknown errors with fallback', () => {
const error = {
name: 'UnknownError',
message: 'Something went wrong',
code: 999
};
const result = handleMongoError(error, 'updateUser', { userId: '123' });
expect(result.type).toBe('UNKNOWN_ERROR');
expect(result.message).toBe('Database operation failed');
expect(result.recoverable).toBe(false);
expect(result.statusCode).toBe(500);
});
});
describe('safeDbOperation', () => {
it('should execute successful operations and return timing', async () => {
const mockOperation = jest.fn().mockResolvedValue({ id: '123', name: 'Test User' });
const result = await safeDbOperation(mockOperation, 'createUser', { userId: '123' });
expect(result.success).toBe(true);
expect(result.data).toEqual({ id: '123', name: 'Test User' });
expect(typeof result.processingTime).toBe('number');
expect(result.processingTime).toBeGreaterThanOrEqual(0);
expect(mockOperation).toHaveBeenCalledTimes(1);
});
it('should handle operation failures and return error info', async () => {
const mockError = {
code: 11000,
message: 'Duplicate key error',
keyValue: { email: 'test@example.com' }
};
const mockOperation = jest.fn().mockRejectedValue(mockError);
const result = await safeDbOperation(mockOperation, 'createUser', { userId: '123' });
expect(result.success).toBe(false);
expect(result.error.type).toBe('DUPLICATE_KEY_ERROR');
expect(result.error.recoverable).toBe(true);
expect(typeof result.processingTime).toBe('number');
expect(mockOperation).toHaveBeenCalledTimes(1);
});
it('should include context in error logging', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const mockError = new Error('Test error');
const mockOperation = jest.fn().mockRejectedValue(mockError);
await safeDbOperation(mockOperation, 'testOperation', { userId: '123', action: 'test' });
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Database operation failed: testOperation'),
expect.objectContaining({
context: { userId: '123', action: 'test' }
})
);
consoleSpy.mockRestore();
});
});
describe('retryDbOperation', () => {
it('should succeed on first attempt without retry', async () => {
const mockOperation = jest.fn().mockResolvedValue({ id: '123' });
const result = await retryDbOperation(mockOperation, 'findUser', {
maxRetries: 3,
context: { userId: '123' }
});
expect(result.success).toBe(true);
expect(result.data).toEqual({ id: '123' });
expect(mockOperation).toHaveBeenCalledTimes(1);
});
it('should retry recoverable errors with exponential backoff', async () => {
const mockError = {
name: 'MongoServerSelectionError',
message: 'Server selection timeout'
};
const mockOperation = jest.fn()
.mockRejectedValueOnce(mockError)
.mockRejectedValueOnce(mockError)
.mockResolvedValueOnce({ id: '123' });
// Mock setTimeout to avoid actual delays in tests
const originalSetTimeout = global.setTimeout;
global.setTimeout = jest.fn((cb) => cb());
const result = await retryDbOperation(mockOperation, 'findUser', {
maxRetries: 3,
baseDelay: 100
});
expect(result.success).toBe(true);
expect(result.data).toEqual({ id: '123' });
expect(mockOperation).toHaveBeenCalledTimes(3);
global.setTimeout = originalSetTimeout;
});
it('should not retry non-recoverable errors', async () => {
const mockError = {
name: 'ValidationError',
message: 'Validation failed',
errors: { email: 'Invalid email' }
};
const mockOperation = jest.fn().mockRejectedValue(mockError);
const result = await retryDbOperation(mockOperation, 'createUser', {
maxRetries: 3,
retryCondition: (error) => error.type === 'CONNECTION_ERROR' // Only retry connection errors
});
expect(result.success).toBe(false);
expect(result.error.type).toBe('VALIDATION_ERROR');
expect(mockOperation).toHaveBeenCalledTimes(1); // No retries
});
it('should stop retrying after max attempts', async () => {
const mockError = {
name: 'MongoServerSelectionError',
message: 'Server selection timeout'
};
const mockOperation = jest.fn().mockRejectedValue(mockError);
// Mock setTimeout to avoid actual delays in tests
const originalSetTimeout = global.setTimeout;
global.setTimeout = jest.fn((cb) => cb());
const result = await retryDbOperation(mockOperation, 'findUser', {
maxRetries: 2,
baseDelay: 100
});
expect(result.success).toBe(false);
expect(result.error.type).toBe('TIMEOUT_ERROR');
expect(mockOperation).toHaveBeenCalledTimes(3); // Initial + 2 retries
global.setTimeout = originalSetTimeout;
});
});
describe('ensureIdempotency', () => {
let mockModel;
beforeEach(() => {
mockModel = {
modelName: 'User',
findOne: jest.fn()
};
});
it('should return existing record if found (idempotent)', async () => {
const existingRecord = { _id: '123', email: 'test@example.com', name: 'Test User' };
mockModel.findOne.mockResolvedValue(existingRecord);
const mockOperation = jest.fn();
const result = await ensureIdempotency(
mockModel,
{ field: 'email', value: 'test@example.com' },
mockOperation,
'createUser'
);
expect(result.success).toBe(true);
expect(result.data).toEqual(existingRecord);
expect(result.idempotent).toBe(true);
expect(mockOperation).not.toHaveBeenCalled();
expect(mockModel.findOne).toHaveBeenCalledWith({ email: 'test@example.com' });
});
it('should execute operation if no existing record found', async () => {
mockModel.findOne.mockResolvedValue(null);
const newRecord = { _id: '456', email: 'new@example.com', name: 'New User' };
const mockOperation = jest.fn().mockResolvedValue(newRecord);
const result = await ensureIdempotency(
mockModel,
{ field: 'email', value: 'new@example.com' },
mockOperation,
'createUser'
);
expect(result.success).toBe(true);
expect(result.data).toEqual(newRecord);
expect(result.idempotent).toBeUndefined();
expect(mockOperation).toHaveBeenCalledTimes(1);
});
it('should handle race conditions gracefully', async () => {
// First call returns null (no existing record)
// Second call (after race condition) returns the record created by another process
const raceRecord = { _id: '789', email: 'race@example.com', name: 'Race User' };
mockModel.findOne
.mockResolvedValueOnce(null)
.mockResolvedValueOnce(raceRecord);
const duplicateError = {
code: 11000,
message: 'Duplicate key error',
keyValue: { email: 'race@example.com' }
};
const mockOperation = jest.fn().mockRejectedValue(duplicateError);
const result = await ensureIdempotency(
mockModel,
{ field: 'email', value: 'race@example.com' },
mockOperation,
'createUser'
);
expect(result.success).toBe(true);
expect(result.data).toEqual(raceRecord);
expect(result.idempotent).toBe(true);
expect(result.raceCondition).toBe(true);
expect(mockModel.findOne).toHaveBeenCalledTimes(2);
});
it('should propagate non-duplicate errors', async () => {
mockModel.findOne.mockResolvedValue(null);
const validationError = {
name: 'ValidationError',
message: 'Validation failed',
errors: { name: 'Name is required' }
};
const mockOperation = jest.fn().mockRejectedValue(validationError);
const result = await ensureIdempotency(
mockModel,
{ field: 'email', value: 'invalid@example.com' },
mockOperation,
'createUser'
);
expect(result.success).toBe(false);
expect(result.error.type).toBe('VALIDATION_ERROR');
});
});
describe('optimizeQuery', () => {
let mockQuery;
beforeEach(() => {
mockQuery = {
lean: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
sort: jest.fn().mockReturnThis(),
populate: jest.fn().mockReturnThis(),
hint: jest.fn().mockReturnThis()
};
});
it('should apply lean optimization by default', () => {
const result = optimizeQuery(mockQuery);
expect(mockQuery.lean).toHaveBeenCalledTimes(1);
expect(result).toBe(mockQuery);
});
it('should apply all optimization options', () => {
const options = {
lean: true,
select: 'name email',
limit: 10,
skip: 20,
sort: { createdAt: -1 },
populate: 'author',
hint: { email: 1 }
};
optimizeQuery(mockQuery, options);
expect(mockQuery.lean).toHaveBeenCalledTimes(1);
expect(mockQuery.select).toHaveBeenCalledWith('name email');
expect(mockQuery.limit).toHaveBeenCalledWith(10);
expect(mockQuery.skip).toHaveBeenCalledWith(20);
expect(mockQuery.sort).toHaveBeenCalledWith({ createdAt: -1 });
expect(mockQuery.populate).toHaveBeenCalledWith('author');
expect(mockQuery.hint).toHaveBeenCalledWith({ email: 1 });
});
it('should skip null/undefined options', () => {
const options = {
lean: false,
select: null,
limit: null,
skip: 5, // Should be applied since it's truthy
sort: undefined,
populate: 'author', // Should be applied since it's truthy
hint: null
};
optimizeQuery(mockQuery, options);
expect(mockQuery.lean).not.toHaveBeenCalled();
expect(mockQuery.select).not.toHaveBeenCalled();
expect(mockQuery.limit).not.toHaveBeenCalled();
expect(mockQuery.skip).toHaveBeenCalledWith(5);
expect(mockQuery.sort).not.toHaveBeenCalled();
expect(mockQuery.populate).toHaveBeenCalledWith('author');
expect(mockQuery.hint).not.toHaveBeenCalled();
});
});
describe('createAggregationPipeline', () => {
it('should create empty pipeline for no stages', () => {
const result = createAggregationPipeline([]);
expect(result).toEqual([]);
});
it('should build pipeline with all stage types', () => {
const stages = [
{ match: { status: 'active' } },
{ group: { _id: '$category', total: { $sum: 1 } } },
{ sort: { total: -1 } },
{ limit: 10 },
{ project: { category: '$_id', count: '$total' } },
{ lookup: { from: 'categories', localField: '_id', foreignField: '_id', as: 'details' } },
{ unwind: '$details' }
];
const result = createAggregationPipeline(stages);
expect(result).toEqual([
{ $match: { status: 'active' } },
{ $group: { _id: '$category', total: { $sum: 1 } } },
{ $sort: { total: -1 } },
{ $limit: 10 },
{ $project: { category: '$_id', count: '$total' } },
{ $lookup: { from: 'categories', localField: '_id', foreignField: '_id', as: 'details' } },
{ $unwind: '$details' }
]);
});
it('should handle stages with multiple operations', () => {
const stages = [
{
match: { status: 'active' },
group: { _id: '$type', count: { $sum: 1 } }
}
];
const result = createAggregationPipeline(stages);
expect(result).toEqual([
{ $match: { status: 'active' } },
{ $group: { _id: '$type', count: { $sum: 1 } } }
]);
});
it('should ignore unknown stage types', () => {
const stages = [
{ match: { status: 'active' } },
{ unknownStage: { field: 'value' } },
{ sort: { createdAt: -1 } }
];
const result = createAggregationPipeline(stages);
expect(result).toEqual([
{ $match: { status: 'active' } },
{ $sort: { createdAt: -1 } }
]);
});
});
// Integration tests for existing utilities to ensure backward compatibility
describe('Existing utilities (backward compatibility)', () => {
describe('ensureMongoDB', () => {
it('should be available and callable', () => {
expect(typeof ensureMongoDB).toBe('function');
const mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
// Should not throw when called
expect(() => ensureMongoDB(mockRes)).not.toThrow();
});
});
describe('ensureUnique', () => {
it('should be available and callable', async () => {
expect(typeof ensureUnique).toBe('function');
const mockModel = {
findOne: jest.fn().mockResolvedValue(null)
};
const mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
const result = await ensureUnique(mockModel, { email: 'test@example.com' }, mockRes, 'User already exists');
expect(result).toBe(true);
expect(mockModel.findOne).toHaveBeenCalledWith({ email: 'test@example.com' });
});
});
});
});