UNPKG

@accounter/server

Version:
284 lines (239 loc) • 7.79 kB
import { createSchema, createYoga } from 'graphql-yoga'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { authPlugin, type RawAuth } from '../auth-plugin.js'; import { handleDevBypassAuth } from '../../modules/auth/providers/auth-context.provider.js'; vi.mock('../../modules/auth/providers/auth-context.provider.js', () => ({ handleDevBypassAuth: vi.fn(), })); describe('authPlugin', () => { const originalAllowDevAuth = process.env.ALLOW_DEV_AUTH; beforeEach(() => { vi.clearAllMocks(); delete process.env.ALLOW_DEV_AUTH; }); afterEach(() => { if (originalAllowDevAuth === undefined) { delete process.env.ALLOW_DEV_AUTH; } else { process.env.ALLOW_DEV_AUTH = originalAllowDevAuth; } }); const createTestYoga = () => { const mockPool = { query: vi.fn(), }; return createYoga({ plugins: [authPlugin()], schema: createSchema({ typeDefs: `type Query { test: String }`, resolvers: { Query: { test: (_: unknown, __: unknown, context: { rawAuth: RawAuth, pool: typeof mockPool }) => { return JSON.stringify(context.rawAuth); }, }, }, }), context: ({ request }) => ({ request, pool: mockPool, rawAuth: { authType: null, token: null } as RawAuth, }), }); }; it('should extract JWT token from Authorization header', async () => { const yoga = createTestYoga(); const token = 'valid.jwt.token'; const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(rawAuth).toEqual({ authType: 'jwt', token, }); }); it('should extract API key from X-API-Key header', async () => { const yoga = createTestYoga(); const key = 'active-api-key'; const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': key, }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(rawAuth).toEqual({ authType: 'apiKey', token: key, }); }); it('should prioritize JWT when both headers are present', async () => { const yoga = createTestYoga(); const token = 'jwt.token.priority'; const key = 'ignored-api-key'; const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'X-API-Key': key, }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(rawAuth).toEqual({ authType: 'jwt', token, }); }); it('should return null auth for missing headers', async () => { const yoga = createTestYoga(); const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(rawAuth).toEqual({ authType: null, token: null, }); }); it('should iterate gracefully on malformed Authorization header', async () => { const yoga = createTestYoga(); const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Basic somecreds', }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); // Should return null unless X-API-Key is also present (checked in logic) expect(rawAuth).toEqual({ authType: null, token: null, }); }); it('should handle empty tokens as null', async () => { const yoga = createTestYoga(); const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ', // Empty token part 'X-API-Key': ' ', // Whitespace only }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(rawAuth).toEqual({ authType: null, token: null, }); }); it('should ignore X-Dev-Auth when ALLOW_DEV_AUTH is missing', async () => { vi.mocked(handleDevBypassAuth).mockResolvedValue({ authType: 'devBypass', token: 'dev-user', tenant: { businessId: 'biz-1' }, user: { userId: 'dev-user', email: 'dev-user', roleId: 'business_owner', permissions: [], emailVerified: true, permissionsVersion: 0, }, }); const yoga = createTestYoga(); const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Dev-Auth': 'dev-user', }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(handleDevBypassAuth).not.toHaveBeenCalled(); expect(rawAuth).toEqual({ authType: null, token: null, }); }); it('should ignore X-Dev-Auth when ALLOW_DEV_AUTH is false', async () => { process.env.ALLOW_DEV_AUTH = '0'; const yoga = createTestYoga(); const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Dev-Auth': 'dev-user', 'X-API-Key': 'fallback-key', }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(handleDevBypassAuth).not.toHaveBeenCalled(); expect(rawAuth).toEqual({ authType: 'apiKey', token: 'fallback-key', }); }); it('should call handleDevBypassAuth when ALLOW_DEV_AUTH=1 and X-Dev-Auth is present', async () => { process.env.ALLOW_DEV_AUTH = '1'; vi.mocked(handleDevBypassAuth).mockResolvedValue({ authType: 'devBypass', token: 'dev-user', tenant: { businessId: 'biz-1' }, user: { userId: 'dev-user', email: 'dev-user', roleId: 'business_owner', permissions: [], emailVerified: true, permissionsVersion: 0, }, }); const yoga = createTestYoga(); const response = await yoga.fetch('http://localhost:4000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Dev-Auth': 'dev-user', Authorization: 'Bearer ignored.jwt.token', }, body: JSON.stringify({ query: '{ test }' }), }); const result = await response.json(); const rawAuth = JSON.parse(result.data.test); expect(handleDevBypassAuth).toHaveBeenCalledWith( expect.objectContaining({ query: expect.any(Function) }), 'dev-user', ); expect(rawAuth).toEqual({ authType: 'devBypass', token: 'dev-user', }); }); });