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' ;
313import type {
414 Authorship ,
515 AuthorshipForRead ,
@@ -8,6 +18,305 @@ import type {
818} from '$lib/types/authorship' ;
919import { 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+
11320const adminId = '1' ;
12321const userId1 = '2' ;
13322const userId2 = '3' ;
0 commit comments