import { AppController } from './app.controller'; import { ProxyService } from './proxy.service'; describe('AppController', () => { const mockProxy = { forward: jest.fn(), forwardWithFile: jest.fn(), } as unknown as ProxyService; let controller: AppController; const req = { headers: { authorization: 'Bearer jwt-token' }, user: { id: 1 } }; beforeEach(() => { jest.clearAllMocks(); controller = new AppController(mockProxy); }); describe('auth routes', () => { it('forwards login to the auth service', () => { const body = { email: 'a@test.com', password: 'x' }; controller.login(body); expect(mockProxy.forward).toHaveBeenCalledWith('auth', '/auth/login', 'POST', body); }); it('returns the request user for verify', () => { expect(controller.verify(req)).toBe(req.user); }); }); describe('user routes', () => { it('forwards getUsers with the auth header', () => { controller.getUsers(req); expect(mockProxy.forward).toHaveBeenCalledWith('auth', '/users', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards createUser with body and auth header', () => { const body = { email: 'new@test.com' }; controller.createUser(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('auth', '/users', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards updateUser with id, body and auth header', () => { const body = { name: 'New' }; controller.updateUser('5', body, req); expect(mockProxy.forward).toHaveBeenCalledWith('auth', '/users/5', 'PUT', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards deleteUser with id and auth header', () => { controller.deleteUser('5', req); expect(mockProxy.forward).toHaveBeenCalledWith('auth', '/users/5', 'DELETE', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('post routes', () => { it('forwards getPosts with querystring params', () => { controller.getPosts({ category: 'tech' }); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts?category=tech', 'GET'); }); it('forwards getFeatured', () => { controller.getFeatured(); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/featured', 'GET'); }); it('forwards getRecent', () => { controller.getRecent(); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/recent', 'GET'); }); it('forwards getStats with auth header', () => { controller.getStats(req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/stats', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards getAdminPosts with query and auth header', () => { controller.getAdminPosts({ status: 'pending' }, req); expect(mockProxy.forward).toHaveBeenCalledWith( 'blog', '/posts/admin?status=pending', 'GET', null, { Authorization: 'Bearer jwt-token' }, ); }); it('forwards getPost by slug', () => { controller.getPost('hello-world'); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/hello-world', 'GET'); }); it('forwards createPost with body and auth header', () => { const body = { title: 'New Post' }; controller.createPost(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards approvePost', () => { controller.approvePost('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/1/approve', 'PATCH', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards rejectPost', () => { controller.rejectPost('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/1/reject', 'PATCH', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards updatePost', () => { const body = { title: 'Updated' }; controller.updatePost('1', body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/1', 'PUT', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards deletePost', () => { controller.deletePost('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/posts/1', 'DELETE', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('category routes', () => { it('forwards getCategories', () => { controller.getCategories(); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/categories', 'GET'); }); it('forwards createCategory', () => { const body = { name: 'Tech' }; controller.createCategory(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/categories', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards updateCategory', () => { const body = { name: 'Updated' }; controller.updateCategory('1', body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/categories/1', 'PUT', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards deleteCategory', () => { controller.deleteCategory('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/categories/1', 'DELETE', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('tag routes', () => { it('forwards getTags', () => { controller.getTags(); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/tags', 'GET'); }); it('forwards createTag', () => { const body = { name: 'react' }; controller.createTag(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/tags', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards createManyTags', () => { const body = { names: ['a', 'b'] }; controller.createManyTags(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/tags/many', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); }); describe('comment routes', () => { it('forwards getComments', () => { controller.getComments(req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/comments', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards getPostComments', () => { controller.getPostComments('5'); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/comments/post/5', 'GET'); }); it('forwards createComment', () => { const body = { content: 'Nice!' }; controller.createComment('5', body); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/comments/post/5', 'POST', body); }); it('forwards approveComment', () => { controller.approveComment('9', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/comments/9/approve', 'PUT', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards deleteComment', () => { controller.deleteComment('9', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/comments/9', 'DELETE', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('media routes', () => { it('forwards getMedia', () => { controller.getMedia(req); expect(mockProxy.forward).toHaveBeenCalledWith('media', '/media', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards uploadMedia via forwardWithFile', () => { const file = { originalname: 'a.jpg' } as Express.Multer.File; const body = { alt: 'photo' }; controller.uploadMedia(file, body, req); expect(mockProxy.forwardWithFile).toHaveBeenCalledWith('/media/upload', file, body, 'Bearer jwt-token'); }); it('forwards deleteMedia', () => { controller.deleteMedia('7', req); expect(mockProxy.forward).toHaveBeenCalledWith('media', '/media/7', 'DELETE', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('story routes', () => { it('forwards getStories with querystring params', () => { controller.getStories({ genre: 'fantasy' }); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories?genre=fantasy', 'GET'); }); it('forwards getRecentStories', () => { controller.getRecentStories(); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/recent', 'GET'); }); it('forwards getStoryStats', () => { controller.getStoryStats(req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/stats', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards getAdminStories', () => { controller.getAdminStories({ status: 'pending' }, req); expect(mockProxy.forward).toHaveBeenCalledWith( 'blog', '/stories/admin?status=pending', 'GET', null, { Authorization: 'Bearer jwt-token' }, ); }); it('forwards getStory by slug', () => { controller.getStory('a-story'); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/a-story', 'GET'); }); it('forwards createStory', () => { const body = { title: 'A Story' }; controller.createStory(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards approveStory', () => { controller.approveStory('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/1/approve', 'PATCH', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards rejectStory', () => { controller.rejectStory('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/1/reject', 'PATCH', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards updateStory', () => { const body = { title: 'Updated' }; controller.updateStory('1', body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/1', 'PUT', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards deleteStory', () => { controller.deleteStory('1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/stories/1', 'DELETE', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('news routes', () => { it('forwards getNews with querystring params', () => { controller.getNews({ region: 'usa' }); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/news?region=usa', 'GET'); }); it('forwards refreshNews', () => { const body = { items: [] }; controller.refreshNews(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/news/refresh', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); }); describe('push routes', () => { it('forwards getPushVapidKey', () => { controller.getPushVapidKey(); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/push/vapid-key', 'GET'); }); it('forwards subscribePush', () => { const body = { endpoint: 'e' }; controller.subscribePush(body); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/push/subscribe', 'POST', body); }); it('forwards unsubscribePush', () => { const body = { endpoint: 'e' }; controller.unsubscribePush(body); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/push/unsubscribe', 'DELETE', body); }); }); describe('agent run routes', () => { it('forwards dispatchAgent', () => { const body = { workflow: 'run-story-agent.yml' }; controller.dispatchAgent(body, req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/agent-runs/dispatch', 'POST', body, { Authorization: 'Bearer jwt-token' }); }); it('forwards createAgentRun', () => { const body = { agentType: 'story' }; controller.createAgentRun(body); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/agent-runs', 'POST', body); }); it('forwards updateAgentRun', () => { const body = { status: 'completed' }; controller.updateAgentRun('r1', body); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/agent-runs/r1', 'PUT', body); }); it('forwards getAgentRuns', () => { controller.getAgentRuns(req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/agent-runs', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); it('forwards getAgentRun', () => { controller.getAgentRun('r1', req); expect(mockProxy.forward).toHaveBeenCalledWith('blog', '/agent-runs/r1', 'GET', null, { Authorization: 'Bearer jwt-token' }); }); }); describe('textToSpeech', () => { const makeRes = () => ({ status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis(), set: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis(), }); const ORIGINAL_ENV = process.env; beforeEach(() => { process.env = { ...ORIGINAL_ENV }; delete process.env.TTS_SERVICE_URL; delete process.env.HF_TOKEN; global.fetch = jest.fn(); }); afterAll(() => { process.env = ORIGINAL_ENV; }); it('rejects an empty text body', async () => { const res = makeRes(); await controller.textToSpeech({ text: ' ' }, res as any); expect(res.status).toHaveBeenCalledWith(400); }); it('returns 503 when no TTS backend is configured', async () => { const res = makeRes(); await controller.textToSpeech({ text: 'hello' }, res as any); expect(res.status).toHaveBeenCalledWith(503); }); it('uses the local Kokoro service when configured and it succeeds', async () => { process.env.TTS_SERVICE_URL = 'http://localhost:5050'; (global.fetch as jest.Mock).mockResolvedValue({ ok: true, arrayBuffer: async () => new ArrayBuffer(8), }); const res = makeRes(); await controller.textToSpeech({ text: 'hello' }, res as any); expect(res.set).toHaveBeenCalledWith(expect.objectContaining({ 'Content-Type': 'audio/wav' })); expect(res.send).toHaveBeenCalled(); }); it('falls back to the HF facebook model when the local service fails', async () => { process.env.TTS_SERVICE_URL = 'http://localhost:5050'; process.env.HF_TOKEN = 'hf-token'; (global.fetch as jest.Mock) .mockResolvedValueOnce({ ok: false, status: 500 }) // local Kokoro fails .mockResolvedValueOnce({ ok: true, arrayBuffer: async () => new ArrayBuffer(8), headers: { get: () => 'audio/flac' }, }); const res = makeRes(); await controller.textToSpeech({ text: 'hello' }, res as any); expect(res.send).toHaveBeenCalled(); }); it('tries the maya1 model first for style="news"', async () => { process.env.HF_TOKEN = 'hf-token'; (global.fetch as jest.Mock).mockResolvedValue({ ok: true, arrayBuffer: async () => new ArrayBuffer(8), headers: { get: () => 'audio/flac' }, }); const res = makeRes(); await controller.textToSpeech({ text: 'breaking news', style: 'news' }, res as any); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('maya-research/maya1'), expect.anything(), ); expect(res.send).toHaveBeenCalled(); }); it('returns 503 with aggregated errors when every backend fails', async () => { process.env.TTS_SERVICE_URL = 'http://localhost:5050'; process.env.HF_TOKEN = 'hf-token'; (global.fetch as jest.Mock).mockResolvedValue({ ok: false, status: 500 }); const res = makeRes(); await controller.textToSpeech({ text: 'hello' }, res as any); expect(res.status).toHaveBeenCalledWith(503); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ message: expect.stringContaining('TTS unavailable') }), ); }); }); });