import { jest } from '@jest/globals'; // Mock src/database/utils.js (where generateId and convertObjectIdStringsInFilter live) jest.unstable_mockModule('../utils.js', () => ({ generateId: jest.fn(() => () => 'test-id'), convertObjectIdStringsInFilter: jest.fn((filter) => filter), })); // Mock src/utils.js (where most database.js helpers live) jest.unstable_mockModule('../../utils.js', () => ({ deleteAuditLog: jest.fn(), deleteNotification: jest.fn(), distributeDelete: jest.fn(), editAuditLog: jest.fn(), editNotification: jest.fn(), expandObjectIds: jest.fn((obj) => obj), flatternObjectIds: jest.fn((object) => { if (!object || typeof object !== 'object') { return object; } const result = {}; for (const [key, value] of Object.entries(object)) { if (value && typeof value === 'object' && value._id) { result[key] = value._id; } else { result[key] = value; } } return result; }), getFieldsByRef: jest.fn(() => []), collectIdsAtPath: jest.fn(() => []), getQueryToCacheKey: jest.fn(({ model, id }) => `${model}:${id}`), modelHasRef: jest.fn(() => false), newAuditLog: jest.fn(), distributeNew: jest.fn(), distributeUpdate: jest.fn(), distributeChildUpdate: jest.fn(), distributeChildDelete: jest.fn(), distributeChildNew: jest.fn(), distributeStats: jest.fn(), })); jest.unstable_mockModule('../redis.js', () => ({ redisServer: { getKey: jest.fn(), setKey: jest.fn(), deleteKey: jest.fn(), getKeysByPattern: jest.fn(() => []), // Return empty array to avoid iterable error }, })); jest.unstable_mockModule('../../services/misc/model.js', () => ({ getAllModels: jest.fn(() => []), getModelByName: jest.fn(() => null), })); jest.unstable_mockModule('../schemas/management/auditlog.schema.js', () => ({ auditLogModel: { find: jest.fn(), create: jest.fn(), }, })); // Mock fileModel specifically as it's imported by database.js jest.unstable_mockModule('../schemas/management/file.schema.js', () => ({ fileModel: { findById: jest.fn(), }, })); // Now import the database utilities const { listObjects, getObject, newObject, editObject, deleteObject } = await import('../database.js'); const { editAuditLog, editNotification } = await import('../../utils.js'); describe('Database Utilities (CRUD)', () => { let mockModel; beforeEach(() => { mockModel = { modelName: 'TestModel', find: jest.fn().mockReturnThis(), findById: jest.fn().mockReturnThis(), findByIdAndUpdate: jest.fn().mockReturnThis(), findByIdAndDelete: jest.fn().mockReturnThis(), create: jest.fn(), sort: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), limit: jest.fn().mockReturnThis(), populate: jest.fn().mockReturnThis(), select: jest.fn().mockReturnThis(), lean: jest.fn().mockReturnThis(), exec: jest.fn(), }; jest.clearAllMocks(); }); describe('listObjects', () => { it('should return a list of objects', async () => { const mockData = [{ _id: '1', name: 'Test' }]; mockModel.lean.mockResolvedValue(mockData); const result = await listObjects({ model: mockModel }); expect(mockModel.find).toHaveBeenCalled(); expect(result).toEqual(mockData); }); it('should handle pagination', async () => { await listObjects({ model: mockModel, page: 2, limit: 10 }); expect(mockModel.skip).toHaveBeenCalledWith(10); expect(mockModel.limit).toHaveBeenCalledWith(10); }); }); describe('getObject', () => { it('should return a single object by ID', async () => { const mockData = { _id: '123', name: 'Test' }; mockModel.lean.mockResolvedValue(mockData); const result = await getObject({ model: mockModel, id: '123' }); expect(mockModel.findById).toHaveBeenCalledWith('123'); expect(result).toEqual(mockData); }); it('should return 404 if object not found', async () => { mockModel.lean.mockResolvedValue(null); const result = await getObject({ model: mockModel, id: '123' }); expect(result).toEqual({ error: 'Object not found.', code: 404 }); }); }); describe('newObject', () => { it('should create a new object', async () => { const newData = { name: 'New' }; const createdData = { _id: '456', ...newData }; mockModel.create.mockResolvedValue({ toObject: () => createdData, _id: '456', }); const result = await newObject({ model: mockModel, newData }); expect(mockModel.create).toHaveBeenCalledWith(newData); expect(result).toEqual(createdData); }); }); describe('editObject', () => { it('should update an existing object', async () => { const id = '123'; const updateData = { name: 'Updated' }; const previousData = { _id: id, name: 'Old' }; mockModel.lean.mockResolvedValue(previousData); const result = await editObject({ model: mockModel, id, updateData }); expect(mockModel.findByIdAndUpdate).toHaveBeenCalledWith(id, updateData); expect(result).toEqual({ ...previousData, ...updateData }); }); it('should flatten object id references before updating', async () => { const id = '123'; const fileId = '507f1f77bcf86cd799439012'; const updateData = { file: { _id: fileId, name: 'part.gcode' } }; const previousData = { _id: id, name: 'Old' }; mockModel.lean.mockResolvedValue(previousData); await editObject({ model: mockModel, id, updateData }); expect(mockModel.findByIdAndUpdate).toHaveBeenCalledWith(id, { file: fileId }); }); it('creates an audit log but no user notification for system edits', async () => { mockModel.lean.mockResolvedValue({ _id: '123', name: 'Old' }); await editObject({ model: mockModel, id: '123', updateData: { name: 'Updated' }, user: 'system', }); expect(editAuditLog).toHaveBeenCalledWith( expect.any(Object), expect.any(Object), '123', 'TestModel', 'system' ); expect(editNotification).not.toHaveBeenCalled(); }); }); describe('deleteObject', () => { it('should delete an object', async () => { const id = '123'; const mockData = { _id: id, name: 'To be deleted' }; mockModel.findByIdAndDelete.mockResolvedValue({ toObject: () => mockData, }); const result = await deleteObject({ model: mockModel, id }); expect(mockModel.findByIdAndDelete).toHaveBeenCalledWith(id); expect(result).toEqual({ deleted: true, object: mockData }); }); }); });