Skip to content

Commit 0c50d52

Browse files
committed
🚨 Add tests for form schema (#2446)
1 parent 9a44bba commit 0c50d52

File tree

2 files changed

+313
-4
lines changed

2 files changed

+313
-4
lines changed

src/lib/utils/authorship.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const initializeAuthForm = async (locals: App.Locals) => {
2727
* Create authentication form with comprehensive fallback handling
2828
* Tries multiple strategies until one succeeds
2929
*/
30-
const createAuthFormWithFallback = async () => {
30+
export const createAuthFormWithFallback = async () => {
3131
for (const strategy of formCreationStrategies) {
3232
try {
3333
const result = await strategy.run();
@@ -149,7 +149,7 @@ const formValidationStrategies = [
149149
...createBaseAuthForm(),
150150
};
151151

152-
return { form: { ...fallbackForm, message: '' } };
152+
return { form: fallbackForm };
153153
},
154154
},
155155
];

src/test/lib/utils/authorship.test.ts

Lines changed: 311 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import { expect, test } from 'vitest';
2-
import { hasAuthority, canRead, canEdit, canDelete } from '$lib/utils/authorship';
1+
import { superValidate } from 'sveltekit-superforms/server';
2+
import { zod } from 'sveltekit-superforms/adapters';
3+
import { expect, test, describe, vi, beforeEach, afterEach } from 'vitest';
4+
5+
import {
6+
createAuthFormWithFallback,
7+
validateAuthFormWithFallback,
8+
hasAuthority,
9+
canRead,
10+
canEdit,
11+
canDelete,
12+
} from '$lib/utils/authorship';
313
import type {
414
Authorship,
515
AuthorshipForRead,
@@ -8,6 +18,305 @@ import type {
818
} from '$lib/types/authorship';
919
import { Roles } from '$lib/types/user';
1020

21+
// Mock modules for testing
22+
vi.mock('sveltekit-superforms/server', () => ({
23+
superValidate: vi.fn(),
24+
}));
25+
26+
vi.mock('sveltekit-superforms/adapters', () => ({
27+
zod: vi.fn(),
28+
}));
29+
30+
vi.mock('$lib/zod/schema', () => ({
31+
authSchema: {},
32+
}));
33+
34+
describe('createAuthFormWithFallback', () => {
35+
beforeEach(() => {
36+
vi.clearAllMocks();
37+
// Mock console methods
38+
vi.spyOn(console, 'log').mockImplementation(() => {});
39+
vi.spyOn(console, 'warn').mockImplementation(() => {});
40+
});
41+
42+
afterEach(() => {
43+
vi.restoreAllMocks();
44+
});
45+
46+
describe('Auth form for successful cases', () => {
47+
test('expect to be succeed with basic superValidate strategy', async () => {
48+
const mockForm = {
49+
id: 'test-form-id',
50+
valid: true,
51+
posted: false,
52+
data: { username: '', password: '' },
53+
errors: {},
54+
constraints: {},
55+
shape: {},
56+
};
57+
58+
vi.mocked(superValidate).mockResolvedValueOnce(mockForm);
59+
vi.mocked(zod).mockReturnValue({} as any);
60+
61+
const result = await createAuthFormWithFallback();
62+
63+
expect(result.form).toMatchObject({
64+
valid: true,
65+
data: { username: '', password: '' },
66+
message: '',
67+
});
68+
});
69+
70+
test('expect to fallback to explicit zod adapter when basic strategy fails', async () => {
71+
const mockForm = {
72+
id: 'test-form-id',
73+
valid: true,
74+
posted: false,
75+
data: { username: '', password: '' },
76+
errors: {},
77+
constraints: {},
78+
shape: {},
79+
};
80+
81+
vi.mocked(superValidate)
82+
.mockRejectedValueOnce(new Error('Failed to create with basic strategy'))
83+
.mockResolvedValueOnce(mockForm);
84+
vi.mocked(zod).mockReturnValue({} as any);
85+
86+
const result = await createAuthFormWithFallback();
87+
88+
expect(result.form).toMatchObject({
89+
valid: true,
90+
data: { username: '', password: '' },
91+
message: '',
92+
});
93+
});
94+
95+
test('expect to use manual form creation when all superValidate attempts fail', async () => {
96+
vi.mocked(superValidate)
97+
.mockRejectedValueOnce(new Error('Failed to create strategy using SuperValidate'))
98+
.mockRejectedValueOnce(new Error('Failed to create strategy with zod adapter'));
99+
vi.mocked(zod).mockReturnValue({} as any);
100+
101+
const result = await createAuthFormWithFallback();
102+
103+
expect(result.form).toMatchObject({
104+
valid: true,
105+
posted: false,
106+
data: { username: '', password: '' },
107+
errors: {},
108+
message: '',
109+
});
110+
expect(result.form.constraints).toBeDefined();
111+
expect(result.form.shape).toBeDefined();
112+
});
113+
});
114+
115+
describe('return value validation', () => {
116+
test('expect to return valid form structure', async () => {
117+
vi.mocked(superValidate).mockRejectedValue(new Error('Force manual fallback'));
118+
vi.mocked(zod).mockReturnValue({} as any);
119+
120+
const result = await createAuthFormWithFallback();
121+
122+
expect(result.form).toHaveProperty('id');
123+
expect(result.form).toHaveProperty('valid');
124+
expect(result.form).toHaveProperty('posted');
125+
expect(result.form).toHaveProperty('data');
126+
expect(result.form).toHaveProperty('errors');
127+
expect(result.form).toHaveProperty('constraints');
128+
expect(result.form).toHaveProperty('shape');
129+
expect(result.form).toHaveProperty('message');
130+
});
131+
132+
test('expect to include correct constraints', async () => {
133+
vi.mocked(superValidate).mockRejectedValue(new Error('Force manual fallback'));
134+
vi.mocked(zod).mockReturnValue({} as any);
135+
136+
const result = await createAuthFormWithFallback();
137+
138+
expect(result.form.constraints).toMatchObject({
139+
username: {
140+
minlength: 3,
141+
maxlength: 24,
142+
required: true,
143+
pattern: '[\\w]*',
144+
},
145+
password: {
146+
minlength: 8,
147+
maxlength: 128,
148+
required: true,
149+
pattern: '(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\\d)[a-zA-Z\\d]{8,128}',
150+
},
151+
});
152+
});
153+
154+
test('expect to include shape property for production compatibility', async () => {
155+
vi.mocked(superValidate).mockRejectedValue(new Error('Force manual fallback'));
156+
vi.mocked(zod).mockReturnValue({} as any);
157+
158+
const result = await createAuthFormWithFallback();
159+
160+
expect(result.form.shape).toMatchObject({
161+
username: { type: 'string' },
162+
password: { type: 'string' },
163+
});
164+
});
165+
});
166+
});
167+
168+
describe('validateAuthFormWithFallback', () => {
169+
beforeEach(() => {
170+
vi.clearAllMocks();
171+
vi.spyOn(console, 'log').mockImplementation(() => {});
172+
vi.spyOn(console, 'warn').mockImplementation(() => {});
173+
});
174+
175+
afterEach(() => {
176+
vi.restoreAllMocks();
177+
});
178+
179+
describe('Auth form for successful cases', () => {
180+
test('expect to validate valid form data successfully', async () => {
181+
const mockForm = {
182+
id: 'test-form-id',
183+
valid: true,
184+
posted: true,
185+
data: { username: 'testuser', password: 'TestPass123' },
186+
errors: {},
187+
constraints: {},
188+
shape: {},
189+
};
190+
191+
vi.mocked(superValidate).mockResolvedValueOnce(mockForm);
192+
vi.mocked(zod).mockReturnValue({} as any);
193+
194+
const mockRequest = new Request('http://localhost', {
195+
method: 'POST',
196+
body: new URLSearchParams({ username: 'testuser', password: 'TestPass123' }).toString(),
197+
headers: {
198+
'Content-Type': 'application/x-www-form-urlencoded',
199+
},
200+
});
201+
202+
const result = await validateAuthFormWithFallback(mockRequest);
203+
204+
expect(result).toMatchObject({
205+
valid: true,
206+
data: { username: 'testuser', password: 'TestPass123' },
207+
});
208+
});
209+
210+
test('expect to return form with validation errors for invalid data', async () => {
211+
const mockForm = {
212+
id: 'test-form-id',
213+
valid: false,
214+
posted: true,
215+
data: { username: 'ab', password: '123' },
216+
errors: {
217+
username: ['Username too short'],
218+
password: ['Password too short'],
219+
},
220+
constraints: {},
221+
shape: {},
222+
};
223+
224+
vi.mocked(superValidate).mockResolvedValueOnce(mockForm);
225+
vi.mocked(zod).mockReturnValue({} as any);
226+
227+
const mockRequest = new Request('http://localhost', {
228+
method: 'POST',
229+
body: new URLSearchParams({ username: 'ab', password: '123' }).toString(),
230+
headers: {
231+
'Content-Type': 'application/x-www-form-urlencoded',
232+
},
233+
});
234+
235+
const result = await validateAuthFormWithFallback(mockRequest);
236+
237+
expect(result.valid).toBe(false);
238+
expect(result.errors).toBeDefined();
239+
});
240+
241+
test('expect to fallback to manual form when validation fails', async () => {
242+
vi.mocked(superValidate)
243+
.mockRejectedValueOnce(new Error('Failed to create strategy using SuperValidate'))
244+
.mockRejectedValueOnce(new Error('Failed to create strategy with zod adapter'));
245+
vi.mocked(zod).mockReturnValue({} as any);
246+
247+
const mockRequest = new Request('http://localhost', {
248+
method: 'POST',
249+
body: new URLSearchParams({ username: 'testuser', password: 'TestPass123' }).toString(),
250+
headers: {
251+
'Content-Type': 'application/x-www-form-urlencoded',
252+
},
253+
});
254+
255+
const result = await validateAuthFormWithFallback(mockRequest);
256+
257+
expect(result).toMatchObject({
258+
valid: false,
259+
posted: true,
260+
data: { username: '', password: '' },
261+
errors: { _form: ['ログインできませんでした。'] },
262+
message: 'サーバでエラーが発生しました。本サービスの開発・運営チームまでご連絡ください。',
263+
});
264+
});
265+
});
266+
267+
describe('Auth form for error cases', () => {
268+
test('expect to handle invalid Request object gracefully', async () => {
269+
vi.mocked(superValidate)
270+
.mockRejectedValueOnce(new Error('Invalid request'))
271+
.mockRejectedValueOnce(new Error('Invalid request'));
272+
vi.mocked(zod).mockReturnValue({} as any);
273+
274+
const mockRequest = null as any;
275+
276+
const result = await validateAuthFormWithFallback(mockRequest);
277+
278+
expect(result).toMatchObject({
279+
valid: false,
280+
posted: true,
281+
errors: { _form: ['ログインできませんでした。'] },
282+
});
283+
});
284+
});
285+
286+
describe('fallback strategy', () => {
287+
test('expect to return error form with appropriate message', async () => {
288+
vi.mocked(superValidate)
289+
.mockRejectedValueOnce(new Error('Failed to create strategy using SuperValidate'))
290+
.mockRejectedValueOnce(new Error('Failed to create strategy with zod adapter'));
291+
vi.mocked(zod).mockReturnValue({} as any);
292+
293+
const mockRequest = new Request('http://localhost', {
294+
method: 'POST',
295+
body: new URLSearchParams({}).toString(),
296+
headers: {
297+
'Content-Type': 'application/x-www-form-urlencoded',
298+
},
299+
});
300+
301+
const result = await validateAuthFormWithFallback(mockRequest);
302+
303+
expect(result.valid).toBe(false);
304+
expect(result.posted).toBe(true);
305+
expect(result.errors).toMatchObject({
306+
_form: ['ログインできませんでした。'],
307+
});
308+
expect(result.message).toBe(
309+
'サーバでエラーが発生しました。本サービスの開発・運営チームまでご連絡ください。',
310+
);
311+
expect(result.constraints).toBeDefined();
312+
expect(result.shape).toBeDefined();
313+
});
314+
});
315+
});
316+
317+
// HACK: Environment dependent tests are currently disabled due to
318+
// complexity in mocking import.meta.env.DEV in vitest
319+
11320
const adminId = '1';
12321
const userId1 = '2';
13322
const userId2 = '3';

0 commit comments

Comments
 (0)