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
409 lines (324 loc) • 13.7 kB
JavaScript
/**
* Document Helpers Test Suite
* Comprehensive testing for generic MongoDB CRUD operations
*
* This test suite validates the document helper utilities including:
* - Safe document retrieval with error handling
* - Document updates with consistent return patterns
* - Document deletion with boolean returns
* - Cascading deletion with cleanup operations
* - Document creation with validation handling
* - Document queries with condition filtering
* - Bulk operations with individual error handling
*/
// Mock the database utilities module BEFORE importing document helpers
const mockSafeDbOperation = jest.fn();
jest.mock('../../lib/database-utils', () => ({
safeDbOperation: mockSafeDbOperation,
handleMongoError: jest.fn()
}));
const {
findDocumentById,
updateDocumentById,
deleteDocumentById,
cascadeDeleteDocument,
createDocument,
findDocuments,
findOneDocument,
bulkUpdateDocuments
} = require('../../lib/document-helpers');
describe('Document Helpers', () => {
let mockModel;
let consoleSpy;
beforeEach(() => {
// Reset all mocks before each test
jest.clearAllMocks();
mockSafeDbOperation.mockClear();
// Mock console methods to verify logging
consoleSpy = {
log: jest.spyOn(console, 'log').mockImplementation(() => {}),
warn: jest.spyOn(console, 'warn').mockImplementation(() => {})
};
// Create mock Mongoose model
mockModel = {
modelName: 'TestModel',
findById: jest.fn(),
findByIdAndUpdate: jest.fn(),
findByIdAndDelete: jest.fn(),
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn()
};
});
afterEach(() => {
// Restore console methods
consoleSpy.log.mockRestore();
consoleSpy.warn.mockRestore();
});
describe('findDocumentById', () => {
it('should return document when found successfully', async () => {
const mockDocument = { _id: '123', name: 'Test Document' };
mockSafeDbOperation.mockResolvedValue({ success: true, data: mockDocument });
const result = await findDocumentById(mockModel, '123');
expect(result).toEqual(mockDocument);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'findDocumentById',
{ model: 'TestModel', id: '123' }
);
expect(consoleSpy.log).toHaveBeenCalledWith(
'findDocumentById is running with TestModel model and 123 id'
);
});
it('should return undefined when document not found', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: undefined });
const result = await findDocumentById(mockModel, '123');
expect(result).toBeUndefined();
});
it('should return undefined when operation fails', async () => {
mockSafeDbOperation.mockResolvedValue({
success: false,
error: { type: 'VALIDATION_ERROR', message: 'Invalid ID' }
});
const result = await findDocumentById(mockModel, 'invalid-id');
expect(result).toBeUndefined();
});
});
describe('updateDocumentById', () => {
it('should return updated document when successful', async () => {
const mockUpdatedDoc = { _id: '123', name: 'Updated Document' };
mockSafeDbOperation.mockResolvedValue({ success: true, data: mockUpdatedDoc });
const updates = { name: 'Updated Document' };
const result = await updateDocumentById(mockModel, '123', updates);
expect(result).toEqual(mockUpdatedDoc);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'updateDocumentById',
{ model: 'TestModel', id: '123', updateFields: ['name'] }
);
});
it('should return undefined when document not found', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: undefined });
const result = await updateDocumentById(mockModel, '123', { name: 'Test' });
expect(result).toBeUndefined();
});
it('should return undefined when operation fails', async () => {
mockSafeDbOperation.mockResolvedValue({
success: false,
error: { type: 'VALIDATION_ERROR', message: 'Update failed' }
});
const result = await updateDocumentById(mockModel, '123', { name: 'Test' });
expect(result).toBeUndefined();
});
});
describe('deleteDocumentById', () => {
it('should return true when document deleted successfully', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: true });
const result = await deleteDocumentById(mockModel, '123');
expect(result).toBe(true);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'deleteDocumentById',
{ model: 'TestModel', id: '123' }
);
});
it('should return false when document not found', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: false });
const result = await deleteDocumentById(mockModel, '123');
expect(result).toBe(false);
});
it('should return false when operation fails', async () => {
mockSafeDbOperation.mockResolvedValue({
success: false,
error: { type: 'CONNECTION_ERROR', message: 'Database error' }
});
const result = await deleteDocumentById(mockModel, '123');
expect(result).toBe(false);
});
});
describe('cascadeDeleteDocument', () => {
it('should execute cascade operations and delete main document', async () => {
const cascadeOp1 = jest.fn().mockResolvedValue(true);
const cascadeOp2 = jest.fn().mockResolvedValue(true);
const cascadeOperations = [cascadeOp1, cascadeOp2];
mockSafeDbOperation.mockResolvedValue({ success: true, data: true });
const result = await cascadeDeleteDocument(mockModel, '123', cascadeOperations);
expect(result).toBe(true);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'cascadeDeleteDocument',
{ model: 'TestModel', id: '123', cascadeCount: 2 }
);
});
it('should continue with main deletion even if cascade operations fail', async () => {
const cascadeOp1 = jest.fn().mockRejectedValue(new Error('Cascade failed'));
const cascadeOp2 = jest.fn().mockResolvedValue(true);
const cascadeOperations = [cascadeOp1, cascadeOp2];
mockSafeDbOperation.mockImplementation(async (operation) => {
const result = await operation();
return { success: true, data: result };
});
mockModel.findByIdAndDelete.mockResolvedValue({ _id: '123' });
const result = await cascadeDeleteDocument(mockModel, '123', cascadeOperations);
expect(result).toBe(true);
expect(cascadeOp1).toHaveBeenCalled();
expect(cascadeOp2).toHaveBeenCalled();
});
it('should work with empty cascade operations array', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: true });
const result = await cascadeDeleteDocument(mockModel, '123', []);
expect(result).toBe(true);
});
});
describe('createDocument', () => {
it('should return created document when successful', async () => {
const mockCreatedDoc = { _id: '123', name: 'New Document' };
mockSafeDbOperation.mockResolvedValue({ success: true, data: mockCreatedDoc });
const data = { name: 'New Document' };
const result = await createDocument(mockModel, data);
expect(result).toEqual(mockCreatedDoc);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'createDocument',
{ model: 'TestModel', dataFields: ['name'] }
);
});
it('should throw error when creation fails', async () => {
const errorResponse = {
success: false,
error: {
type: 'VALIDATION_ERROR',
message: 'Validation failed',
statusCode: 400
}
};
mockSafeDbOperation.mockResolvedValue(errorResponse);
const data = { name: '' }; // Invalid data
await expect(createDocument(mockModel, data)).rejects.toThrow('Validation failed');
});
});
describe('findDocuments', () => {
it('should return array of documents when successful', async () => {
const mockDocuments = [
{ _id: '1', name: 'Doc 1' },
{ _id: '2', name: 'Doc 2' }
];
mockSafeDbOperation.mockResolvedValue({ success: true, data: mockDocuments });
const condition = { status: 'active' };
const result = await findDocuments(mockModel, condition);
expect(result).toEqual(mockDocuments);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'findDocuments',
{ model: 'TestModel', condition, hasSort: false }
);
});
it('should handle sorting options', async () => {
const mockDocuments = [{ _id: '1', name: 'Doc 1' }];
mockSafeDbOperation.mockResolvedValue({ success: true, data: mockDocuments });
const condition = { status: 'active' };
const sortOptions = { name: 1 };
const result = await findDocuments(mockModel, condition, sortOptions);
expect(result).toEqual(mockDocuments);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'findDocuments',
{ model: 'TestModel', condition, hasSort: true }
);
});
it('should return empty array when operation fails', async () => {
mockSafeDbOperation.mockResolvedValue({
success: false,
error: { type: 'CONNECTION_ERROR', message: 'Database error' }
});
const result = await findDocuments(mockModel, { status: 'active' });
expect(result).toEqual([]);
});
});
describe('findOneDocument', () => {
it('should return document when found successfully', async () => {
const mockDocument = { _id: '123', name: 'Test Document' };
mockSafeDbOperation.mockResolvedValue({ success: true, data: mockDocument });
const condition = { name: 'Test Document' };
const result = await findOneDocument(mockModel, condition);
expect(result).toEqual(mockDocument);
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'findOneDocument',
{ model: 'TestModel', condition }
);
});
it('should return undefined when document not found', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: undefined });
const result = await findOneDocument(mockModel, { name: 'Nonexistent' });
expect(result).toBeUndefined();
});
it('should return undefined when operation fails', async () => {
mockSafeDbOperation.mockResolvedValue({
success: false,
error: { type: 'CONNECTION_ERROR', message: 'Database error' }
});
const result = await findOneDocument(mockModel, { name: 'Test' });
expect(result).toBeUndefined();
});
});
describe('bulkUpdateDocuments', () => {
it('should return count of successful updates', async () => {
// Mock successful updates
mockSafeDbOperation
.mockResolvedValueOnce({ success: true, data: true })
.mockResolvedValueOnce({ success: true, data: true })
.mockResolvedValueOnce({ success: false, data: false });
const updates = [
{ id: '1', data: { name: 'Updated 1' } },
{ id: '2', data: { name: 'Updated 2' } },
{ id: '3', data: { name: 'Updated 3' } }
];
const result = await bulkUpdateDocuments(mockModel, updates);
expect(result).toBe(2); // 2 successful updates
expect(mockSafeDbOperation).toHaveBeenCalledTimes(3);
});
it('should handle empty updates array', async () => {
const result = await bulkUpdateDocuments(mockModel, []);
expect(result).toBe(0);
expect(mockSafeDbOperation).not.toHaveBeenCalled();
});
it('should continue processing even when individual updates fail', async () => {
// Mix of successful and failed updates
mockSafeDbOperation
.mockResolvedValueOnce({ success: false, error: { message: 'Error 1' } })
.mockResolvedValueOnce({ success: true, data: true })
.mockResolvedValueOnce({ success: false, error: { message: 'Error 2' } });
const updates = [
{ id: '1', data: { name: 'Update 1' } },
{ id: '2', data: { name: 'Update 2' } },
{ id: '3', data: { name: 'Update 3' } }
];
const result = await bulkUpdateDocuments(mockModel, updates);
expect(result).toBe(1); // Only 1 successful update
expect(mockSafeDbOperation).toHaveBeenCalledTimes(3);
});
});
describe('Integration with safeDbOperation', () => {
it('should pass correct parameters to safeDbOperation', async () => {
mockSafeDbOperation.mockResolvedValue({ success: true, data: { _id: '123' } });
await findDocumentById(mockModel, '123');
expect(mockSafeDbOperation).toHaveBeenCalledWith(
expect.any(Function),
'findDocumentById',
{ model: 'TestModel', id: '123' }
);
});
it('should handle safeDbOperation errors gracefully', async () => {
mockSafeDbOperation.mockRejectedValue(new Error('Unexpected error'));
// Should not throw, but handle gracefully
try {
const result = await findDocumentById(mockModel, '123');
expect(result).toBeUndefined();
} catch (error) {
// Document helpers should handle errors gracefully and not throw
expect(error).toBeUndefined();
}
});
});
});