From 360d40e190f28203c290c9fe2efa50d78da324b4 Mon Sep 17 00:00:00 2001 From: mshriver Date: Mon, 24 Nov 2025 09:38:59 -0500 Subject: [PATCH] Add api test modules --- .../AdminProjectManagementApi.test.ts | 210 ++++++++++++++++ .../__tests__/AdminUserManagementApi.test.ts | 213 ++++++++++++++++ src/apis/__tests__/ImportApi.test.ts | 118 +++++++++ src/apis/__tests__/TaskApi.test.ts | 72 ++++++ src/apis/__tests__/UserApi.test.ts | 200 +++++++++++++++ src/apis/__tests__/WidgetApi.test.ts | 152 ++++++++++++ src/apis/__tests__/WidgetConfigApi.test.ts | 211 ++++++++++++++++ src/models/__tests__/LoginError.test.ts | 233 ++++++++++++++++++ src/models/__tests__/WidgetParam.test.ts | 220 +++++++++++++++++ 9 files changed, 1629 insertions(+) create mode 100644 src/apis/__tests__/AdminProjectManagementApi.test.ts create mode 100644 src/apis/__tests__/AdminUserManagementApi.test.ts create mode 100644 src/apis/__tests__/ImportApi.test.ts create mode 100644 src/apis/__tests__/TaskApi.test.ts create mode 100644 src/apis/__tests__/UserApi.test.ts create mode 100644 src/apis/__tests__/WidgetApi.test.ts create mode 100644 src/apis/__tests__/WidgetConfigApi.test.ts create mode 100644 src/models/__tests__/LoginError.test.ts create mode 100644 src/models/__tests__/WidgetParam.test.ts diff --git a/src/apis/__tests__/AdminProjectManagementApi.test.ts b/src/apis/__tests__/AdminProjectManagementApi.test.ts new file mode 100644 index 0000000..e2b57aa --- /dev/null +++ b/src/apis/__tests__/AdminProjectManagementApi.test.ts @@ -0,0 +1,210 @@ +import { AdminProjectManagementApi } from '../AdminProjectManagementApi'; +import type { Project, ProjectList } from '../../models'; +import { Configuration } from '../../runtime'; +import { createMockFetch, createMockResponse } from '../../__tests__/test-utils'; + +describe('AdminProjectManagementApi', () => { + let api: AdminProjectManagementApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new AdminProjectManagementApi(config); + }); + + describe('adminAddProject', () => { + it('should create a new project', async () => { + const newProject: Project = { + name: 'new-project', + title: 'New Project', + }; + + const responseProject: Project = { + id: 'project-123', + ...newProject, + }; + + mockFetch = createMockFetch(responseProject, 201); + global.fetch = mockFetch; + + const result = await api.adminAddProject({ project: newProject }); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/admin/project', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + }), + }) + ); + expect(result.id).toBe('project-123'); + }); + + it('should handle errors when creating a project', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Bad Request' }, 400)); + global.fetch = mockFetch; + + await expect(api.adminAddProject({ project: {} })).rejects.toThrow(); + }); + }); + + describe('adminGetProject', () => { + it('should fetch a project by ID', async () => { + const projectId = 'project-123'; + const expectedProject: Project = { + id: projectId, + name: 'my-project', + }; + + mockFetch = createMockFetch(expectedProject); + global.fetch = mockFetch; + + const result = await api.adminGetProject({ id: projectId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/admin/project/${projectId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.id).toBe(projectId); + }); + + it('should handle 404 when project not found', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Not Found' }, 404)); + global.fetch = mockFetch; + await expect(api.adminGetProject({ id: 'missing' })).rejects.toThrow(); + }); + + it('should require id parameter', async () => { + await expect(api.adminGetProject({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('adminGetProjectList', () => { + it('should fetch a list of projects', async () => { + const mockList: ProjectList = { + projects: [ + { id: '1', name: 'p1' }, + { id: '2', name: 'p2' }, + ], + pagination: { + page: 1, + pageSize: 25, + totalItems: 2, + totalPages: 1, + }, + }; + + mockFetch = createMockFetch(mockList); + global.fetch = mockFetch; + + const result = await api.adminGetProjectList({}); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/admin/project', + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.projects).toHaveLength(2); + }); + + it('should handle pagination parameters', async () => { + mockFetch = createMockFetch({ projects: [] }); + global.fetch = mockFetch; + + await api.adminGetProjectList({ page: 2, pageSize: 10 }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain('page=2'); + expect(url).toContain('pageSize=10'); + }); + + it('should handle filter parameters', async () => { + mockFetch = createMockFetch({ projects: [] }); + global.fetch = mockFetch; + + await api.adminGetProjectList({ filter: ['name=test'] }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain('filter='); + }); + }); + + describe('adminUpdateProject', () => { + it('should update an existing project', async () => { + const projectId = 'project-123'; + const updatedProject: Project = { + id: projectId, + title: 'Updated Title', + }; + + mockFetch = createMockFetch(updatedProject); + global.fetch = mockFetch; + + const result = await api.adminUpdateProject({ id: projectId, project: updatedProject }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/admin/project/${projectId}`, + expect.objectContaining({ + method: 'PUT', + }) + ); + expect(result.title).toBe('Updated Title'); + }); + + it('should require id parameter', async () => { + await expect( + api.adminUpdateProject({ id: null as unknown as string, project: {} }) + ).rejects.toThrow(); + }); + }); + + describe('adminDeleteProject', () => { + it('should delete a project', async () => { + const projectId = 'project-delete'; + mockFetch = jest.fn().mockResolvedValue({ ok: true, status: 204 }); + global.fetch = mockFetch; + + await api.adminDeleteProject({ id: projectId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/admin/project/${projectId}`, + expect.objectContaining({ + method: 'DELETE', + }) + ); + }); + + it('should require id parameter', async () => { + await expect(api.adminDeleteProject({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new AdminProjectManagementApi(config); + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.adminGetProject({ id: '1' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/apis/__tests__/AdminUserManagementApi.test.ts b/src/apis/__tests__/AdminUserManagementApi.test.ts new file mode 100644 index 0000000..a0d5a89 --- /dev/null +++ b/src/apis/__tests__/AdminUserManagementApi.test.ts @@ -0,0 +1,213 @@ +import { AdminUserManagementApi } from '../AdminUserManagementApi'; +import type { User, UserList } from '../../models'; +import { Configuration } from '../../runtime'; +import { createMockFetch, createMockResponse } from '../../__tests__/test-utils'; + +describe('AdminUserManagementApi', () => { + let api: AdminUserManagementApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new AdminUserManagementApi(config); + }); + + describe('adminAddUser', () => { + it('should create a new user', async () => { + const newUser: User = { + email: 'test@example.com', + name: 'Test User', + }; + + const responseUser: User = { + id: 'user-123', + ...newUser, + }; + + mockFetch = createMockFetch(responseUser, 201); + global.fetch = mockFetch; + + const result = await api.adminAddUser({ user: newUser }); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/admin/user', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + }), + }) + ); + expect(result.id).toBe('user-123'); + }); + + it('should handle errors when creating a user', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Bad Request' }, 400)); + global.fetch = mockFetch; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await expect(api.adminAddUser({ user: {} as any })).rejects.toThrow(); + }); + }); + + describe('adminGetUser', () => { + it('should fetch a user by ID', async () => { + const userId = 'user-123'; + const expectedUser: User = { + id: userId, + email: 'test@example.com', + }; + + mockFetch = createMockFetch(expectedUser); + global.fetch = mockFetch; + + const result = await api.adminGetUser({ id: userId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/admin/user/${userId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.id).toBe(userId); + }); + + it('should handle 404 when user not found', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Not Found' }, 404)); + global.fetch = mockFetch; + await expect(api.adminGetUser({ id: 'missing' })).rejects.toThrow(); + }); + + it('should require id parameter', async () => { + await expect(api.adminGetUser({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('adminGetUserList', () => { + it('should fetch a list of users', async () => { + const mockList: UserList = { + users: [ + { id: '1', email: 'u1@e.com' }, + { id: '2', email: 'u2@e.com' }, + ], + pagination: { + page: 1, + pageSize: 25, + totalItems: 2, + totalPages: 1, + }, + }; + + mockFetch = createMockFetch(mockList); + global.fetch = mockFetch; + + const result = await api.adminGetUserList({}); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/admin/user', + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.users).toHaveLength(2); + }); + + it('should handle pagination parameters', async () => { + mockFetch = createMockFetch({ users: [] }); + global.fetch = mockFetch; + + await api.adminGetUserList({ page: 2, pageSize: 10 }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain('page=2'); + expect(url).toContain('pageSize=10'); + }); + + it('should handle filter parameters', async () => { + mockFetch = createMockFetch({ users: [] }); + global.fetch = mockFetch; + + await api.adminGetUserList({ filter: ['email=test'] }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain('filter='); + }); + }); + + describe('adminUpdateUser', () => { + it('should update an existing user', async () => { + const userId = 'user-123'; + const updatedUser: User = { + id: userId, + email: 'test@example.com', + name: 'Updated Name', + }; + + mockFetch = createMockFetch(updatedUser); + global.fetch = mockFetch; + + const result = await api.adminUpdateUser({ id: userId, user: updatedUser }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/admin/user/${userId}`, + expect.objectContaining({ + method: 'PUT', + }) + ); + expect(result.name).toBe('Updated Name'); + }); + + it('should require id parameter', async () => { + await expect( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + api.adminUpdateUser({ id: null as unknown as string, user: {} as any }) + ).rejects.toThrow(); + }); + }); + + describe('adminDeleteUser', () => { + it('should delete a user', async () => { + const userId = 'user-delete'; + mockFetch = jest.fn().mockResolvedValue({ ok: true, status: 204 }); + global.fetch = mockFetch; + + await api.adminDeleteUser({ id: userId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/admin/user/${userId}`, + expect.objectContaining({ + method: 'DELETE', + }) + ); + }); + + it('should require id parameter', async () => { + await expect(api.adminDeleteUser({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new AdminUserManagementApi(config); + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.adminGetUser({ id: '1' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/apis/__tests__/ImportApi.test.ts b/src/apis/__tests__/ImportApi.test.ts new file mode 100644 index 0000000..82b8bad --- /dev/null +++ b/src/apis/__tests__/ImportApi.test.ts @@ -0,0 +1,118 @@ +import { ImportApi } from '../ImportApi'; +import type { Import } from '../../models'; +import { Configuration } from '../../runtime'; +import { createMockFetch, createMockResponse } from '../../__tests__/test-utils'; + +describe('ImportApi', () => { + let api: ImportApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new ImportApi(config); + }); + + describe('addImport', () => { + it('should upload an import file', async () => { + const mockFile = new Blob(['content'], { type: 'application/xml' }); + const responseImport: Import = { + id: 'import-123', + status: 'pending', + }; + + mockFetch = createMockFetch(responseImport, 201); + global.fetch = mockFetch; + + const result = await api.addImport({ + importFile: mockFile, + project: 'project-1', + source: 'archive', + metadata: { key: 'value' }, + }); + + expect(result).toEqual(responseImport); + expect(mockFetch).toHaveBeenCalledTimes(1); + const callArgs = mockFetch.mock.calls[0]; + expect(callArgs[0]).toBe('http://localhost/api/import'); + expect(callArgs[1].method).toBe('POST'); + + // Verify FormData + const formData = callArgs[1].body; + expect(formData).toBeInstanceOf(FormData); + // We can't easily inspect FormData content in JSDOM environment without further mocking + // but we verified it's the correct type being sent + }); + + it('should require importFile parameter', async () => { + await expect(api.addImport({ importFile: null as unknown as Blob })).rejects.toThrow(); + }); + + it('should handle upload errors', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Upload Failed' }, 500)); + global.fetch = mockFetch; + + await expect(api.addImport({ importFile: new Blob([]) })).rejects.toThrow(); + }); + }); + + describe('getImport', () => { + it('should fetch an import by ID', async () => { + const importId = 'import-123'; + const expectedImport: Import = { + id: importId, + status: 'done', + }; + + mockFetch = createMockFetch(expectedImport); + global.fetch = mockFetch; + + const result = await api.getImport({ id: importId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/import/${importId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.id).toBe(importId); + expect(result.status).toBe('done'); + }); + + it('should handle 404 when import not found', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Not Found' }, 404)); + global.fetch = mockFetch; + await expect(api.getImport({ id: 'missing' })).rejects.toThrow(); + }); + + it('should require id parameter', async () => { + await expect(api.getImport({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new ImportApi(config); + + // Mock fetch for getImport + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getImport({ id: '1' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/apis/__tests__/TaskApi.test.ts b/src/apis/__tests__/TaskApi.test.ts new file mode 100644 index 0000000..878948d --- /dev/null +++ b/src/apis/__tests__/TaskApi.test.ts @@ -0,0 +1,72 @@ +import { TaskApi } from '../TaskApi'; +import { Configuration } from '../../runtime'; +import { createMockFetch, createMockResponse } from '../../__tests__/test-utils'; + +describe('TaskApi', () => { + let api: TaskApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new TaskApi(config); + }); + + describe('getTask', () => { + it('should fetch a task by ID', async () => { + const taskId = 'task-123'; + const expectedTask = { + id: taskId, + status: 'running', + result: null, + }; + + mockFetch = createMockFetch(expectedTask); + global.fetch = mockFetch; + + const result = await api.getTask({ id: taskId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/task/${taskId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result).toEqual(expectedTask); + }); + + it('should handle 404 when task not found', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Not Found' }, 404)); + global.fetch = mockFetch; + await expect(api.getTask({ id: 'missing' })).rejects.toThrow(); + }); + + it('should require id parameter', async () => { + await expect(api.getTask({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new TaskApi(config); + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getTask({ id: '1' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/apis/__tests__/UserApi.test.ts b/src/apis/__tests__/UserApi.test.ts new file mode 100644 index 0000000..e616264 --- /dev/null +++ b/src/apis/__tests__/UserApi.test.ts @@ -0,0 +1,200 @@ +import { UserApi } from '../UserApi'; +import { + type CreateToken, + type Token, + type TokenList, + type User, + TokenToJSON, + TokenListToJSON, +} from '../../models'; +import { Configuration } from '../../runtime'; +import { createMockFetch } from '../../__tests__/test-utils'; + +describe('UserApi', () => { + let api: UserApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new UserApi(config); + }); + + describe('getCurrentUser', () => { + it('should fetch the current user', async () => { + const expectedUser: User = { + id: 'user-123', + email: 'test@example.com', + name: 'Test User', + }; + + mockFetch = createMockFetch(expectedUser); + global.fetch = mockFetch; + + const result = await api.getCurrentUser(); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/user', + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result).toEqual(expectedUser); + }); + }); + + describe('updateCurrentUser', () => { + it('should update the current user', async () => { + const updatedUser: User = { + id: 'user-123', + email: 'test@example.com', + name: 'Updated Name', + }; + + mockFetch = createMockFetch(updatedUser); + global.fetch = mockFetch; + + const result = await api.updateCurrentUser(); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/user', + expect.objectContaining({ + method: 'PUT', + }) + ); + expect(result).toEqual(updatedUser); + }); + }); + + describe('token management', () => { + it('should create a new token', async () => { + const createToken: CreateToken = { + name: 'New Token', + expires: null, + }; + const responseToken: Token = { + id: 'token-123', + userId: 'user-123', + name: 'New Token', + token: 'abc-123', + expires: undefined, + }; + + // Use TokenToJSON to simulate server response (snake_case) + mockFetch = createMockFetch(TokenToJSON(responseToken), 201); + global.fetch = mockFetch; + + const result = await api.addToken({ createToken }); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/user/token', + expect.objectContaining({ + method: 'POST', + }) + ); + expect(result).toEqual(responseToken); + }); + + it('should get a token by ID', async () => { + const tokenId = 'token-123'; + const expectedToken: Token = { + id: tokenId, + userId: 'user-123', + name: 'My Token', + token: 'abc-123', + expires: undefined, + }; + + mockFetch = createMockFetch(TokenToJSON(expectedToken)); + global.fetch = mockFetch; + + const result = await api.getToken({ id: tokenId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/user/token/${tokenId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result).toEqual(expectedToken); + }); + + it('should get a list of tokens', async () => { + const mockList: TokenList = { + tokens: [ + { id: '1', userId: 'u1', name: 't1', token: 'v1' }, + { id: '2', userId: 'u2', name: 't2', token: 'v2' }, + ], + pagination: { + page: 1, + pageSize: 25, + totalItems: 2, + totalPages: 1, + }, + }; + + mockFetch = createMockFetch(TokenListToJSON(mockList)); + global.fetch = mockFetch; + + const result = await api.getTokenList({}); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/user/token', + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.tokens).toHaveLength(2); + // Verify content to ensure deserialization worked + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.tokens![0].userId).toBe('u1'); + }); + + it('should delete a token', async () => { + const tokenId = 'token-delete'; + mockFetch = jest.fn().mockResolvedValue({ ok: true, status: 204 }); + global.fetch = mockFetch; + + await api.deleteToken({ id: tokenId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/user/token/${tokenId}`, + expect.objectContaining({ + method: 'DELETE', + }) + ); + }); + + it('should require id for deleteToken', async () => { + await expect(api.deleteToken({ id: null as unknown as string })).rejects.toThrow(); + }); + + it('should require id for getToken', async () => { + await expect(api.getToken({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new UserApi(config); + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getCurrentUser(); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/apis/__tests__/WidgetApi.test.ts b/src/apis/__tests__/WidgetApi.test.ts new file mode 100644 index 0000000..d285327 --- /dev/null +++ b/src/apis/__tests__/WidgetApi.test.ts @@ -0,0 +1,152 @@ +import { WidgetApi } from '../WidgetApi'; +import type { WidgetTypeList } from '../../models'; +import { Configuration } from '../../runtime'; +import { createMockFetch, createMockResponse } from '../../__tests__/test-utils'; + +describe('WidgetApi', () => { + let api: WidgetApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new WidgetApi(config); + }); + + describe('getWidget', () => { + it('should fetch a widget by ID', async () => { + const widgetId = 'widget-123'; + const expectedWidget = { + id: widgetId, + type: 'bar', + title: 'Test Widget', + }; + + mockFetch = createMockFetch(expectedWidget); + global.fetch = mockFetch; + + const result = await api.getWidget({ id: widgetId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/widget/${widgetId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result).toEqual(expectedWidget); + }); + + it('should include params in query string', async () => { + const widgetId = 'widget-params'; + const params = { metric: 'duration' }; + + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getWidget({ id: widgetId, params }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain(widgetId); + }); + + it('should handle 404 when widget not found', async () => { + const widgetId = 'non-existent'; + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Not Found' }, 404)); + global.fetch = mockFetch; + + await expect(api.getWidget({ id: widgetId })).rejects.toThrow(); + }); + + it('should require id parameter', async () => { + await expect(api.getWidget({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('getWidgetTypes', () => { + it('should fetch a list of widget types', async () => { + const mockWidgetTypeList: WidgetTypeList = { + types: [ + { + id: 'type-1', + title: 'Type One', + }, + { + id: 'type-2', + title: 'Type Two', + }, + ], + }; + + mockFetch = createMockFetch(mockWidgetTypeList); + global.fetch = mockFetch; + + const result = await api.getWidgetTypes(); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/widget/types', + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.types).toHaveLength(2); + }); + + it('should filter by type', async () => { + const type = 'bar'; + mockFetch = createMockFetch({ types: [] }); + global.fetch = mockFetch; + + await api.getWidgetTypes({ type }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain(`type=${type}`); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured for getWidget', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new WidgetApi(config); + + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getWidget({ id: '123' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + + it('should include Bearer token when configured for getWidgetTypes', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new WidgetApi(config); + + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getWidgetTypes(); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/apis/__tests__/WidgetConfigApi.test.ts b/src/apis/__tests__/WidgetConfigApi.test.ts new file mode 100644 index 0000000..3e3e792 --- /dev/null +++ b/src/apis/__tests__/WidgetConfigApi.test.ts @@ -0,0 +1,211 @@ +import { WidgetConfigApi } from '../WidgetConfigApi'; +import type { WidgetConfig, WidgetConfigList } from '../../models'; +import { Configuration } from '../../runtime'; +import { createMockFetch, createMockResponse } from '../../__tests__/test-utils'; + +describe('WidgetConfigApi', () => { + let api: WidgetConfigApi; + let mockFetch: jest.Mock; + + beforeEach(() => { + const config = new Configuration({ + basePath: 'http://localhost/api', + }); + api = new WidgetConfigApi(config); + }); + + describe('addWidgetConfig', () => { + it('should create a new widget config', async () => { + const newConfig: WidgetConfig = { + title: 'Test Config', + type: 'bar', + }; + + const responseConfig: WidgetConfig = { + id: 'config-123', + ...newConfig, + }; + + mockFetch = createMockFetch(responseConfig, 201); + global.fetch = mockFetch; + + const result = await api.addWidgetConfig({ widgetConfig: newConfig }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/widget-config', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + }), + }) + ); + expect(result.id).toBe('config-123'); + }); + + it('should handle errors when creating a widget config', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Bad Request' }, 400)); + global.fetch = mockFetch; + + await expect(api.addWidgetConfig({ widgetConfig: {} })).rejects.toThrow(); + }); + }); + + describe('getWidgetConfig', () => { + it('should fetch a widget config by ID', async () => { + const configId = 'config-123'; + const expectedConfig: WidgetConfig = { + id: configId, + title: 'Test Config', + }; + + mockFetch = createMockFetch(expectedConfig); + global.fetch = mockFetch; + + const result = await api.getWidgetConfig({ id: configId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/widget-config/${configId}`, + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.id).toBe(configId); + }); + + it('should handle 404 when not found', async () => { + mockFetch = jest.fn().mockResolvedValue(createMockResponse({ error: 'Not Found' }, 404)); + global.fetch = mockFetch; + await expect(api.getWidgetConfig({ id: 'missing' })).rejects.toThrow(); + }); + + it('should require id parameter', async () => { + await expect(api.getWidgetConfig({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('getWidgetConfigList', () => { + it('should fetch a list of widget configs', async () => { + const mockList: WidgetConfigList = { + widgets: [ + { id: '1', title: 'Config 1' }, + { id: '2', title: 'Config 2' }, + ], + pagination: { + page: 1, + pageSize: 25, + totalItems: 2, + totalPages: 1, + }, + }; + + mockFetch = createMockFetch(mockList); + global.fetch = mockFetch; + + const result = await api.getWidgetConfigList({}); + + expect(mockFetch).toHaveBeenCalledWith( + 'http://localhost/api/widget-config', + expect.objectContaining({ + method: 'GET', + }) + ); + expect(result.widgets).toHaveLength(2); + }); + + it('should handle pagination parameters', async () => { + mockFetch = createMockFetch({ widgets: [] }); + global.fetch = mockFetch; + + await api.getWidgetConfigList({ page: 2, pageSize: 10 }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain('page=2'); + expect(url).toContain('pageSize=10'); + }); + + it('should handle filter parameters', async () => { + mockFetch = createMockFetch({ widgets: [] }); + global.fetch = mockFetch; + + await api.getWidgetConfigList({ filter: ['type=bar'] }); + + const url = mockFetch.mock.calls[0][0] as string; + expect(url).toContain('filter='); + }); + }); + + describe('updateWidgetConfig', () => { + it('should update an existing widget config', async () => { + const configId = 'config-123'; + const updatedConfig: WidgetConfig = { + id: configId, + title: 'Updated Config', + }; + + mockFetch = createMockFetch(updatedConfig); + global.fetch = mockFetch; + + const result = await api.updateWidgetConfig({ id: configId, widgetConfig: updatedConfig }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/widget-config/${configId}`, + expect.objectContaining({ + method: 'PUT', + }) + ); + expect(result.title).toBe('Updated Config'); + }); + + it('should require id parameter', async () => { + await expect( + api.updateWidgetConfig({ id: null as unknown as string, widgetConfig: {} }) + ).rejects.toThrow(); + }); + }); + + describe('deleteWidgetConfig', () => { + it('should delete a widget config', async () => { + const configId = 'config-delete'; + mockFetch = jest.fn().mockResolvedValue({ ok: true, status: 204 }); + global.fetch = mockFetch; + + await api.deleteWidgetConfig({ id: configId }); + + expect(mockFetch).toHaveBeenCalledWith( + `http://localhost/api/widget-config/${configId}`, + expect.objectContaining({ + method: 'DELETE', + }) + ); + }); + + it('should require id parameter', async () => { + await expect(api.deleteWidgetConfig({ id: null as unknown as string })).rejects.toThrow(); + }); + }); + + describe('authentication', () => { + it('should include Bearer token when configured', async () => { + const config = new Configuration({ + basePath: 'http://localhost/api', + accessToken: async () => 'test-token', + }); + api = new WidgetConfigApi(config); + mockFetch = createMockFetch({}); + global.fetch = mockFetch; + + await api.getWidgetConfig({ id: '1' }); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + }), + }) + ); + }); + }); +}); diff --git a/src/models/__tests__/LoginError.test.ts b/src/models/__tests__/LoginError.test.ts new file mode 100644 index 0000000..fac91dc --- /dev/null +++ b/src/models/__tests__/LoginError.test.ts @@ -0,0 +1,233 @@ +import { + type LoginError, + LoginErrorFromJSON, + LoginErrorToJSON, + instanceOfLoginError, +} from '../LoginError'; + +describe('LoginError Model', () => { + describe('interface and types', () => { + it('should create a valid LoginError object with all fields', () => { + const loginError: LoginError = { + code: 'AUTH_001', + message: 'Invalid credentials', + }; + + expect(loginError.code).toBe('AUTH_001'); + expect(loginError.message).toBe('Invalid credentials'); + }); + + it('should create a LoginError object with minimal fields', () => { + const loginError: LoginError = { + code: 'ERR_001', + }; + + expect(loginError.code).toBe('ERR_001'); + expect(loginError.message).toBeUndefined(); + }); + + it('should allow undefined values for optional fields', () => { + const loginError: LoginError = { + code: undefined, + message: undefined, + }; + + expect(loginError.code).toBeUndefined(); + expect(loginError.message).toBeUndefined(); + }); + + it('should support different error codes', () => { + const authError: LoginError = { + code: 'INVALID_TOKEN', + message: 'Token has expired', + }; + const permissionError: LoginError = { + code: 'PERMISSION_DENIED', + message: 'User lacks required permissions', + }; + + expect(authError.code).toBe('INVALID_TOKEN'); + expect(permissionError.code).toBe('PERMISSION_DENIED'); + }); + }); + + describe('LoginErrorFromJSON', () => { + it('should convert JSON to LoginError object', () => { + const json = { + code: 'NETWORK_ERROR', + message: 'Connection timeout', + }; + + const loginError = LoginErrorFromJSON(json); + + expect(loginError.code).toBe('NETWORK_ERROR'); + expect(loginError.message).toBe('Connection timeout'); + }); + + it('should handle null values correctly', () => { + const json = { + code: null, + message: null, + }; + + const loginError = LoginErrorFromJSON(json); + + expect(loginError.code).toBeUndefined(); + expect(loginError.message).toBeUndefined(); + }); + + it('should handle missing fields as undefined', () => { + const json = { + code: 'PARTIAL_ERROR', + }; + + const loginError = LoginErrorFromJSON(json); + + expect(loginError.code).toBe('PARTIAL_ERROR'); + expect(loginError.message).toBeUndefined(); + }); + + it('should return null when passed null', () => { + const result = LoginErrorFromJSON(null); + expect(result).toBeNull(); + }); + + it('should handle empty JSON object', () => { + const json = {}; + + const loginError = LoginErrorFromJSON(json); + + expect(loginError.code).toBeUndefined(); + expect(loginError.message).toBeUndefined(); + }); + + it('should handle server error responses', () => { + const json = { + code: '500', + message: 'Internal server error', + }; + + const loginError = LoginErrorFromJSON(json); + + expect(loginError.code).toBe('500'); + expect(loginError.message).toBe('Internal server error'); + }); + }); + + describe('LoginErrorToJSON', () => { + it('should convert LoginError object to JSON', () => { + const loginError: LoginError = { + code: 'BAD_REQUEST', + message: 'Missing required parameter', + }; + + const json = LoginErrorToJSON(loginError); + + expect(json.code).toBe('BAD_REQUEST'); + expect(json.message).toBe('Missing required parameter'); + }); + + it('should handle undefined fields', () => { + const loginError: LoginError = { + code: 'UNKNOWN', + }; + + const json = LoginErrorToJSON(loginError); + + expect(json.code).toBe('UNKNOWN'); + expect(json.message).toBeUndefined(); + }); + + it('should return null when passed null', () => { + const result = LoginErrorToJSON(null); + expect(result).toBeNull(); + }); + + it('should return undefined when passed undefined', () => { + const result = LoginErrorToJSON(undefined); + expect(result).toBeUndefined(); + }); + + it('should handle empty LoginError object', () => { + const loginError: LoginError = {}; + + const json = LoginErrorToJSON(loginError); + + expect(json.code).toBeUndefined(); + expect(json.message).toBeUndefined(); + }); + + it('should preserve special characters in messages', () => { + const loginError: LoginError = { + code: 'VALIDATION_ERROR', + message: 'Field "username" must not contain special characters: @#$%', + }; + + const json = LoginErrorToJSON(loginError); + + expect(json.message).toBe('Field "username" must not contain special characters: @#$%'); + }); + }); + + describe('instanceOfLoginError', () => { + it('should return true for any object (as per implementation)', () => { + const result = instanceOfLoginError({}); + expect(result).toBe(true); + }); + + it('should return true for valid LoginError objects', () => { + const loginError: LoginError = { + code: 'TEST_ERROR', + message: 'Test message', + }; + const result = instanceOfLoginError(loginError); + expect(result).toBe(true); + }); + }); + + describe('JSON round-trip', () => { + it('should maintain data integrity through JSON conversion round-trip', () => { + const original: LoginError = { + code: 'ROUNDTRIP_ERROR', + message: 'Testing round-trip conversion', + }; + + const json = LoginErrorToJSON(original); + const restored = LoginErrorFromJSON(json); + + expect(restored).toEqual(original); + }); + + it('should handle minimal LoginError in round-trip', () => { + const original: LoginError = { + code: 'MINIMAL', + }; + + const json = LoginErrorToJSON(original); + const restored = LoginErrorFromJSON(json); + + expect(restored).toEqual(original); + }); + + it('should handle empty LoginError in round-trip', () => { + const original: LoginError = {}; + + const json = LoginErrorToJSON(original); + const restored = LoginErrorFromJSON(json); + + expect(restored).toEqual(original); + }); + + it('should handle multiline error messages', () => { + const original: LoginError = { + code: 'COMPLEX_ERROR', + message: 'Error occurred:\n- Line 1\n- Line 2\n- Line 3', + }; + + const json = LoginErrorToJSON(original); + const restored = LoginErrorFromJSON(json); + + expect(restored).toEqual(original); + }); + }); +}); diff --git a/src/models/__tests__/WidgetParam.test.ts b/src/models/__tests__/WidgetParam.test.ts new file mode 100644 index 0000000..44298f8 --- /dev/null +++ b/src/models/__tests__/WidgetParam.test.ts @@ -0,0 +1,220 @@ +import { + type WidgetParam, + WidgetParamFromJSON, + WidgetParamToJSON, + instanceOfWidgetParam, +} from '../WidgetParam'; + +describe('WidgetParam Model', () => { + describe('interface and types', () => { + it('should create a valid WidgetParam object with all fields', () => { + const widgetParam: WidgetParam = { + name: 'test-param', + description: 'A test parameter', + type: 'string', + }; + + expect(widgetParam.name).toBe('test-param'); + expect(widgetParam.description).toBe('A test parameter'); + expect(widgetParam.type).toBe('string'); + }); + + it('should create a WidgetParam object with minimal fields', () => { + const widgetParam: WidgetParam = { + name: 'minimal-param', + }; + + expect(widgetParam.name).toBe('minimal-param'); + expect(widgetParam.description).toBeUndefined(); + expect(widgetParam.type).toBeUndefined(); + }); + + it('should allow undefined values for optional fields', () => { + const widgetParam: WidgetParam = { + name: undefined, + description: undefined, + type: undefined, + }; + + expect(widgetParam.name).toBeUndefined(); + expect(widgetParam.description).toBeUndefined(); + expect(widgetParam.type).toBeUndefined(); + }); + + it('should support different parameter types', () => { + const stringParam: WidgetParam = { + name: 'str-param', + type: 'string', + }; + const intParam: WidgetParam = { + name: 'int-param', + type: 'integer', + }; + const boolParam: WidgetParam = { + name: 'bool-param', + type: 'boolean', + }; + + expect(stringParam.type).toBe('string'); + expect(intParam.type).toBe('integer'); + expect(boolParam.type).toBe('boolean'); + }); + }); + + describe('WidgetParamFromJSON', () => { + it('should convert JSON to WidgetParam object', () => { + const json = { + name: 'json-param', + description: 'Parameter from JSON', + type: 'integer', + }; + + const widgetParam = WidgetParamFromJSON(json); + + expect(widgetParam.name).toBe('json-param'); + expect(widgetParam.description).toBe('Parameter from JSON'); + expect(widgetParam.type).toBe('integer'); + }); + + it('should handle null values correctly', () => { + const json = { + name: null, + description: null, + type: null, + }; + + const widgetParam = WidgetParamFromJSON(json); + + expect(widgetParam.name).toBeUndefined(); + expect(widgetParam.description).toBeUndefined(); + expect(widgetParam.type).toBeUndefined(); + }); + + it('should handle missing fields as undefined', () => { + const json = { + name: 'only-name', + }; + + const widgetParam = WidgetParamFromJSON(json); + + expect(widgetParam.name).toBe('only-name'); + expect(widgetParam.description).toBeUndefined(); + expect(widgetParam.type).toBeUndefined(); + }); + + it('should return null when passed null', () => { + const result = WidgetParamFromJSON(null); + expect(result).toBeNull(); + }); + + it('should handle empty JSON object', () => { + const json = {}; + + const widgetParam = WidgetParamFromJSON(json); + + expect(widgetParam.name).toBeUndefined(); + expect(widgetParam.description).toBeUndefined(); + expect(widgetParam.type).toBeUndefined(); + }); + }); + + describe('WidgetParamToJSON', () => { + it('should convert WidgetParam object to JSON', () => { + const widgetParam: WidgetParam = { + name: 'output-param', + description: 'Parameter to JSON', + type: 'string', + }; + + const json = WidgetParamToJSON(widgetParam); + + expect(json.name).toBe('output-param'); + expect(json.description).toBe('Parameter to JSON'); + expect(json.type).toBe('string'); + }); + + it('should handle undefined fields', () => { + const widgetParam: WidgetParam = { + name: 'partial-param', + }; + + const json = WidgetParamToJSON(widgetParam); + + expect(json.name).toBe('partial-param'); + expect(json.description).toBeUndefined(); + expect(json.type).toBeUndefined(); + }); + + it('should return null when passed null', () => { + const result = WidgetParamToJSON(null); + expect(result).toBeNull(); + }); + + it('should return undefined when passed undefined', () => { + const result = WidgetParamToJSON(undefined); + expect(result).toBeUndefined(); + }); + + it('should handle empty WidgetParam object', () => { + const widgetParam: WidgetParam = {}; + + const json = WidgetParamToJSON(widgetParam); + + expect(json.name).toBeUndefined(); + expect(json.description).toBeUndefined(); + expect(json.type).toBeUndefined(); + }); + }); + + describe('instanceOfWidgetParam', () => { + it('should return true for any object (as per implementation)', () => { + const result = instanceOfWidgetParam({}); + expect(result).toBe(true); + }); + + it('should return true for valid WidgetParam objects', () => { + const widgetParam: WidgetParam = { + name: 'test', + description: 'test description', + type: 'string', + }; + const result = instanceOfWidgetParam(widgetParam); + expect(result).toBe(true); + }); + }); + + describe('JSON round-trip', () => { + it('should maintain data integrity through JSON conversion round-trip', () => { + const original: WidgetParam = { + name: 'roundtrip-param', + description: 'Testing round-trip conversion', + type: 'boolean', + }; + + const json = WidgetParamToJSON(original); + const restored = WidgetParamFromJSON(json); + + expect(restored).toEqual(original); + }); + + it('should handle minimal WidgetParam in round-trip', () => { + const original: WidgetParam = { + name: 'minimal', + }; + + const json = WidgetParamToJSON(original); + const restored = WidgetParamFromJSON(json); + + expect(restored).toEqual(original); + }); + + it('should handle empty WidgetParam in round-trip', () => { + const original: WidgetParam = {}; + + const json = WidgetParamToJSON(original); + const restored = WidgetParamFromJSON(json); + + expect(restored).toEqual(original); + }); + }); +});