Files
BPOS-Service-Gateway/test/unit/keycloak-auth.test.ts
T

86 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Hono } from 'hono';
import { beforeAll, describe, expect, it } from 'vitest';
import { makeToken, setupTestJwks } from '../support/token';
import { requireAuth, requireRealmRole, requireService } from '../../src/shared/keycloak-auth';
function appWith(...mw: Parameters<Hono['use']>[1][]): Hono {
const app = new Hono();
for (const m of mw) app.use('/p/*', m);
app.get('/p/x', (c) => c.json({ user: c.get('user') }));
return app;
}
describe('keycloak-auth', () => {
beforeAll(async () => {
await setupTestJwks();
});
it('缺 Bearer → 401 unauthorized', async () => {
const res = await appWith(requireAuth()).request('/p/x');
expect(res.status).toBe(401);
expect(((await res.json()) as { error: string }).error).toBe('unauthorized');
});
it('合法 token → 200 且 user 就位', async () => {
const token = await makeToken({ sub: 'u-1', roles: ['user'] });
const res = await appWith(requireAuth()).request('/p/x', {
headers: { authorization: `Bearer ${token}` },
});
expect(res.status).toBe(200);
const body = (await res.json()) as { user: { id: string; roles: string[] } };
expect(body.user.id).toBe('u-1');
expect(body.user.roles).toContain('user');
});
it('aud 不含本服务 → 401 invalid_token 且 message 点名 audience mapper', async () => {
const token = await makeToken({ aud: 'some-other-service' });
const res = await appWith(requireAuth()).request('/p/x', {
headers: { authorization: `Bearer ${token}` },
});
expect(res.status).toBe(401);
expect(((await res.json()) as { message: string }).message).toContain('audience mapper');
});
it('过期 token → 401 且 message 为 token 已过期', async () => {
const token = await makeToken({ expiresIn: -60 });
const res = await appWith(requireAuth()).request('/p/x', {
headers: { authorization: `Bearer ${token}` },
});
expect(res.status).toBe(401);
expect(((await res.json()) as { message: string }).message).toContain('过期');
});
it('角色不够 → 403(401/403 语义分明)', async () => {
const token = await makeToken({ roles: ['user'] });
const res = await appWith(requireAuth(), requireRealmRole('admin')).request('/p/x', {
headers: { authorization: `Bearer ${token}` },
});
expect(res.status).toBe(403);
});
it('admin 通过 requireRealmRole(user, admin) 任一命中', async () => {
const token = await makeToken({ roles: ['admin'] });
const res = await appWith(requireAuth(), requireRealmRole('user', 'admin')).request('/p/x', {
headers: { authorization: `Bearer ${token}` },
});
expect(res.status).toBe(200);
});
it('requireService:service 角色+azp 白名单', async () => {
const svc = await makeToken({
roles: ['service'], azp: 'task-dispatcher', username: 'service-account-task-dispatcher',
});
const ok = await appWith(requireAuth(), requireService(['task-dispatcher'])).request('/p/x', {
headers: { authorization: `Bearer ${svc}` },
});
expect(ok.status).toBe(200);
const wrongAzp = await makeToken({
roles: ['service'], azp: 'agent-service', username: 'service-account-agent-service',
});
const deny = await appWith(requireAuth(), requireService(['task-dispatcher'])).request('/p/x', {
headers: { authorization: `Bearer ${wrongAzp}` },
});
expect(deny.status).toBe(403);
});
});