11import { describe , it , expect , beforeEach , vi } from 'vitest' ;
2+ import Fastify from 'fastify' ;
3+ import { profileRoutes } from '../routes/profiles.js' ;
24
3- // Mock test for duplicate username check
4- // Note: This test verifies the expected behavior of the /api/profiles/me PUT endpoint
5- // when attempting to change username to one that's already taken.
6- //
7- // The actual implementation in profiles.ts (lines 54-63) already handles this correctly:
8- // - Checks for existing username with different user ID
9- // - Returns 409 status with { error: "Username already taken" }
10- //
11- // Concurrency note: The current implementation uses a simple findFirst query.
12- // For production, consider adding a timestamp/version field to handle race conditions
13- // where two users might try to claim the same username simultaneously.
14-
15- describe ( 'PUT /api/profiles/me - Duplicate Username' , ( ) => {
16- // This test would require setting up the full Fastify app with test database
17- // For now, documenting the expected behavior based on profiles.ts implementation
18-
19- it ( 'should return 409 with error "Username already taken" when username exists' , async ( ) => {
20- // Expected behavior (from profiles.ts lines 54-63):
21- // const existing = await app.prisma.user.findFirst({
22- // where: {
23- // username: parsed.data.username,
24- // NOT: { id: userId },
25- // },
26- // });
27- // if (existing) {
28- // return reply.status(409).send({ error: 'Username already taken' });
29- // }
30-
31- // Expected response:
32- expect ( true ) . toBe ( true ) ; // Placeholder - actual test needs full app setup
5+ const mockUser = {
6+ id : 'user-123' ,
7+ email : 'test@example.com' ,
8+ username : 'testuser' ,
9+ displayName : 'Test User' ,
10+ bio : null ,
11+ pronouns : null ,
12+ role : null ,
13+ company : null ,
14+ avatarUrl : null ,
15+ accentColor : '#ffffff' ,
16+ platformLinks : [ ] ,
17+ cards : [ ] ,
18+ provider : 'github' ,
19+ providerId : 'gh-123' ,
20+ } ;
21+
22+ const mockPrisma = {
23+ user : {
24+ findUnique : vi . fn ( ) ,
25+ findFirst : vi . fn ( ) ,
26+ update : vi . fn ( ) ,
27+ } ,
28+ } ;
29+
30+ async function buildApp ( ) {
31+ const app = Fastify ( ) ;
32+ app . decorate ( 'prisma' , mockPrisma ) ;
33+ app . decorate ( 'authenticate' , async ( request : any ) => {
34+ request . user = { id : 'user-123' } ;
35+ } ) ;
36+ app . register ( profileRoutes , { prefix : '/api/profiles' } ) ;
37+ await app . ready ( ) ;
38+ return app ;
39+ }
40+
41+ describe ( 'GET /api/profiles/me' , ( ) => {
42+ beforeEach ( ( ) => vi . clearAllMocks ( ) ) ;
43+
44+ it ( 'should return user profile with displayName' , async ( ) => {
45+ mockPrisma . user . findUnique . mockResolvedValue ( mockUser ) ;
46+ const app = await buildApp ( ) ;
47+ const res = await app . inject ( { method : 'GET' , url : '/api/profiles/me' } ) ;
48+ expect ( res . statusCode ) . toBe ( 200 ) ;
49+ const body = res . json ( ) ;
50+ expect ( body . displayName ) . toBe ( 'Test User' ) ;
51+ expect ( body . email ) . toBe ( 'test@example.com' ) ;
52+ expect ( body . provider ) . toBeUndefined ( ) ;
53+ expect ( body . providerId ) . toBeUndefined ( ) ;
54+ } ) ;
55+
56+ it ( 'should return 404 if user not found' , async ( ) => {
57+ mockPrisma . user . findUnique . mockResolvedValue ( null ) ;
58+ const app = await buildApp ( ) ;
59+ const res = await app . inject ( { method : 'GET' , url : '/api/profiles/me' } ) ;
60+ expect ( res . statusCode ) . toBe ( 404 ) ;
61+ expect ( res . json ( ) . error ) . toBe ( 'User not found' ) ;
62+ } ) ;
63+ } ) ;
64+
65+ describe ( 'PUT /api/profiles/me' , ( ) => {
66+ beforeEach ( ( ) => vi . clearAllMocks ( ) ) ;
67+
68+ it ( 'should update profile and return updated data' , async ( ) => {
69+ mockPrisma . user . findFirst . mockResolvedValue ( null ) ;
70+ mockPrisma . user . update . mockResolvedValue ( { ...mockUser , displayName : 'Updated Name' } ) ;
71+ const app = await buildApp ( ) ;
72+ const res = await app . inject ( {
73+ method : 'PUT' ,
74+ url : '/api/profiles/me' ,
75+ payload : { displayName : 'Updated Name' } ,
76+ } ) ;
77+ expect ( res . statusCode ) . toBe ( 200 ) ;
78+ expect ( res . json ( ) . displayName ) . toBe ( 'Updated Name' ) ;
79+ } ) ;
80+
81+ it ( 'should return 400 for invalid accentColor' , async ( ) => {
82+ const app = await buildApp ( ) ;
83+ const res = await app . inject ( {
84+ method : 'PUT' ,
85+ url : '/api/profiles/me' ,
86+ payload : { accentColor : 'notacolor' } ,
87+ } ) ;
88+ expect ( res . statusCode ) . toBe ( 400 ) ;
89+ expect ( res . json ( ) . error ) . toBe ( 'Validation failed' ) ;
3390 } ) ;
3491
35- it ( 'should allow username change when username is available' , async ( ) => {
36- // Expected: 200 OK with updated profile
37- expect ( true ) . toBe ( true ) ;
92+ it ( 'should return 409 if username is already taken' , async ( ) => {
93+ mockPrisma . user . findFirst . mockResolvedValue ( { id : 'other-user' } ) ;
94+ const app = await buildApp ( ) ;
95+ const res = await app . inject ( {
96+ method : 'PUT' ,
97+ url : '/api/profiles/me' ,
98+ payload : { username : 'takenuser' } ,
99+ } ) ;
100+ expect ( res . statusCode ) . toBe ( 409 ) ;
101+ expect ( res . json ( ) . error ) . toBe ( 'Username already taken' ) ;
38102 } ) ;
39103} ) ;
0 commit comments