From b6ce5010acc6be628136e7e0e948ea2847e98cca Mon Sep 17 00:00:00 2001 From: Nuwan Date: Tue, 27 Jan 2026 08:14:00 +0530 Subject: [PATCH] test(07-02): add failing tests for REST API chat methods Add comprehensive unit tests for getChatMessages and sendChatMessage: - Test session and global channel message fetching - Test pagination cursor (before parameter) - Test all HTTP error codes (403, 404, 422, 500) - Test request headers and credentials - Test error handling with invalid JSON responses Tests fail as expected - functions not yet implemented. Co-Authored-By: Claude Sonnet 4.5 --- jam-ui/src/helpers/__tests__/rest.test.js | 302 ++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 jam-ui/src/helpers/__tests__/rest.test.js diff --git a/jam-ui/src/helpers/__tests__/rest.test.js b/jam-ui/src/helpers/__tests__/rest.test.js new file mode 100644 index 000000000..d1eee6dcb --- /dev/null +++ b/jam-ui/src/helpers/__tests__/rest.test.js @@ -0,0 +1,302 @@ +import { getChatMessages, sendChatMessage } from '../rest'; + +// Mock global fetch +global.fetch = jest.fn(); + +// Mock environment variables +process.env.REACT_APP_API_BASE_URL = 'http://localhost:3000/api'; + +describe('getChatMessages', () => { + beforeEach(() => { + // Clear all mocks before each test + global.fetch.mockClear(); + }); + + test('fetches messages for session channel', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ + messages: [ + { id: 'msg-1', message: 'Hello', sender_id: 'user-1' } + ], + next: 20 + }) + }); + + const result = await getChatMessages({ + channel: 'session', + sessionId: 'session-abc' + }); + + expect(fetch).toHaveBeenCalledWith( + expect.stringContaining('/api/chat?channel=session&session_id=session-abc'), + expect.objectContaining({ method: 'GET' }) + ); + expect(result.messages).toHaveLength(1); + expect(result.next).toBe(20); + }); + + test('fetches messages for global channel', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ + messages: [ + { id: 'msg-1', message: 'Hello world', sender_id: 'user-1' } + ], + next: null + }) + }); + + const result = await getChatMessages({ + channel: 'global' + }); + + expect(fetch).toHaveBeenCalledWith( + expect.stringContaining('/api/chat?channel=global'), + expect.objectContaining({ method: 'GET' }) + ); + // Should NOT contain session_id in URL + expect(fetch.mock.calls[0][0]).not.toContain('session_id'); + expect(result.messages).toHaveLength(1); + expect(result.next).toBeNull(); + }); + + test('includes pagination cursor (before)', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ + messages: [], + next: null + }) + }); + + await getChatMessages({ + channel: 'session', + sessionId: 'session-abc', + before: 50 + }); + + expect(fetch).toHaveBeenCalledWith( + expect.stringContaining('before=50'), + expect.any(Object) + ); + }); + + test('throws error on 404', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 404, + statusText: 'Not Found' + }); + + await expect( + getChatMessages({ channel: 'session', sessionId: 'invalid' }) + ).rejects.toThrow(); + }); + + test('throws error on 403', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 403, + statusText: 'Forbidden' + }); + + await expect( + getChatMessages({ channel: 'session', sessionId: 'session-abc' }) + ).rejects.toThrow(); + }); + + test('throws error on 500', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 500, + statusText: 'Internal Server Error' + }); + + await expect( + getChatMessages({ channel: 'global' }) + ).rejects.toThrow(); + }); + + test('includes credentials and content-type headers', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ messages: [], next: null }) + }); + + await getChatMessages({ channel: 'global' }); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + credentials: 'include', + headers: expect.objectContaining({ + 'Content-Type': 'application/json' + }) + }) + ); + }); +}); + +describe('sendChatMessage', () => { + beforeEach(() => { + global.fetch.mockClear(); + }); + + test('sends message to session channel', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ + message: { + id: 'msg-new', + message: 'Hello world', + sender_id: 'user-1', + created_at: '2026-01-26T12:00:00Z' + } + }) + }); + + const result = await sendChatMessage({ + channel: 'session', + sessionId: 'session-abc', + message: 'Hello world' + }); + + expect(fetch).toHaveBeenCalledWith( + expect.stringContaining('/api/chat'), + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + channel: 'session', + session_id: 'session-abc', + message: 'Hello world' + }) + }) + ); + expect(result.message.id).toBe('msg-new'); + }); + + test('sends message to global channel', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ + message: { + id: 'msg-global', + message: 'Hello everyone', + sender_id: 'user-1', + created_at: '2026-01-26T12:00:00Z' + } + }) + }); + + const result = await sendChatMessage({ + channel: 'global', + message: 'Hello everyone' + }); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + channel: 'global', + message: 'Hello everyone' + }) + }) + ); + // Should NOT contain session_id + const body = JSON.parse(fetch.mock.calls[0][1].body); + expect(body.session_id).toBeUndefined(); + expect(result.message.id).toBe('msg-global'); + }); + + test('throws error on 422 (validation error)', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 422, + statusText: 'Unprocessable Entity', + json: async () => ({ error: 'Message too long' }) + }); + + await expect( + sendChatMessage({ + channel: 'session', + sessionId: 'abc', + message: 'x'.repeat(1000) + }) + ).rejects.toThrow(); + }); + + test('throws error on 403', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 403, + statusText: 'Forbidden', + json: async () => ({ error: 'Unauthorized' }) + }); + + await expect( + sendChatMessage({ + channel: 'session', + sessionId: 'session-abc', + message: 'Hello' + }) + ).rejects.toThrow(); + }); + + test('throws error on 500', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + json: async () => ({ error: 'Server error' }) + }); + + await expect( + sendChatMessage({ + channel: 'global', + message: 'Hello' + }) + ).rejects.toThrow(); + }); + + test('includes credentials and content-type headers', async () => { + global.fetch.mockResolvedValue({ + ok: true, + json: async () => ({ message: { id: 'msg-1' } }) + }); + + await sendChatMessage({ + channel: 'global', + message: 'Test' + }); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + credentials: 'include', + headers: expect.objectContaining({ + 'Content-Type': 'application/json' + }) + }) + ); + }); + + test('handles error response with invalid json gracefully', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + json: async () => { + throw new Error('Invalid JSON'); + } + }); + + await expect( + sendChatMessage({ + channel: 'global', + message: 'Test' + }) + ).rejects.toThrow(); + }); +});