/** * Team Service — unit tests * * Tests the in-place workspace member creation flow (no email invites): * 1. signUpInWorkspace → creates user + workspace member * 2. updateWorkspaceMember → set name * 3. updateWorkspaceMemberRole → assign role * 4. (optional) updateAgent → link SIP seat * * Platform GraphQL + SessionService are mocked. */ import { Test } from '@nestjs/testing'; import { TeamService, CreateMemberInput } from './team.service'; import { PlatformGraphqlService } from '../platform/platform-graphql.service'; import { SessionService } from '../auth/session.service'; describe('TeamService', () => { let service: TeamService; let platform: jest.Mocked; let session: jest.Mocked; const mockWorkspace = { id: '42424242-1c25-4d02-bf25-6aeccf7ea419', inviteHash: 'test-invite-hash', isPublicInviteLinkEnabled: true, }; const mockMemberId = 'member-new-001'; beforeEach(async () => { const module = await Test.createTestingModule({ providers: [ TeamService, { provide: PlatformGraphqlService, useValue: { query: jest.fn().mockImplementation((query: string) => { if (query.includes('currentWorkspace')) { return Promise.resolve({ currentWorkspace: mockWorkspace }); } if (query.includes('signUpInWorkspace')) { return Promise.resolve({ signUpInWorkspace: { workspace: { id: mockWorkspace.id } } }); } if (query.includes('workspaceMembers')) { return Promise.resolve({ workspaceMembers: { edges: [{ node: { id: mockMemberId, userId: 'user-new-001', userEmail: 'ccagent@ramaiahcare.com', }, }], }, }); } if (query.includes('updateWorkspaceMember')) { return Promise.resolve({ updateWorkspaceMember: { id: mockMemberId } }); } if (query.includes('updateWorkspaceMemberRole')) { return Promise.resolve({ updateWorkspaceMemberRole: { id: mockMemberId } }); } if (query.includes('updateAgent')) { return Promise.resolve({ updateAgent: { id: 'agent-001', workspaceMemberId: mockMemberId } }); } return Promise.resolve({}); }), }, }, { provide: SessionService, useValue: { setCache: jest.fn().mockResolvedValue(undefined), getCache: jest.fn().mockResolvedValue(null), }, }, ], }).compile(); service = module.get(TeamService); platform = module.get(PlatformGraphqlService); session = module.get(SessionService); }); // ── Create member (standard flow) ──────────────────────────── it('should create a workspace member with correct 5-step flow', async () => { const input: CreateMemberInput = { firstName: 'CC', lastName: 'Agent', email: 'ccagent@ramaiahcare.com', password: 'CcRamaiah@2026', roleId: 'role-agent-001', }; const result = await service.createMember(input); expect(result.id).toBe(mockMemberId); expect(result.userEmail).toBe('ccagent@ramaiahcare.com'); // Verify the 5-step call chain const calls = platform.query.mock.calls.map(c => c[0] as string); // Step 1: fetch workspace context (inviteHash) expect(calls.some(q => q.includes('currentWorkspace'))).toBe(true); // Step 2: signUpInWorkspace expect(calls.some(q => q.includes('signUpInWorkspace'))).toBe(true); // Step 3: find member by email expect(calls.some(q => q.includes('workspaceMembers'))).toBe(true); // Step 4: set name expect(calls.some(q => q.includes('updateWorkspaceMember'))).toBe(true); // Step 5: assign role expect(calls.some(q => q.includes('updateWorkspaceMemberRole'))).toBe(true); }); // ── Create member with SIP seat link ───────────────────────── it('should link agent SIP seat when agentId is provided', async () => { const input: CreateMemberInput = { firstName: 'CC', lastName: 'Agent', email: 'ccagent@ramaiahcare.com', password: 'CcRamaiah@2026', roleId: 'role-agent-001', agentId: 'agent-sip-001', }; const result = await service.createMember(input); // Step 6: updateAgent (links workspaceMemberId) const agentCall = platform.query.mock.calls.find( c => (c[0] as string).includes('updateAgent'), ); expect(agentCall).toBeDefined(); expect(agentCall![1]).toMatchObject({ id: 'agent-sip-001', data: { workspaceMemberId: mockMemberId }, }); }); // ── Validation ─────────────────────────────────────────────── it('should reject missing required fields', async () => { await expect( service.createMember({ firstName: '', lastName: 'Test', email: 'test@ramaiahcare.com', password: 'pass', roleId: 'role-001', }), ).rejects.toThrow(); }); it('should reject missing email', async () => { await expect( service.createMember({ firstName: 'Test', lastName: 'User', email: '', password: 'pass', roleId: 'role-001', }), ).rejects.toThrow(); }); // ── Temp password caching ──────────────────────────────────── it('should cache temp password in Redis with 24h TTL', async () => { const input: CreateMemberInput = { firstName: 'CC', lastName: 'Agent', email: 'ccagent@ramaiahcare.com', password: 'CcRamaiah@2026', roleId: 'role-001', }; await service.createMember(input); // Redis setCache should be called with the temp password expect(session.setCache).toHaveBeenCalledWith( expect.stringContaining('team:tempPassword:'), 'CcRamaiah@2026', 86400, // 24 hours ); }); // ── Email normalization ────────────────────────────────────── it('should lowercase email before signUp', async () => { const input: CreateMemberInput = { firstName: 'Test', lastName: 'User', email: 'CcAgent@RamaiahCare.COM', password: 'pass', roleId: 'role-001', }; await service.createMember(input); const signUpCall = platform.query.mock.calls.find( c => (c[0] as string).includes('signUpInWorkspace'), ); expect(signUpCall![1]?.email).toBe('ccagent@ramaiahcare.com'); }); // ── Workspace context caching ──────────────────────────────── it('should fetch workspace context only once for multiple creates', async () => { let callCount = 0; platform.query.mockImplementation((query: string) => { if (query.includes('currentWorkspace')) { return Promise.resolve({ currentWorkspace: mockWorkspace }); } if (query.includes('signUpInWorkspace')) { return Promise.resolve({ signUpInWorkspace: { workspace: { id: mockWorkspace.id } } }); } if (query.includes('workspaceMembers')) { callCount++; // Return matching email based on call order const email = callCount <= 1 ? 'a@test.com' : 'b@test.com'; return Promise.resolve({ workspaceMembers: { edges: [{ node: { id: `member-${callCount}`, userId: 'u', userEmail: email } }], }, }); } if (query.includes('updateWorkspaceMember')) return Promise.resolve({ updateWorkspaceMember: { id: 'member-x' } }); if (query.includes('updateWorkspaceMemberRole')) return Promise.resolve({ updateWorkspaceMemberRole: { id: 'member-x' } }); return Promise.resolve({}); }); await service.createMember({ firstName: 'A', lastName: 'B', email: 'a@test.com', password: 'p', roleId: 'r' }); await service.createMember({ firstName: 'C', lastName: 'D', email: 'b@test.com', password: 'p', roleId: 'r' }); const wsCalls = platform.query.mock.calls.filter( c => (c[0] as string).includes('currentWorkspace'), ); // Should only fetch workspace context once (cached) expect(wsCalls.length).toBe(1); }); });