import { describe, it, expect, beforeEach, vi } from 'vitest'; import { GameObjectsManager } from './GameObjectsManager.js'; // backend/app/bl/GameObjectsManager.test.js // --- Mocks --- vi.mock('fs', () => ({ default: { promises: { copyFile: vi.fn().mockResolvedValue(), unlink: vi.fn().mockResolvedValue(), } } })); vi.mock('path', () => ({ default: { extname: vi.fn((name) => { const m = name.match(/\.[^.]+$/); return m ? m[0] : ''; }) } })); vi.mock('decompress', () => ({ __esModule: true, default: vi.fn().mockResolvedValue([{ path: 'file.gltf' }]) })); vi.mock('sharp', () => { const sharpMock = vi.fn(() => ({ resize: vi.fn().mockReturnThis(), toFile: vi.fn().mockResolvedValue() })); sharpMock.cache = vi.fn(); return { __esModule: true, default: sharpMock }; }); vi.mock('node:util', () => ({ default: {}, promisify: (fn) => vi.fn().mockResolvedValue() })); vi.mock('child_process', () => ({ execFile: vi.fn() })); // --- Test helpers --- function createMockDb() { let store = {}; let lastId = 0; return { getId: vi.fn(async () => lastId), create: vi.fn(async (coll, obj) => { lastId++; obj.id = lastId; store[obj.id] = { ...obj }; }), get: vi.fn(async (coll, query) => store[query.id]), update: vi.fn(async (coll, query, obj) => { store[query.id] = { ...store[query.id], ...obj }; }), remove: vi.fn(async (coll, query) => { delete store[query.id]; }), list: vi.fn(async (coll, { query }) => Object.values(store).filter(o => !query || o.id === query.id)), _store: store }; } function createMockConfig() { return { fs: { repo: '/mockrepo' } }; } // --- Tests --- describe('GameObjectsManager', () => { let manager, app, db, config; beforeEach(() => { db = createMockDb(); config = createMockConfig(); app = { db, config }; manager = new GameObjectsManager(); manager.init(app); }); it('should create a game object and assign an ID', async () => { const ctx = {}; const data = { name: 'TestObj' }; const result = await manager.create(ctx, data); expect(result.id).toBe(1); expect(db.create).toHaveBeenCalled(); }); it('should call addFile and update if ctx.files.file is present', async () => { const ctx = { files: { file: { name: 'file.png', path: '/tmp/file.png' } } }; const data = { name: 'WithFile' }; const spy = vi.spyOn(manager, 'addFile').mockResolvedValue(); await manager.create(ctx, data); expect(spy).toHaveBeenCalled(); expect(db.update).toHaveBeenCalled(); }); it('should read a game object by ID', async () => { const ctx = {}; const data = { name: 'ReadObj' }; await manager.create(ctx, data); const obj = await manager.read(1); expect(obj.name).toBe('ReadObj'); }); it('should update a game object and call addFile/addThumb if files present', async () => { const ctx = {}; const data = { name: 'UpdateObj' }; await manager.create(ctx, data); const ctx2 = { files: { file: { name: 'file.png', path: '/tmp/file.png' }, thumb: { path: '/tmp/thumb.png' } } }; const spyFile = vi.spyOn(manager, 'addFile').mockResolvedValue(); const spyThumb = vi.spyOn(manager, 'addThumb').mockResolvedValue(); await manager.update(ctx2, { id: 1, name: 'Updated' }); expect(spyFile).toHaveBeenCalled(); expect(spyThumb).toHaveBeenCalled(); expect(db.update).toHaveBeenCalled(); }); it('should remove a game object by ID', async () => { const ctx = {}; const data = { name: 'RemoveObj' }; await manager.create(ctx, data); await manager.remove(1); expect(db.remove).toHaveBeenCalledWith('assets', { id: 1 }); }); it('should call sharp for image thumb in addThumb', async () => { const sharp = (await import('sharp')).default; const object = { asset: {} }; await manager.addThumb(object, '/tmp/thumb.png'); expect(sharp).toHaveBeenCalled(); expect(object.asset.thumb).toBeDefined(); }); it('should call execFile for video thumb in addThumb', async () => { const util = await import('node:util'); const object = { asset: {} }; await manager.addThumb(object, '/tmp/video.mp4'); expect(object.asset.thumb).toBeDefined(); }); it('should call decompress for .zip in addFile', async () => { const decompress = (await import('decompress')).default; const object = {}; const tmpFile = { name: 'archive.zip', path: '/tmp/archive.zip' }; await manager.addFile(object, tmpFile); expect(decompress).toHaveBeenCalled(); expect(object.asset.type).toBe('bundle'); }); it('should call addThumb for image/video in addFile', async () => { const spy = vi.spyOn(manager, 'addThumb').mockResolvedValue(); const object = {}; const tmpFile = { name: 'pic.png', path: '/tmp/pic.png' }; await manager.addFile(object, tmpFile); expect(spy).toHaveBeenCalled(); }); it('should list game objects', async () => { await manager.create({}, { name: 'Obj1' }); await manager.create({}, { name: 'Obj2' }); const list = await manager.list({}); expect(Array.isArray(list)).toBe(true); expect(list.length).toBe(2); }); });