MeshiTrack/packages/api/tests/modules/users/users.routes.test.ts

136 lines
3.9 KiB
TypeScript
Raw Normal View History

2026-03-27 14:50:34 +09:00
import { describe, it, expect, vi, beforeEach } from 'vitest';
import Fastify from 'fastify';
import { fastifyAwilixPlugin } from '@fastify/awilix';
import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
// Mock jose for auth plugin
vi.mock('jose', () => ({
createRemoteJWKSet: vi.fn(() => 'mock-jwks'),
jwtVerify: vi.fn().mockResolvedValue({
payload: {
sub: 'kc-1',
email: 'test@example.com',
preferred_username: 'testuser',
realm_access: { roles: ['member'] },
householdIds: ['hh1'],
},
protectedHeader: { alg: 'RS256' },
key: {},
}),
}));
// Mock the users repository module with a real class
2026-03-28 08:19:48 +09:00
const { mockUpsertFromToken, mockFindByKeycloakId } = vi.hoisted(() => ({
mockUpsertFromToken: vi.fn(),
mockFindByKeycloakId: vi.fn(),
}));
2026-05-19 11:06:03 +09:00
vi.mock('../../../src/modules/users/users.repository.js', () => ({
2026-03-27 14:50:34 +09:00
UsersRepository: class MockUsersRepository {
upsertFromToken = mockUpsertFromToken;
2026-03-28 08:19:48 +09:00
findByKeycloakId = mockFindByKeycloakId;
2026-03-27 14:50:34 +09:00
},
}));
2026-05-19 11:06:03 +09:00
import authPlugin from '../../../src/plugins/auth.plugin.js';
import usersRoutes from '../../../src/modules/users/users.routes.js';
2026-03-27 14:50:34 +09:00
describe('users.routes', () => {
async function buildTestApp() {
const app = Fastify({ logger: false });
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
await app.register(fastifyAwilixPlugin, {
disposeOnClose: true,
disposeOnResponse: true,
strictBooleanEnforced: true,
});
await app.register(authPlugin);
await app.register(usersRoutes);
return app;
}
beforeEach(() => {
vi.clearAllMocks();
2026-03-28 08:19:48 +09:00
mockFindByKeycloakId.mockResolvedValue({ householdIds: ['hh1'] });
});
it('returns 404 when syncFromToken returns null', async () => {
mockUpsertFromToken.mockResolvedValue(null);
const app = await buildTestApp();
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/users/me',
headers: { authorization: 'Bearer valid-token' },
});
expect(res.statusCode).toBe(404);
await app.close();
});
it('handles ObjectId and Date objects in response', async () => {
const mockUser = {
_id: { toString: () => 'u-obj' },
keycloakId: 'kc-1',
displayName: 'testuser',
email: 'test@example.com',
householdIds: ['hh1'],
defaultHouseholdId: 'hh1',
createdAt: { toISOString: () => '2024-01-01T00:00:00.000Z' },
updatedAt: { toISOString: () => '2024-01-02T00:00:00.000Z' },
};
mockUpsertFromToken.mockResolvedValue(mockUser);
const app = await buildTestApp();
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/users/me',
headers: { authorization: 'Bearer valid-token' },
});
expect(res.statusCode).toBe(200);
const body = res.json();
expect(body._id).toBe('u-obj');
expect(body.createdAt).toBe('2024-01-01T00:00:00.000Z');
expect(body.defaultHouseholdId).toBe('hh1');
await app.close();
2026-03-27 14:50:34 +09:00
});
it('GET /api/v1/users/me syncs user from token and returns profile', async () => {
const mockUser = {
_id: 'u1',
keycloakId: 'kc-1',
displayName: 'testuser',
email: 'test@example.com',
householdIds: ['hh1'],
defaultHouseholdId: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
mockUpsertFromToken.mockResolvedValue(mockUser);
const app = await buildTestApp();
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/users/me',
headers: { authorization: 'Bearer valid-token' },
});
expect(res.statusCode).toBe(200);
const body = res.json();
expect(body.keycloakId).toBe('kc-1');
expect(body.email).toBe('test@example.com');
expect(mockUpsertFromToken).toHaveBeenCalledWith('kc-1', 'test@example.com', 'testuser');
await app.close();
});
});