import { randomUUID } from 'node:crypto'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import type sinon from 'sinon'; import type { CodeTask, CodeTaskPreset } from '#shared/codeTask/codeTask.model'; import type { CodeTaskRepository } from './codeTaskRepository'; chai.use(chaiAsPromised); // Helper to create mock codeTask data const createMockCodeTask = (userId: string, overrides: Partial = {}): CodeTask => { const id = overrides.id ?? randomUUID(); const now = Date.now(); return { id: id, userId: userId, title: `Test CodeTask ${id.substring(0, 4)}`, instructions: 'Test instructions', repositorySource: 'local', repositoryId: '/test/repo', targetBranch: 'main', workingBranch: `codeTask/${id.substring(0, 8)}`, createWorkingBranch: true, useSharedRepos: false, status: 'initializing', createdAt: now, updatedAt: now, lastAgentActivity: now, error: null, ...overrides, // Apply overrides, including potential timestamp overrides }; }; // Helper to create mock preset data const createMockPreset = (userId: string, overrides: Partial = {}): CodeTaskPreset => { const id = overrides.id ?? randomUUID(); const now = Date.now(); return { id: id, userId: userId, name: `Test Preset ${id.substring(0, 4)}`, config: { repositorySource: 'github', repositoryFullPath: '12345', // Mock SCM project ID repositoryName: 'owner/repo', // Mock repository name targetBranch: 'develop', workingBranch: 'codeTask/feature-branch', createWorkingBranch: true, useSharedRepos: true, }, createdAt: now, updatedAt: now, ...overrides, }; }; export function runCodeTaskRepositoryTests( createRepository: () => CodeTaskRepository, beforeEachHook: () => Promise | void, afterEachHook: () => Promise | void, // Removed parameters for currentUserStub and user objects ): void { let repo: CodeTaskRepository; // Define fixed user IDs for testing consistency const testUserId = 'test-user-repo-tests'; const otherUserId = 'another-user-repo-tests'; // Must be different from testUserId const userWithNoItemsId = 'user-with-no-items'; // For empty list tests beforeEach(async () => { await beforeEachHook(); // Run implementation-specific setup (e.g., clear DB/memory) repo = createRepository(); }); afterEach(async () => { // sinon.restore(); // Removed: Stub restoration is handled by the calling test file's setup await afterEachHook(); // Run implementation-specific teardown }); describe('CodeTask CRUD', () => { it('should create a new codeTask and retrieve it', async () => { const codeTaskData = createMockCodeTask(testUserId); const codeTaskId = await repo.createCodeTask(codeTaskData); expect(codeTaskId).to.equal(codeTaskData.id); const retrievedCodeTask = await repo.getCodeTask(testUserId, codeTaskId); expect(retrievedCodeTask).to.not.be.null; // Compare essential fields, allow timestamps to differ slightly if generated by DB expect(retrievedCodeTask?.id).to.equal(codeTaskData.id); expect(retrievedCodeTask?.userId).to.equal(testUserId); expect(retrievedCodeTask?.title).to.equal(codeTaskData.title); expect(retrievedCodeTask?.status).to.equal('initializing'); expect(retrievedCodeTask?.createdAt).to.be.a('number'); }); it('should return null when retrieving a non-existent codeTask', async () => { const nonExistentId = randomUUID(); const retrievedCodeTask = await repo.getCodeTask(testUserId, nonExistentId); expect(retrievedCodeTask).to.be.null; }); it('should return null when retrieving a codeTask belonging to another user', async () => { const codeTaskData = createMockCodeTask(otherUserId); const codeTaskId = await repo.createCodeTask(codeTaskData); const retrievedCodeTask = await repo.getCodeTask(testUserId, codeTaskId); expect(retrievedCodeTask).to.be.null; }); it.skip('should list codeTasks for a user, ordered by updatedAt descending', async () => { const codeTask1 = createMockCodeTask(testUserId, { createdAt: Date.now() - 5000, updatedAt: Date.now() - 2000 }); const codeTask2 = createMockCodeTask(testUserId, { createdAt: Date.now() - 6000, updatedAt: Date.now() - 1000 }); // Newer update with older creation const codeTaskOtherUser = createMockCodeTask(otherUserId); await repo.createCodeTask(codeTask1); await repo.createCodeTask(codeTask2); await repo.createCodeTask(codeTaskOtherUser); const userCodeTasks = await repo.listCodeTasks(testUserId); expect(userCodeTasks).to.be.an('array').with.lengthOf(2); expect(userCodeTasks[0].id).to.equal(codeTask2.id); // Newest first expect(userCodeTasks[1].id).to.equal(codeTask1.id); }); it('should return an empty array when listing codeTasks for a user with no codeTasks', async () => { // No need to stub currentUser, just call list with the specific ID const userCodeTasks = await repo.listCodeTasks(userWithNoItemsId); expect(userCodeTasks).to.be.an('array').that.is.empty; }); it('should update an existing codeTask', async () => { const originalCodeTask = createMockCodeTask(testUserId); const codeTaskId = await repo.createCodeTask(originalCodeTask); const originalUpdateTimestamp = (await repo.getCodeTask(testUserId, codeTaskId))?.updatedAt; // Ensure timestamp can change await new Promise((resolve) => setTimeout(resolve, 5)); const updates: Partial = { title: 'Updated Title', status: 'design_review', error: 'An error occurred', }; await repo.updateCodeTask(testUserId, codeTaskId, updates); const updatedCodeTask = await repo.getCodeTask(testUserId, codeTaskId); expect(updatedCodeTask).to.not.be.null; expect(updatedCodeTask?.title).to.equal(updates.title); expect(updatedCodeTask?.status).to.equal(updates.status); expect(updatedCodeTask?.error).to.equal(updates.error); expect(updatedCodeTask?.instructions).to.equal(originalCodeTask.instructions); // Check unchanged field expect(updatedCodeTask?.updatedAt).to.be.a('number'); // Check if timestamp actually updated (might be equal in fast in-memory tests) if (originalUpdateTimestamp) { expect(updatedCodeTask?.updatedAt).to.be.greaterThanOrEqual(originalUpdateTimestamp); } }); it('should throw an error when updating a non-existent codeTask', async () => { const nonExistentId = randomUUID(); const updates = { title: 'Update Fail' }; await expect(repo.updateCodeTask(testUserId, nonExistentId, updates)).to.be.rejectedWith(/not found/i); }); it('should throw an error when updating a codeTask belonging to another user', async () => { const codeTaskData = createMockCodeTask(otherUserId); const codeTaskId = await repo.createCodeTask(codeTaskData); const updates = { title: 'Update Fail Other User' }; await expect(repo.updateCodeTask(testUserId, codeTaskId, updates)).to.be.rejectedWith(/not found|authorized/i); }); it('should delete an existing codeTask', async () => { const codeTaskData = createMockCodeTask(testUserId); const codeTaskId = await repo.createCodeTask(codeTaskData); let retrieved = await repo.getCodeTask(testUserId, codeTaskId); expect(retrieved).to.not.be.null; await repo.deleteCodeTask(testUserId, codeTaskId); retrieved = await repo.getCodeTask(testUserId, codeTaskId); expect(retrieved).to.be.null; }); it('should not throw an error when deleting a non-existent codeTask', async () => { const nonExistentId = randomUUID(); await expect(repo.deleteCodeTask(testUserId, nonExistentId)).to.not.be.rejected; }); it('should not delete a codeTask belonging to another user', async () => { const codeTaskData = createMockCodeTask(otherUserId); const codeTaskId = await repo.createCodeTask(codeTaskData); // Attempt delete as testUserId await repo.deleteCodeTask(testUserId, codeTaskId); // Verify codeTask still exists for otherUserId by calling getCodeTask with otherUserId const retrieved = await repo.getCodeTask(otherUserId, codeTaskId); expect(retrieved).to.not.be.null; }); }); describe('CodeTaskPreset CRUD', () => { it('should create a new preset and retrieve it', async () => { const presetData = createMockPreset(testUserId); const presetId = await repo.saveCodeTaskPreset(presetData); expect(presetId).to.equal(presetData.id); // Need to list and find, as there's no getPresetById const userPresets = await repo.listCodeTaskPresets(testUserId); const retrievedPreset = userPresets.find((p) => p.id === presetId); expect(retrievedPreset).to.not.be.undefined; expect(retrievedPreset?.id).to.equal(presetData.id); expect(retrievedPreset?.userId).to.equal(testUserId); expect(retrievedPreset?.name).to.equal(presetData.name); expect(retrievedPreset?.config).to.deep.equal(presetData.config); expect(retrievedPreset?.createdAt).to.be.a('number'); }); it('should list presets for a user, ordered by createdAt descending', async () => { const preset1 = createMockPreset(testUserId, { createdAt: Date.now() - 2000 }); const preset2 = createMockPreset(testUserId, { createdAt: Date.now() - 1000 }); // Newer const presetOtherUser = createMockPreset(otherUserId); await repo.saveCodeTaskPreset(preset1); await repo.saveCodeTaskPreset(preset2); // Save otherUser's preset directly, userId is in the object await repo.saveCodeTaskPreset(presetOtherUser); // No need to switch context, just list for testUserId const userPresets = await repo.listCodeTaskPresets(testUserId); expect(userPresets).to.be.an('array').with.lengthOf(2); expect(userPresets[0].id).to.equal(preset2.id); // Newest first expect(userPresets[1].id).to.equal(preset1.id); }); it('should return an empty array when listing presets for a user with no presets', async () => { // No need to stub currentUser, just call list with the specific ID const userPresets = await repo.listCodeTaskPresets(userWithNoItemsId); expect(userPresets).to.be.an('array').that.is.empty; }); it('should delete an existing preset', async () => { const presetData = createMockPreset(testUserId); const presetId = await repo.saveCodeTaskPreset(presetData); let userPresets = await repo.listCodeTaskPresets(testUserId); expect(userPresets.some((p) => p.id === presetId)).to.be.true; await repo.deleteCodeTaskPreset(testUserId, presetId); userPresets = await repo.listCodeTaskPresets(testUserId); expect(userPresets.some((p) => p.id === presetId)).to.be.false; }); it('should not throw an error when deleting a non-existent preset', async () => { const nonExistentId = randomUUID(); await expect(repo.deleteCodeTaskPreset(testUserId, nonExistentId)).to.not.be.rejected; }); it('should not delete a preset belonging to another user', async () => { const presetData = createMockPreset(otherUserId); // Save otherUser's preset directly const presetId = await repo.saveCodeTaskPreset(presetData); // No need to switch context for delete attempt // Attempt delete as testUserId await repo.deleteCodeTaskPreset(testUserId, presetId); // Verify preset still exists for otherUserId by listing their presets const otherUserPresets = await repo.listCodeTaskPresets(otherUserId); expect(otherUserPresets.some((p) => p.id === presetId)).to.be.true; }); }); }