Files
mcpctl/src/mcpd/tests/auth-me.test.ts
Michal 86c5a61eaa feat: add userName tracking to audit events
- Add userName column to AuditEvent schema with index and migration
- Add GET /api/v1/auth/me endpoint returning current user identity
- AuditCollector auto-fills userName from session→user map, resolved
  lazily via /auth/me on first session creation
- Support userName and date range (from/to) filtering on audit events
  and sessions endpoints
- Audit console sidebar groups sessions by project → user
- Add date filter presets (d key: all/today/1h/24h/7d) to console
- Add scrolling and page up/down to sidebar navigation
- Tests: auth-me (4), audit-username collector (4), route filters (2),
  smoke tests (2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:18:58 +00:00

153 lines
4.4 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from 'vitest';
import Fastify from 'fastify';
import type { FastifyInstance } from 'fastify';
import { registerAuthRoutes } from '../src/routes/auth.js';
import { errorHandler } from '../src/middleware/error-handler.js';
import type { SafeUser } from '../src/repositories/user.repository.js';
let app: FastifyInstance;
afterEach(async () => {
if (app) await app.close();
});
function makeSafeUser(overrides?: Partial<SafeUser>): SafeUser {
return {
id: 'user-1',
email: 'michal@example.com',
name: 'Michal',
role: 'user',
provider: 'local',
externalId: null,
version: 1,
createdAt: new Date(),
updatedAt: new Date(),
...overrides,
};
}
function createMockDeps() {
return {
authService: {
login: vi.fn(async () => ({})),
logout: vi.fn(async () => {}),
findSession: vi.fn(async () => null),
impersonate: vi.fn(async () => ({})),
},
userService: {
count: vi.fn(async () => 1),
create: vi.fn(async () => makeSafeUser()),
list: vi.fn(async () => []),
getById: vi.fn(async () => makeSafeUser()),
getByEmail: vi.fn(async () => makeSafeUser()),
delete: vi.fn(async () => {}),
},
groupService: {
create: vi.fn(async () => ({})),
list: vi.fn(async () => []),
getById: vi.fn(async () => null),
getByName: vi.fn(async () => null),
update: vi.fn(async () => null),
delete: vi.fn(async () => {}),
},
rbacDefinitionService: {
create: vi.fn(async () => ({})),
list: vi.fn(async () => []),
getById: vi.fn(async () => null),
getByName: vi.fn(async () => null),
update: vi.fn(async () => null),
delete: vi.fn(async () => {}),
},
rbacService: {
canAccess: vi.fn(async () => false),
canRunOperation: vi.fn(async () => false),
getPermissions: vi.fn(async () => []),
},
};
}
describe('GET /api/v1/auth/me', () => {
it('returns user identity for authenticated request', async () => {
const deps = createMockDeps();
deps.authService.findSession.mockResolvedValue({
userId: 'user-1',
expiresAt: new Date(Date.now() + 86400_000),
});
deps.userService.getById.mockResolvedValue(makeSafeUser({ id: 'user-1', name: 'Michal', email: 'michal@example.com' }));
app = Fastify({ logger: false });
app.setErrorHandler(errorHandler);
registerAuthRoutes(app, deps as never);
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/auth/me',
headers: { authorization: 'Bearer valid-token' },
});
expect(res.statusCode).toBe(200);
const body = res.json<{ id: string; email: string; name: string | null }>();
expect(body.id).toBe('user-1');
expect(body.email).toBe('michal@example.com');
expect(body.name).toBe('Michal');
});
it('returns null name when user has no name', async () => {
const deps = createMockDeps();
deps.authService.findSession.mockResolvedValue({
userId: 'user-1',
expiresAt: new Date(Date.now() + 86400_000),
});
deps.userService.getById.mockResolvedValue(makeSafeUser({ name: null }));
app = Fastify({ logger: false });
app.setErrorHandler(errorHandler);
registerAuthRoutes(app, deps as never);
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/auth/me',
headers: { authorization: 'Bearer valid-token' },
});
expect(res.statusCode).toBe(200);
expect(res.json<{ name: string | null }>().name).toBeNull();
});
it('returns 401 for unauthenticated request', async () => {
const deps = createMockDeps();
app = Fastify({ logger: false });
app.setErrorHandler(errorHandler);
registerAuthRoutes(app, deps as never);
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/auth/me',
});
expect(res.statusCode).toBe(401);
});
it('returns 401 for invalid token', async () => {
const deps = createMockDeps();
deps.authService.findSession.mockResolvedValue(null);
app = Fastify({ logger: false });
app.setErrorHandler(errorHandler);
registerAuthRoutes(app, deps as never);
await app.ready();
const res = await app.inject({
method: 'GET',
url: '/api/v1/auth/me',
headers: { authorization: 'Bearer bad-token' },
});
expect(res.statusCode).toBe(401);
});
});