From 573f54888c66c4487dce11ed26647afe17ab9abf Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sun, 1 Mar 2026 17:03:49 -0800 Subject: [PATCH] fix(jarvis-teams): enforce maxMembers capacity in addMember, return null on full/missing team (25 tests) --- .../modules/jarvis-teams/jarvis-teams.test.ts | 55 +++++++++++++++++-- .../src/modules/jarvis-teams/repository.ts | 10 +++- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/services/platform-service/src/modules/jarvis-teams/jarvis-teams.test.ts b/services/platform-service/src/modules/jarvis-teams/jarvis-teams.test.ts index c95bb260..a37f4469 100644 --- a/services/platform-service/src/modules/jarvis-teams/jarvis-teams.test.ts +++ b/services/platform-service/src/modules/jarvis-teams/jarvis-teams.test.ts @@ -117,8 +117,48 @@ describe('addMember', () => { role: 'member', invitedBy: 'user_1', }); - expect(member.status).toBe('invited'); - expect(member.role).toBe('member'); + expect(member).not.toBeNull(); + expect(member!.status).toBe('invited'); + expect(member!.role).toBe('member'); + }); + + it('returns null when team is at capacity', () => { + // starter plan = 5 max, owner counts as 1 + const team = createTeam({ name: 'Full', ownerId: 'user_1' }); + // Add 4 more to fill capacity (owner = 1, so 4 more = 5) + for (let i = 2; i <= 5; i++) { + const m = addMember({ + teamId: team.teamId, + userId: `user_${i}`, + email: `u${i}@b.com`, + displayName: `U${i}`, + role: 'member', + invitedBy: 'user_1', + }); + expect(m).not.toBeNull(); + } + // 6th should be rejected + const rejected = addMember({ + teamId: team.teamId, + userId: 'user_6', + email: 'u6@b.com', + displayName: 'U6', + role: 'member', + invitedBy: 'user_1', + }); + expect(rejected).toBeNull(); + }); + + it('returns null for non-existent team', () => { + const result = addMember({ + teamId: 'nope', + userId: 'u1', + email: 'a@b.com', + displayName: 'A', + role: 'member', + invitedBy: 'u0', + }); + expect(result).toBeNull(); }); }); @@ -133,7 +173,8 @@ describe('acceptInvite', () => { role: 'member', invitedBy: 'user_1', }); - const accepted = acceptInvite(member.id); + expect(member).not.toBeNull(); + const accepted = acceptInvite(member!.id); expect(accepted?.status).toBe('active'); expect(accepted?.joinedAt).toBeTruthy(); }); @@ -156,7 +197,8 @@ describe('updateMemberRole', () => { role: 'member', invitedBy: 'user_1', }); - const updated = updateMemberRole(member.id, 'manager'); + expect(member).not.toBeNull(); + const updated = updateMemberRole(member!.id, 'manager'); expect(updated?.role).toBe('manager'); }); @@ -178,8 +220,9 @@ describe('removeMember', () => { role: 'member', invitedBy: 'user_1', }); - expect(removeMember(member.id)).toBe(true); - expect(getMember(member.id)).toBeUndefined(); + expect(member).not.toBeNull(); + expect(removeMember(member!.id)).toBe(true); + expect(getMember(member!.id)).toBeUndefined(); }); it('cannot remove owner', () => { diff --git a/services/platform-service/src/modules/jarvis-teams/repository.ts b/services/platform-service/src/modules/jarvis-teams/repository.ts index 1fdb1a7d..4548ada5 100644 --- a/services/platform-service/src/modules/jarvis-teams/repository.ts +++ b/services/platform-service/src/modules/jarvis-teams/repository.ts @@ -104,7 +104,15 @@ export function addMember(input: { displayName: string; role: 'owner' | 'manager' | 'member'; invitedBy: string; -}): TeamMember { +}): TeamMember | null { + // Enforce capacity (owner auto-add is exempt) + if (input.role !== 'owner') { + const team = teams.get(input.teamId); + if (!team) return null; + const currentCount = getTeamMembers(input.teamId).length; + if (currentCount >= team.maxMembers) return null; + } + const now = new Date().toISOString(); const id = `member_${crypto.randomUUID()}`;