|
1 | 1 | import { member } from '@sim/db/schema' |
2 | 2 | import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' |
| 3 | +import { resetEnvMock, setEnv } from '@sim/testing/mocks/env.mock' |
3 | 4 | import { |
4 | 5 | organizationMemberLimitsMock, |
5 | 6 | organizationMemberLimitsMockFns, |
6 | 7 | } from '@sim/testing/mocks/organization-member-limits.mock' |
7 | | -import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 8 | +import { outboxServiceMock, outboxServiceMockFns } from '@sim/testing/mocks/outbox-service.mock' |
| 9 | +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' |
8 | 10 |
|
9 | 11 | vi.mock('@/lib/billing/organizations/member-limits', () => organizationMemberLimitsMock) |
| 12 | +vi.mock('@/lib/core/outbox/service', () => outboxServiceMock) |
10 | 13 |
|
11 | 14 | import { |
12 | 15 | removeExternalUserFromOrganizationWorkspaces, |
13 | 16 | removeUserFromOrganization, |
14 | 17 | } from '@/lib/billing/organizations/membership' |
| 18 | +import { MEMORY_CLEANUP_EVENT } from '@/lib/mothership/memories/cleanup-event' |
15 | 19 |
|
16 | 20 | const mockSetOrgMemberUsageLimit = organizationMemberLimitsMockFns.mockSetOrgMemberUsageLimit |
| 21 | +const { mockEnqueueOutboxEvents } = outboxServiceMockFns |
| 22 | + |
| 23 | +/** The private-memory erasure a removal queues for the departing user (D106 in Mothership). */ |
| 24 | +function memberErasure(userId: string) { |
| 25 | + return [ |
| 26 | + expect.anything(), |
| 27 | + MEMORY_CLEANUP_EVENT, |
| 28 | + [ |
| 29 | + expect.objectContaining({ |
| 30 | + request: { |
| 31 | + version: 1, |
| 32 | + target: { kind: 'organization_member', organizationId: 'org-1', userId }, |
| 33 | + }, |
| 34 | + }), |
| 35 | + ], |
| 36 | + ] |
| 37 | +} |
17 | 38 |
|
18 | 39 | describe('external organization access removal', () => { |
19 | 40 | beforeEach(() => { |
20 | 41 | resetDbChainMock() |
| 42 | + setEnv({ COPILOT_API_KEY: 'test-key' }) |
| 43 | + }) |
| 44 | + |
| 45 | + afterAll(() => { |
| 46 | + resetEnvMock() |
| 47 | + }) |
| 48 | + |
| 49 | + it("erases the departing user's private Mothership memory in the organization", async () => { |
| 50 | + await removeExternalUserFromOrganizationWorkspaces({ |
| 51 | + organizationId: 'org-1', |
| 52 | + userId: 'external-user', |
| 53 | + }) |
| 54 | + expect(mockEnqueueOutboxEvents).toHaveBeenCalledWith(...memberErasure('external-user')) |
| 55 | + |
| 56 | + queueTableRows(member, [{ id: 'membership', userId: 'target', role: 'member' }]) |
| 57 | + dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'membership' }]) |
| 58 | + await removeUserFromOrganization({ |
| 59 | + organizationId: 'org-1', |
| 60 | + userId: 'target', |
| 61 | + memberId: 'membership', |
| 62 | + skipBillingLogic: true, |
| 63 | + onError: 'throw', |
| 64 | + }) |
| 65 | + expect(mockEnqueueOutboxEvents).toHaveBeenCalledWith(...memberErasure('target')) |
| 66 | + }) |
| 67 | + |
| 68 | + it('erases nothing when a removal does not commit', async () => { |
| 69 | + queueTableRows(member, []) |
| 70 | + queueTableRows(member, [{ id: 'new-membership' }]) |
| 71 | + await expect( |
| 72 | + removeExternalUserFromOrganizationWorkspaces({ organizationId: 'org-1', userId: 'external' }) |
| 73 | + ).rejects.toMatchObject({ code: 'conflict' }) |
| 74 | + expect(mockEnqueueOutboxEvents).not.toHaveBeenCalled() |
21 | 75 | }) |
22 | 76 |
|
23 | 77 | it('clears a stale per-user organization usage cap in the removal transaction', async () => { |
|
0 commit comments