Skip to content

Commit e136375

Browse files
Test: add vitest tests for GET and PUT /api/profiles/me routes (#76)
* Vitest * test: add vitest tests for profile routes
1 parent 25e5abe commit e136375

1 file changed

Lines changed: 97 additions & 33 deletions

File tree

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,103 @@
11
import { 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

Comments
 (0)