11import type { UserDocument } from '@nbw/database' ;
2- import { GetUser , PageQueryDTO } from '@nbw/database' ;
3- import { HttpException } from '@nestjs/common' ;
2+ import {
3+ GetUser ,
4+ PageQueryDTO ,
5+ UpdateUsernameDto ,
6+ UserDto ,
7+ } from '@nbw/database' ;
8+ import { HttpException , HttpStatus } from '@nestjs/common' ;
49import { Test , TestingModule } from '@nestjs/testing' ;
510
611import { UserController } from './user.controller' ;
712import { UserService } from './user.service' ;
813
914const mockUserService = {
10- getUserByEmailOrId : jest . fn ( ) ,
15+ findByEmail : jest . fn ( ) ,
16+ findByID : jest . fn ( ) ,
1117 getUserPaginated : jest . fn ( ) ,
12- getSelfUserData : jest . fn ( ) ,
18+ normalizeUsername : jest . fn ( ) ,
19+ usernameExists : jest . fn ( ) ,
1320} ;
1421
1522describe ( 'UserController' , ( ) => {
@@ -36,28 +43,59 @@ describe('UserController', () => {
3643 } ) ;
3744
3845 describe ( 'getUser' , ( ) => {
39- it ( 'should return user data by email or ID ' , async ( ) => {
46+ it ( 'should return user data by email' , async ( ) => {
4047 const query : GetUser = {
414842- username : 'test-username' ,
49+ } ;
50+
51+ const user = { email :
'[email protected] ' } ; 52+
53+ mockUserService . findByEmail . mockResolvedValueOnce ( user ) ;
54+
55+ const result = await userController . getUser ( query ) ;
56+
57+ expect ( result ) . toEqual ( user ) ;
58+ expect ( userService . findByEmail ) . toHaveBeenCalledWith ( query . email ) ;
59+ } ) ;
60+
61+ it ( 'should return user data by ID' , async ( ) => {
62+ const query : GetUser = {
4363 id : 'test-id' ,
4464 } ;
4565
46- const user = { email : 'test@example.com ' } ;
66+ const user = { _id : 'test-id ' } ;
4767
48- mockUserService . getUserByEmailOrId . mockResolvedValueOnce ( user ) ;
68+ mockUserService . findByID . mockResolvedValueOnce ( user ) ;
4969
5070 const result = await userController . getUser ( query ) ;
5171
5272 expect ( result ) . toEqual ( user ) ;
53- expect ( userService . getUserByEmailOrId ) . toHaveBeenCalledWith ( query ) ;
73+ expect ( userService . findByID ) . toHaveBeenCalledWith ( query . id ) ;
74+ } ) ;
75+
76+ it ( 'should throw an error if username is provided' , async ( ) => {
77+ const query : GetUser = {
78+ username : 'test-username' ,
79+ } ;
80+
81+ await expect ( userController . getUser ( query ) ) . rejects . toThrow (
82+ HttpException ,
83+ ) ;
84+ } ) ;
85+
86+ it ( 'should throw an error if neither email nor ID is provided' , async ( ) => {
87+ const query : GetUser = { } ;
88+
89+ await expect ( userController . getUser ( query ) ) . rejects . toThrow (
90+ HttpException ,
91+ ) ;
5492 } ) ;
5593 } ) ;
5694
5795 describe ( 'getUserPaginated' , ( ) => {
5896 it ( 'should return paginated user data' , async ( ) => {
5997 const query : PageQueryDTO = { page : 1 , limit : 10 } ;
60- const paginatedUsers = { items : [ ] , total : 0 } ;
98+ const paginatedUsers = { users : [ ] , total : 0 , page : 1 , limit : 10 } ;
6199
62100 mockUserService . getUserPaginated . mockResolvedValueOnce ( paginatedUsers ) ;
63101
@@ -71,20 +109,209 @@ describe('UserController', () => {
71109 describe ( 'getMe' , ( ) => {
72110 it ( 'should return the token owner data' , async ( ) => {
73111 const user : UserDocument = { _id : 'test-user-id' } as UserDocument ;
74- const userData = { _id :
'test-user-id' , email :
'[email protected] ' } ; 112+ const userData = {
113+ _id : 'test-user-id' ,
114+ 115+ lastSeen : new Date ( ) ,
116+ loginStreak : 1 ,
117+ maxLoginStreak : 1 ,
118+ loginCount : 1 ,
119+ save : jest . fn ( ) . mockResolvedValue ( true ) ,
120+ } as unknown as UserDocument ;
75121
76- mockUserService . getSelfUserData . mockResolvedValueOnce ( userData ) ;
122+ mockUserService . findByID . mockResolvedValueOnce ( userData ) ;
77123
78124 const result = await userController . getMe ( user ) ;
79125
80126 expect ( result ) . toEqual ( userData ) ;
81- expect ( userService . getSelfUserData ) . toHaveBeenCalledWith ( user ) ;
127+ expect ( userService . findByID ) . toHaveBeenCalledWith ( user . _id . toString ( ) ) ;
82128 } ) ;
83129
84130 it ( 'should handle null user' , async ( ) => {
85131 const user = null ;
86132
87133 await expect ( userController . getMe ( user ) ) . rejects . toThrow ( HttpException ) ;
88134 } ) ;
135+
136+ it ( 'should throw an error if user is not found' , async ( ) => {
137+ const user : UserDocument = { _id : 'test-user-id' } as UserDocument ;
138+
139+ mockUserService . findByID . mockResolvedValueOnce ( null ) ;
140+
141+ await expect ( userController . getMe ( user ) ) . rejects . toThrow ( HttpException ) ;
142+ } ) ;
143+
144+ it ( 'should update lastSeen and increment loginStreak if lastSeen is before today' , async ( ) => {
145+ const user : UserDocument = { _id : 'test-user-id' } as UserDocument ;
146+ const yesterday = new Date ( ) ;
147+ yesterday . setDate ( yesterday . getDate ( ) - 1 ) ;
148+ yesterday . setHours ( 0 , 0 , 0 , 0 ) ;
149+
150+ const userData = {
151+ _id : 'test-user-id' ,
152+ lastSeen : yesterday ,
153+ loginStreak : 1 ,
154+ maxLoginStreak : 1 ,
155+ loginCount : 1 ,
156+ save : jest . fn ( ) . mockResolvedValue ( true ) ,
157+ } as unknown as UserDocument ;
158+
159+ mockUserService . findByID . mockResolvedValueOnce ( userData ) ;
160+
161+ const result = await userController . getMe ( user ) ;
162+
163+ expect ( result . lastSeen ) . toBeInstanceOf ( Date ) ;
164+ expect ( result . loginStreak ) . toBe ( 2 ) ;
165+ expect ( result . loginCount ) . toBe ( 2 ) ;
166+ expect ( userData . save ) . toHaveBeenCalled ( ) ;
167+ } ) ;
168+
169+ it ( 'should not update lastSeen or increment loginStreak if lastSeen is today' , async ( ) => {
170+ const user : UserDocument = { _id : 'test-user-id' } as UserDocument ;
171+ const today = new Date ( ) ;
172+ today . setHours ( 0 , 0 , 0 , 0 ) ;
173+
174+ const userData = {
175+ _id : 'test-user-id' ,
176+ lastSeen : today ,
177+ loginStreak : 1 ,
178+ maxLoginStreak : 1 ,
179+ loginCount : 1 ,
180+ save : jest . fn ( ) . mockResolvedValue ( true ) ,
181+ } as unknown as UserDocument ;
182+
183+ mockUserService . findByID . mockResolvedValueOnce ( userData ) ;
184+
185+ const result = await userController . getMe ( user ) ;
186+
187+ expect ( result . lastSeen ) . toEqual ( today ) ;
188+ expect ( result . loginStreak ) . toBe ( 1 ) ;
189+ expect ( result . loginCount ) . toBe ( 1 ) ;
190+ expect ( userData . save ) . not . toHaveBeenCalled ( ) ;
191+ } ) ;
192+
193+ it ( 'should reset loginStreak if lastSeen is not yesterday' , async ( ) => {
194+ const user : UserDocument = { _id : 'test-user-id' } as UserDocument ;
195+ const twoDaysAgo = new Date ( ) ;
196+ twoDaysAgo . setDate ( twoDaysAgo . getDate ( ) - 2 ) ;
197+ twoDaysAgo . setHours ( 0 , 0 , 0 , 0 ) ;
198+
199+ const userData = {
200+ _id : 'test-user-id' ,
201+ lastSeen : twoDaysAgo ,
202+ loginStreak : 5 ,
203+ maxLoginStreak : 5 ,
204+ loginCount : 1 ,
205+ save : jest . fn ( ) . mockResolvedValue ( true ) ,
206+ } as unknown as UserDocument ;
207+
208+ mockUserService . findByID . mockResolvedValueOnce ( userData ) ;
209+
210+ const result = await userController . getMe ( user ) ;
211+
212+ expect ( result . lastSeen ) . toBeInstanceOf ( Date ) ;
213+ expect ( result . loginStreak ) . toBe ( 1 ) ;
214+ expect ( userData . save ) . toHaveBeenCalled ( ) ;
215+ } ) ;
216+
217+ it ( 'should increment maxLoginStreak if login streak exceeds max' , async ( ) => {
218+ const user : UserDocument = { _id : 'test-user-id' } as UserDocument ;
219+ const yesterday = new Date ( ) ;
220+ yesterday . setDate ( yesterday . getDate ( ) - 1 ) ;
221+ yesterday . setHours ( 0 , 0 , 0 , 0 ) ;
222+
223+ const userData = {
224+ _id : 'test-user-id' ,
225+ lastSeen : yesterday ,
226+ loginStreak : 8 ,
227+ maxLoginStreak : 8 ,
228+ loginCount : 1 ,
229+ save : jest . fn ( ) . mockResolvedValue ( true ) ,
230+ } as unknown as UserDocument ;
231+
232+ mockUserService . findByID . mockResolvedValueOnce ( userData ) ;
233+
234+ const result = await userController . getMe ( user ) ;
235+
236+ expect ( result . maxLoginStreak ) . toBe ( 9 ) ;
237+ expect ( userData . save ) . toHaveBeenCalled ( ) ;
238+ } ) ;
239+ } ) ;
240+
241+ describe ( 'updateUsername' , ( ) => {
242+ it ( 'should update the username' , async ( ) => {
243+ const user : UserDocument = {
244+ _id : 'test-user-id' ,
245+ username : 'olduser' ,
246+ save : jest . fn ( ) . mockResolvedValue ( true ) ,
247+ } as unknown as UserDocument ;
248+ const body : UpdateUsernameDto = { username : 'newuser' } ;
249+ const normalizedUsername = 'newuser' ;
250+
251+ mockUserService . normalizeUsername . mockReturnValue ( normalizedUsername ) ;
252+ mockUserService . usernameExists . mockResolvedValue ( false ) ;
253+
254+ // Mock UserDto.fromEntity
255+ jest . spyOn ( UserDto , 'fromEntity' ) . mockReturnValue ( {
256+ username : normalizedUsername ,
257+ publicName : user . publicName ,
258+ email : user . email ,
259+ } ) ;
260+
261+ const result = await userController . updateUsername ( user , body ) ;
262+
263+ expect ( user . username ) . toBe ( normalizedUsername ) ;
264+ expect ( user . lastEdited ) . toBeInstanceOf ( Date ) ;
265+ expect ( user . save ) . toHaveBeenCalled ( ) ;
266+ expect ( userService . normalizeUsername ) . toHaveBeenCalledWith ( body . username ) ;
267+ expect ( userService . usernameExists ) . toHaveBeenCalledWith (
268+ normalizedUsername ,
269+ ) ;
270+ expect ( result . username ) . toBe ( normalizedUsername ) ;
271+ } ) ;
272+
273+ it ( 'should throw an error if username already exists' , async ( ) => {
274+ const user : UserDocument = {
275+ _id : 'test-user-id' ,
276+ username : 'olduser' ,
277+ } as unknown as UserDocument ;
278+ const body : UpdateUsernameDto = { username : 'existinguser' } ;
279+ const normalizedUsername = 'existinguser' ;
280+
281+ mockUserService . normalizeUsername . mockReturnValue ( normalizedUsername ) ;
282+ mockUserService . usernameExists . mockResolvedValue ( true ) ;
283+
284+ await expect ( userController . updateUsername ( user , body ) ) . rejects . toThrow (
285+ HttpException ,
286+ ) ;
287+ expect ( userService . usernameExists ) . toHaveBeenCalledWith (
288+ normalizedUsername ,
289+ ) ;
290+ } ) ;
291+
292+ it ( 'should throw an error if username is the same' , async ( ) => {
293+ const user : UserDocument = {
294+ _id : 'test-user-id' ,
295+ username : 'sameuser' ,
296+ } as unknown as UserDocument ;
297+ const body : UpdateUsernameDto = { username : 'sameuser' } ;
298+ const normalizedUsername = 'sameuser' ;
299+
300+ mockUserService . normalizeUsername . mockReturnValue ( normalizedUsername ) ;
301+ mockUserService . usernameExists . mockResolvedValue ( false ) ;
302+
303+ await expect ( userController . updateUsername ( user , body ) ) . rejects . toThrow (
304+ HttpException ,
305+ ) ;
306+ } ) ;
307+
308+ it ( 'should handle null user' , async ( ) => {
309+ const user = null ;
310+ const body : UpdateUsernameDto = { username : 'newuser' } ;
311+
312+ await expect ( userController . updateUsername ( user , body ) ) . rejects . toThrow (
313+ HttpException ,
314+ ) ;
315+ } ) ;
89316 } ) ;
90317} ) ;
0 commit comments