UNPKG

@accounter/server

Version:
136 lines 5.35 kB
import { GraphQLError } from 'graphql'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ApiKeysProvider } from '../providers/api-keys.provider.js'; import { apiKeysResolvers } from '../resolvers/api-keys.resolver.js'; const listApiKeysResolver = apiKeysResolvers.Query?.listApiKeys; const revokeApiKeyResolver = apiKeysResolvers.Mutation?.revokeApiKey; const mockInfo = {}; describe('API key management', () => { let mockAuthProvider; let mockDb; let mockAuditProvider; let apiKeysProvider; beforeEach(() => { mockAuthProvider = { getAuthContext: vi.fn().mockResolvedValue({ authType: 'jwt', token: 'owner-token', user: { userId: 'owner-user-id', roleId: 'business_owner', email: 'owner@example.com', auth0UserId: 'auth0|owner-user-id', permissions: [], emailVerified: true, permissionsVersion: 1, }, tenant: { businessId: 'business-123', roleId: 'business_owner', }, }), }; mockDb = { query: vi.fn(), transaction: vi.fn().mockImplementation(async (callback) => { return callback(mockDb); }), }; mockAuditProvider = { log: vi.fn().mockResolvedValue(undefined), }; apiKeysProvider = new ApiKeysProvider(mockDb, mockAuthProvider, mockAuditProvider); }); afterEach(() => { vi.clearAllMocks(); }); function createContext() { return { injector: { get(token) { if (token === ApiKeysProvider) { return apiKeysProvider; } throw new Error(`Unexpected token requested: ${String(token)}`); }, }, }; } it('listApiKeys returns active keys sorted by creation time', async () => { mockDb.query.mockResolvedValueOnce({ rows: [ { id: 'key-2', name: 'newer key', role_id: 'scraper', last_used_at: new Date('2030-02-01T00:00:00.000Z'), created_at: new Date('2030-02-01T00:00:00.000Z'), }, { id: 'key-1', name: 'older key', role_id: 'employee', last_used_at: null, created_at: new Date('2030-01-01T00:00:00.000Z'), }, ], rowCount: 2, }); const result = await listApiKeysResolver({}, {}, createContext(), mockInfo); expect(result).toEqual([ { id: 'key-2', name: 'newer key', roleId: 'scraper', lastUsedAt: new Date('2030-02-01T00:00:00.000Z'), createdAt: new Date('2030-02-01T00:00:00.000Z'), }, { id: 'key-1', name: 'older key', roleId: 'employee', lastUsedAt: null, createdAt: new Date('2030-01-01T00:00:00.000Z'), }, ]); }); it('revokeApiKey soft-deletes and writes API_KEY_REVOKED audit log', async () => { mockDb.query.mockResolvedValueOnce({ rows: [{ id: 'key-1' }], rowCount: 1, }); const revoked = await revokeApiKeyResolver({}, { id: 'key-1' }, createContext(), mockInfo); expect(revoked).toBe(true); expect(mockAuditProvider.log).toHaveBeenCalledTimes(1); const auditEvent = mockAuditProvider.log.mock.calls[0][0]; expect(JSON.stringify(auditEvent)).not.toContain({ action: 'API_KEY_REVOKED', entity: 'ApiKey', entityId: 'key-1', ownerId: 'business-123', userId: 'owner-user-id', }); }); it('revokeApiKey returns false when key does not exist or already revoked', async () => { mockDb.query.mockResolvedValueOnce({ rows: [], rowCount: 0, }); const revoked = await revokeApiKeyResolver({}, { id: 'missing-key' }, createContext(), mockInfo); expect(revoked).toBe(false); expect(mockAuditProvider.log).not.toHaveBeenCalled(); }); it('listApiKeys wraps unexpected failures', async () => { mockDb.query.mockRejectedValueOnce(new Error('db down')); await expect(listApiKeysResolver({}, {}, createContext(), mockInfo)).rejects.toSatisfy(error => { return error instanceof GraphQLError && error.extensions?.code === 'API_KEY_LIST_FAILED'; }); }); it('revokeApiKey wraps unexpected failures', async () => { mockDb.query.mockRejectedValueOnce(new Error('db down')); await expect(revokeApiKeyResolver({}, { id: 'key-1' }, createContext(), mockInfo)).rejects.toSatisfy(error => { return (error instanceof GraphQLError && error.extensions?.code === 'API_KEY_REVOCATION_FAILED'); }); }); }); //# sourceMappingURL=api-key-management.test.js.map