UNPKG

@accounter/server

Version:
162 lines • 7.43 kB
import { GraphQLError } from 'graphql'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { invitationsResolvers } from '../resolvers/invitations.resolver.js'; import { AuthContextProvider } from '../providers/auth-context.provider.js'; import { Auth0ManagementProvider } from '../providers/auth0-management.provider.js'; import { InvitationsProvider } from '../providers/invitations.provider.js'; const createInvitation = invitationsResolvers.Mutation?.createInvitation; const mockInfo = {}; describe('createInvitation resolver', () => { let mockAuthProvider; let mockAuth0ManagementProvider; let mockInvitationsProvider; beforeEach(() => { mockAuthProvider = { getAuthContext: vi.fn().mockResolvedValue({ authType: 'jwt', user: { userId: 'user-123', roleId: 'business_owner', email: 'owner@example.com', permissions: [], emailVerified: true, permissionsVersion: 1, auth0UserId: 'auth0|owner-123', }, tenant: { businessId: 'business-123', roleId: 'business_owner', }, }), }; mockAuth0ManagementProvider = { createBlockedUser: vi.fn().mockResolvedValue('auth0|new-user-123'), deleteUser: vi.fn().mockResolvedValue(undefined), }; mockInvitationsProvider = { getInvitationByEmailLoader: { load: vi.fn().mockResolvedValue([]), }, insertInvitation: vi.fn().mockResolvedValue({ id: 'inv-123', email: 'new.user@example.com', roleId: 'employee', expiresAt: new Date('2030-01-01T00:00:00.000Z'), token: 'invitation-token-abc', }), }; }); afterEach(() => { vi.clearAllMocks(); }); function createContext() { return { injector: { get(token) { if (token === AuthContextProvider) { return mockAuthProvider; } if (token === Auth0ManagementProvider) { return mockAuth0ManagementProvider; } if (token === InvitationsProvider) { return mockInvitationsProvider; } throw new Error(`Unexpected token requested: ${String(token)}`); }, }, }; } it('business_owner can create invitation (happy path)', async () => { const result = await createInvitation({}, { email: 'new.user@example.com', roleId: 'employee' }, createContext(), mockInfo); expect(result).toEqual({ id: 'inv-123', email: 'new.user@example.com', roleId: 'employee', expiresAt: new Date('2030-01-01T00:00:00.000Z'), token: 'invitation-token-abc', }); }); it('Auth0 Management API called with correct parameters (mock)', async () => { await createInvitation({}, { email: 'INVITEE@EXAMPLE.COM', roleId: 'scraper' }, createContext(), mockInfo); expect(mockAuth0ManagementProvider.createBlockedUser).toHaveBeenCalledWith('invitee@example.com'); }); it('invitation record inserted with correct fields', async () => { await createInvitation({}, { email: 'new.user@example.com', roleId: 'accountant' }, createContext(), mockInfo); expect(mockInvitationsProvider.insertInvitation).toHaveBeenCalledTimes(1); const call = mockInvitationsProvider.insertInvitation.mock.calls[0][0]; expect(call.email).toBe('new.user@example.com'); expect(call.roleId).toBe('accountant'); expect(call.auth0UserId).toBe('auth0|new-user-123'); expect(call.invitedByUserId).toBe('user-123'); expect(call.ownerId).toBe('business-123'); }); it('cleans up Auth0 user when DB insert fails', async () => { const dbError = new Error('DB insert failed'); mockInvitationsProvider.insertInvitation.mockRejectedValueOnce(dbError); await expect(createInvitation({}, { email: 'new.user@example.com', roleId: 'employee' }, createContext(), mockInfo)).rejects.toBe(dbError); expect(mockAuth0ManagementProvider.deleteUser).toHaveBeenCalledWith('auth0|new-user-123'); }); it('duplicate active invitation rejected', async () => { mockInvitationsProvider.getInvitationByEmailLoader.load.mockResolvedValueOnce([ { id: 'existing-invitation', email: 'new.user@example.com', }, ]); await expect(createInvitation({}, { email: 'new.user@example.com', roleId: 'employee' }, createContext(), mockInfo)).rejects.toMatchObject({ extensions: { code: 'INVITATION_ALREADY_PENDING' }, }); expect(mockAuth0ManagementProvider.createBlockedUser).not.toHaveBeenCalled(); expect(mockInvitationsProvider.insertInvitation).not.toHaveBeenCalled(); }); it('Auth0 rate limit handled gracefully (mocked 429)', async () => { mockAuth0ManagementProvider.createBlockedUser.mockRejectedValue({ statusCode: 429, retryAfter: 60, message: 'Too many requests', }); await expect(createInvitation({}, { email: 'new.user@example.com', roleId: 'employee' }, createContext(), mockInfo)).rejects.toMatchObject({ extensions: { code: 'RATE_LIMITED', retryAfter: 60, }, }); expect(mockInvitationsProvider.insertInvitation).not.toHaveBeenCalled(); }); it('Auth0 user already exists returns USER_ALREADY_EXISTS', async () => { mockAuth0ManagementProvider.createBlockedUser.mockRejectedValue({ statusCode: 409, message: 'The user already exists', }); await expect(createInvitation({}, { email: 'new.user@example.com', roleId: 'employee' }, createContext(), mockInfo)).rejects.toMatchObject({ extensions: { code: 'USER_ALREADY_EXISTS', }, }); expect(mockInvitationsProvider.insertInvitation).not.toHaveBeenCalled(); }); it('employee calling mutation gets FORBIDDEN', async () => { mockAuthProvider.getAuthContext.mockResolvedValueOnce({ authType: 'jwt', user: { userId: 'user-employee', roleId: 'employee', email: 'employee@example.com', permissions: [], emailVerified: true, permissionsVersion: 1, auth0UserId: 'auth0|employee', }, tenant: { businessId: 'business-123', roleId: 'employee', }, }); await expect(createInvitation({}, { email: 'new.user@example.com', roleId: 'employee' }, createContext(), mockInfo)).rejects.toSatisfy(error => { return error instanceof GraphQLError && error.extensions?.code === 'FORBIDDEN'; }); expect(mockInvitationsProvider.insertInvitation).not.toHaveBeenCalled(); }); }); //# sourceMappingURL=create-invitation.test.js.map