diff --git a/backend/src/routes/__tests__/blocks.test.ts b/backend/src/routes/__tests__/blocks.test.ts new file mode 100644 index 0000000..cdf1fc1 --- /dev/null +++ b/backend/src/routes/__tests__/blocks.test.ts @@ -0,0 +1,172 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { fastify } from 'fastify'; +import blocksRoutes from '../blocks'; +import { BlockService } from '../../services/blockService'; + +// Mock BlockService +vi.mock('../../services/blockService', () => ({ + BlockService: { + listBlocks: vi.fn(), + createBlock: vi.fn(), + updateBlock: vi.fn(), + deleteBlock: vi.fn(), + insertBlockInstance: vi.fn() + } +})); + +// Mock auth middleware +vi.mock('../middleware/auth', () => ({ + authenticate: vi.fn(async (request, reply, done) => done()), + authorizeProjectMember: vi.fn(async (request, reply, done) => done()), + authorizePlanner: vi.fn(async (request, reply, done) => done()) +})); + +const app = fastify(); +app.register(blocksRoutes, { prefix: '/projects/:projectId/blocks' }); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('GET /projects/:projectId/blocks', () => { + it('should return list of blocks', async () => { + const mockBlocks = [{ id: '1', name: 'Block 1' }]; + (BlockService.listBlocks as vi.Mock).mockResolvedValue(mockBlocks); + + const response = await app.inject({ + method: 'GET', + url: '/projects/123/blocks' + }); + + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.body)).toEqual(mockBlocks); + }); + + it('should handle errors', async () => { + (BlockService.listBlocks as vi.Mock).mockRejectedValue(new Error('Database error')); + + const response = await app.inject({ + method: 'GET', + url: '/projects/123/blocks' + }); + + expect(response.statusCode).toBe(500); + expect(JSON.parse(response.body)).toEqual({ error: 'Failed to list blocks' }); + }); +}); + +describe('POST /projects/:projectId/blocks', () => { + it('should create a new block', async () => { + const mockBlock = { id: '1', name: 'New Block' }; + (BlockService.createBlock as vi.Mock).mockResolvedValue(mockBlock); + + const response = await app.inject({ + method: 'POST', + url: '/projects/123/blocks', + payload: { + name: 'New Block', + elements: ['element1', 'element2'] + } + }); + + expect(response.statusCode).toBe(201); + expect(JSON.parse(response.body)).toEqual(mockBlock); + }); + + it('should handle validation errors', async () => { + const response = await app.inject({ + method: 'POST', + url: '/projects/123/blocks', + payload: { + // name is missing + elements: ['element1', 'element2'] + } + }); + + expect(response.statusCode).toBe(400); + }); +}); + +describe('PUT /projects/:projectId/blocks/:blockId', () => { + it('should update a block', async () => { + const mockBlock = { id: '1', name: 'Updated Block' }; + (BlockService.updateBlock as vi.Mock).mockResolvedValue(mockBlock); + + const response = await app.inject({ + method: 'PUT', + url: '/projects/123/blocks/1', + payload: { + name: 'Updated Block' + } + }); + + expect(response.statusCode).toBe(200); + expect(JSON.parse(response.body)).toEqual(mockBlock); + }); + + it('should handle validation errors', async () => { + const response = await app.inject({ + method: 'PUT', + url: '/projects/123/blocks/invalid-id', + payload: { + name: 'Updated Block' + } + }); + + expect(response.statusCode).toBe(400); + }); +}); + +describe('DELETE /projects/:projectId/blocks/:blockId', () => { + it('should delete a block', async () => { + (BlockService.deleteBlock as vi.Mock).mockResolvedValue(undefined); + + const response = await app.inject({ + method: 'DELETE', + url: '/projects/123/blocks/1' + }); + + expect(response.statusCode).toBe(204); + }); + + it('should handle validation errors', async () => { + const response = await app.inject({ + method: 'DELETE', + url: '/projects/123/blocks/invalid-id' + }); + + expect(response.statusCode).toBe(400); + }); +}); + +describe('POST /projects/:projectId/blocks/:blockId/insert', () => { + it('should insert a block instance', async () => { + const mockInstance = { id: '1', blockId: '1', position_x: 0, position_y: 0 }; + (BlockService.insertBlockInstance as vi.Mock).mockResolvedValue(mockInstance); + + const response = await app.inject({ + method: 'POST', + url: '/projects/123/blocks/1/insert', + payload: { + position_x: 0, + position_y: 0 + } + }); + + expect(response.statusCode).toBe(201); + expect(JSON.parse(response.body)).toEqual(mockInstance); + }); + + it('should handle validation errors', async () => { + const response = await app.inject({ + method: 'POST', + url: '/projects/123/blocks/1/insert', + payload: { + // position_x is missing + position_y: 0 + } + }); + + expect(response.statusCode).toBe(400); + }); +}); \ No newline at end of file