Skip to content

Commit 0142e59

Browse files
committed
chore: fix tests
1 parent 53b9ce7 commit 0142e59

8 files changed

Lines changed: 907 additions & 112 deletions

File tree

package-lock.json

Lines changed: 693 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"@eslint/js": "^9.39.4",
149149
"@eslint/json": "^1.0.1",
150150
"@tailwindcss/vite": "^4.2.2",
151+
"@testing-library/react": "^16.3.2",
151152
"@types/activedirectory2": "^1.2.6",
152153
"@types/cors": "^2.8.19",
153154
"@types/express": "^5.0.6",
@@ -169,15 +170,16 @@
169170
"@vitest/coverage-v8": "^3.2.4",
170171
"c8": "^11.0.0",
171172
"cross-env": "^10.1.0",
173+
"cypress": "^15.9.0",
172174
"eslint": "^9.39.4",
173175
"eslint-config-prettier": "^10.1.8",
174-
"cypress": "^15.9.0",
175176
"eslint-plugin-cypress": "^5.2.1",
176177
"eslint-plugin-license-header": "^0.9.0",
177178
"eslint-plugin-react": "^7.37.5",
178179
"fast-check": "^4.5.3",
179180
"globals": "^16.5.0",
180181
"husky": "^9.1.7",
182+
"jsdom": "^29.0.2",
181183
"lint-staged": "^16.2.7",
182184
"nyc": "^17.1.0",
183185
"prettier": "^3.8.1",

src/ui/services/repo.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import axios from 'axios';
1818
import { getAxiosConfig } from './auth.js';
1919
import { Repo } from '../../db/types';
2020
import { RepoView } from '../types';
21-
import type { RepoSortField } from '../views/RepoList/Components/repoSortField';
2221
import { getApiV1BaseUrl } from './apiConfig';
2322
import { ServiceResult, getServiceError, errorResult, successResult } from './errors';
2423
import { SCMRepositoryMetadata } from '../types';
@@ -147,17 +146,6 @@ const fetchRepoViews = async (): Promise<ServiceResult<RepoView[]>> => {
147146
}
148147
};
149148

150-
const getRepos = async (
151-
sort: RepoSortField,
152-
currentUsername?: string | null,
153-
): Promise<ServiceResult<RepoView[]>> => {
154-
const result = await fetchRepoViews();
155-
if (!result.success || !result.data) {
156-
return result;
157-
}
158-
return successResult(sortRepoViews(result.data, sort, currentUsername));
159-
};
160-
161149
const getRepo = async (id: string): Promise<ServiceResult<RepoView>> => {
162150
const apiV1Base = await getApiV1BaseUrl();
163151
const url = new URL(`${apiV1Base}/repo/${id}`);
@@ -245,13 +233,4 @@ const deleteRepo = async (repoId: string): Promise<void> => {
245233
}
246234
};
247235

248-
export {
249-
addUser,
250-
deleteUser,
251-
getRepos,
252-
fetchRepoViews,
253-
getRepo,
254-
getRepoScmMetadata,
255-
addRepo,
256-
deleteRepo,
257-
};
236+
export { addUser, deleteUser, fetchRepoViews, getRepo, getRepoScmMetadata, addRepo, deleteRepo };

src/ui/services/user.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -160,26 +160,11 @@ const resolveDisplayNameByUsername = async (username: string): Promise<string |
160160
}
161161
};
162162

163-
const updateUser = async (
164-
user: PublicUser,
165-
setErrorMessage: SetStateCallback<string>,
166-
): Promise<void> => {
167-
try {
168-
const baseUrl = await getBaseUrl();
169-
await axios.post(`${baseUrl}/api/auth/gitAccount`, user, getAxiosConfig());
170-
} catch (error: unknown) {
171-
const { status, message } = getServiceError(error, 'Unknown error');
172-
setErrorMessage(formatErrorMessage('Error updating user', status, message));
173-
throw error;
174-
}
175-
};
176-
177163
export {
178164
getUser,
179165
getUsers,
180166
fetchUsersForAutocomplete,
181167
sortUsers,
182168
resolveUsernameByEmail,
183169
resolveDisplayNameByUsername,
184-
updateUser,
185170
};

test/ui/repo.test.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
1818
import {
1919
addUser,
2020
deleteUser,
21+
fetchRepoViews,
2122
getRepo,
22-
getRepos,
2323
addRepo,
2424
deleteRepo,
2525
} from '../../src/ui/services/repo';
@@ -148,36 +148,25 @@ describe('repo service additional functions', () => {
148148
vi.clearAllMocks();
149149
});
150150

151-
describe('getRepos', () => {
152-
it('returns sorted repos on success', async () => {
151+
describe('fetchRepoViews', () => {
152+
it('returns repo list on success', async () => {
153153
const reposData = [
154154
{ name: 'zebra-repo', project: 'org', url: 'https://example.com/org/zebra-repo.git' },
155155
{ name: 'alpha-repo', project: 'org', url: 'https://example.com/org/alpha-repo.git' },
156156
];
157157

158158
axiosMock.mockResolvedValue({ data: reposData });
159159

160-
const result = await getRepos();
161-
162-
expect(result.success).toBe(true);
163-
expect(result.data).toEqual([
164-
{ name: 'alpha-repo', project: 'org', url: 'https://example.com/org/alpha-repo.git' },
165-
{ name: 'zebra-repo', project: 'org', url: 'https://example.com/org/zebra-repo.git' },
166-
]);
167-
});
168-
169-
it('passes query parameters correctly', async () => {
170-
axiosMock.mockResolvedValue({ data: [] });
171-
172-
await getRepos({ active: true });
160+
const result = await fetchRepoViews();
173161

162+
expect(result).toEqual({ success: true, data: reposData });
174163
expect(axiosMock).toHaveBeenCalledWith(
175-
'http://localhost:8080/api/v1/repo?active=true',
164+
'http://localhost:8080/api/v1/repo',
176165
expect.any(Object),
177166
);
178167
});
179168

180-
it('returns error result when getRepos fails', async () => {
169+
it('returns error result on failure', async () => {
181170
axiosMock.mockRejectedValue({
182171
response: {
183172
status: 500,
@@ -187,7 +176,7 @@ describe('repo service additional functions', () => {
187176
},
188177
});
189178

190-
const result = await getRepos();
179+
const result = await fetchRepoViews();
191180

192181
expect(result).toEqual({
193182
success: false,
@@ -199,7 +188,7 @@ describe('repo service additional functions', () => {
199188
it('uses fallback message when error has no response data', async () => {
200189
axiosMock.mockRejectedValue(new Error('Connection timeout'));
201190

202-
const result = await getRepos();
191+
const result = await fetchRepoViews();
203192

204193
expect(result).toEqual({
205194
success: false,

test/ui/useRepoQuery.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* Copyright 2026 GitProxy Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// @vitest-environment jsdom
18+
19+
import { beforeEach, describe, expect, it, vi } from 'vitest';
20+
import { renderHook, waitFor } from '@testing-library/react';
21+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
22+
import { MemoryRouter } from 'react-router';
23+
import React from 'react';
24+
import { useRepoQuery } from '../../src/ui/query/useRepoQuery';
25+
26+
const navigateMock = vi.fn();
27+
28+
vi.mock('react-router', async (importOriginal) => {
29+
const actual = await importOriginal<typeof import('react-router')>();
30+
return { ...actual, useNavigate: () => navigateMock };
31+
});
32+
33+
vi.mock('../../src/ui/services/repo', () => ({
34+
getRepo: vi.fn(),
35+
}));
36+
37+
import { getRepo } from '../../src/ui/services/repo';
38+
const getRepoMock = getRepo as ReturnType<typeof vi.fn>;
39+
40+
function wrapper({ children }: { children: React.ReactNode }) {
41+
const client = new QueryClient({
42+
defaultOptions: { queries: { retry: false } },
43+
});
44+
return React.createElement(
45+
QueryClientProvider,
46+
{ client },
47+
React.createElement(MemoryRouter, null, children),
48+
);
49+
}
50+
51+
describe('useRepoQuery', () => {
52+
beforeEach(() => {
53+
vi.clearAllMocks();
54+
});
55+
56+
it('returns repo data on success', async () => {
57+
const repoData = { name: 'test-repo', project: 'org' };
58+
getRepoMock.mockResolvedValue({ success: true, data: repoData });
59+
60+
const { result } = renderHook(() => useRepoQuery('repo-1'), { wrapper });
61+
62+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
63+
expect(result.current.data).toEqual(repoData);
64+
expect(getRepoMock).toHaveBeenCalledWith('repo-1');
65+
});
66+
67+
it('navigates to /login and throws on 401', async () => {
68+
getRepoMock.mockResolvedValue({ success: false, status: 401, message: 'Not logged in' });
69+
70+
const { result } = renderHook(() => useRepoQuery('repo-1'), { wrapper });
71+
72+
await waitFor(() => expect(result.current.isError).toBe(true));
73+
expect(navigateMock).toHaveBeenCalledWith('/login', { replace: true });
74+
expect((result.current.error as Error).message).toBe('Not logged in');
75+
});
76+
77+
it('throws with message on non-401 failure', async () => {
78+
getRepoMock.mockResolvedValue({
79+
success: false,
80+
status: 403,
81+
message: 'User not authorised on this repository',
82+
});
83+
84+
const { result } = renderHook(() => useRepoQuery('repo-1'), { wrapper });
85+
86+
await waitFor(() => expect(result.current.isError).toBe(true));
87+
expect(navigateMock).not.toHaveBeenCalled();
88+
expect((result.current.error as Error).message).toBe('User not authorised on this repository');
89+
});
90+
91+
it('is disabled when id is undefined', () => {
92+
const { result } = renderHook(() => useRepoQuery(undefined), { wrapper });
93+
94+
expect(result.current.fetchStatus).toBe('idle');
95+
expect(getRepoMock).not.toHaveBeenCalled();
96+
});
97+
});
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright 2026 GitProxy Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// @vitest-environment jsdom
18+
19+
import { beforeEach, describe, expect, it, vi } from 'vitest';
20+
import { renderHook, waitFor } from '@testing-library/react';
21+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
22+
import { MemoryRouter } from 'react-router';
23+
import React from 'react';
24+
import { useRepoViewsListQuery } from '../../src/ui/query/useRepoViewsListQuery';
25+
26+
const navigateMock = vi.fn();
27+
28+
vi.mock('react-router', async (importOriginal) => {
29+
const actual = await importOriginal<typeof import('react-router')>();
30+
return { ...actual, useNavigate: () => navigateMock };
31+
});
32+
33+
vi.mock('../../src/ui/services/repo', () => ({
34+
fetchRepoViews: vi.fn(),
35+
}));
36+
37+
import { fetchRepoViews } from '../../src/ui/services/repo';
38+
const fetchRepoViewsMock = fetchRepoViews as ReturnType<typeof vi.fn>;
39+
40+
function wrapper({ children }: { children: React.ReactNode }) {
41+
const client = new QueryClient({
42+
defaultOptions: { queries: { retry: false } },
43+
});
44+
return React.createElement(
45+
QueryClientProvider,
46+
{ client },
47+
React.createElement(MemoryRouter, null, children),
48+
);
49+
}
50+
51+
describe('useRepoViewsListQuery', () => {
52+
beforeEach(() => {
53+
vi.clearAllMocks();
54+
});
55+
56+
it('returns repo list on success', async () => {
57+
const reposData = [
58+
{ name: 'alpha-repo', project: 'org' },
59+
{ name: 'zebra-repo', project: 'org' },
60+
];
61+
fetchRepoViewsMock.mockResolvedValue({ success: true, data: reposData });
62+
63+
const { result } = renderHook(() => useRepoViewsListQuery(true), { wrapper });
64+
65+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
66+
expect(result.current.data).toEqual(reposData);
67+
});
68+
69+
it('navigates to /login and throws on 401', async () => {
70+
fetchRepoViewsMock.mockResolvedValue({
71+
success: false,
72+
status: 401,
73+
message: 'Not authenticated',
74+
});
75+
76+
const { result } = renderHook(() => useRepoViewsListQuery(true), { wrapper });
77+
78+
await waitFor(() => expect(result.current.isError).toBe(true));
79+
expect(navigateMock).toHaveBeenCalledWith('/login', { replace: true });
80+
expect((result.current.error as Error).message).toBe('Not authenticated');
81+
});
82+
83+
it('throws with message on non-401 failure', async () => {
84+
fetchRepoViewsMock.mockResolvedValue({
85+
success: false,
86+
status: 500,
87+
message: 'Database connection failed',
88+
});
89+
90+
const { result } = renderHook(() => useRepoViewsListQuery(true), { wrapper });
91+
92+
await waitFor(() => expect(result.current.isError).toBe(true));
93+
expect(navigateMock).not.toHaveBeenCalled();
94+
expect((result.current.error as Error).message).toBe('Database connection failed');
95+
});
96+
97+
it('is disabled when enabled is false', () => {
98+
const { result } = renderHook(() => useRepoViewsListQuery(false), { wrapper });
99+
100+
expect(result.current.fetchStatus).toBe('idle');
101+
expect(fetchRepoViewsMock).not.toHaveBeenCalled();
102+
});
103+
});

0 commit comments

Comments
 (0)