diff --git a/services/platform-service/src/modules/scim/routes.test.ts b/services/platform-service/src/modules/scim/routes.test.ts index 288f1739..8ae94b1b 100644 --- a/services/platform-service/src/modules/scim/routes.test.ts +++ b/services/platform-service/src/modules/scim/routes.test.ts @@ -6,12 +6,14 @@ const repoMock = { createConnector: vi.fn(), getConnector: vi.fn(), updateConnector: vi.fn(), + deleteConnector: vi.fn(), upsertUserSync: vi.fn(), listUserSync: vi.fn(), upsertGroupSync: vi.fn(), listGroupSync: vi.fn(), createEvent: vi.fn(), listEvents: vi.fn(), + getConnectorStats: vi.fn(), }; const orgRepoMock = { @@ -116,4 +118,99 @@ describe('scimRoutes', () => { }) ); }); + + it('GET /scim/connectors/:orgId/:id/stats returns connector stats', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', orgId: 'org_1' }); + repoMock.getConnectorStats.mockResolvedValue({ + connectorId: 'scim_1', + userCount: 10, + groupCount: 3, + eventCount: 25, + provisionedUsers: 8, + failedUsers: 2, + provisionedGroups: 3, + failedGroups: 0, + lastEventAt: '2026-03-20T00:00:00.000Z', + }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ method: 'GET', url: '/api/scim/connectors/org_1/scim_1/stats' }); + + expect(res.statusCode).toBe(200); + expect(res.json().userCount).toBe(10); + expect(res.json().failedUsers).toBe(2); + }); + + it('POST /scim/connectors/:orgId/:id/pause pauses an active connector', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'active' }); + repoMock.updateConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ + method: 'POST', + url: '/api/scim/connectors/org_1/scim_1/pause', + }); + + expect(res.statusCode).toBe(200); + expect(repoMock.updateConnector).toHaveBeenCalledWith('scim_1', 'org_1', { status: 'paused' }); + }); + + it('POST /scim/connectors/:orgId/:id/pause rejects non-active connector', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ + method: 'POST', + url: '/api/scim/connectors/org_1/scim_1/pause', + }); + + expect(res.statusCode).toBe(400); + }); + + it('POST /scim/connectors/:orgId/:id/resume resumes a paused connector', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' }); + repoMock.updateConnector.mockResolvedValue({ id: 'scim_1', status: 'active' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ + method: 'POST', + url: '/api/scim/connectors/org_1/scim_1/resume', + }); + + expect(res.statusCode).toBe(200); + expect(repoMock.updateConnector).toHaveBeenCalledWith('scim_1', 'org_1', { status: 'active' }); + }); + + it('POST /scim/connectors/:orgId/:id/resume rejects non-paused connector', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'active' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ + method: 'POST', + url: '/api/scim/connectors/org_1/scim_1/resume', + }); + + expect(res.statusCode).toBe(400); + }); + + it('DELETE /scim/connectors/:orgId/:id deletes a paused connector', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' }); + repoMock.deleteConnector.mockResolvedValue(undefined); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ method: 'DELETE', url: '/api/scim/connectors/org_1/scim_1' }); + + expect(res.statusCode).toBe(200); + expect(res.json()).toEqual({ deleted: true }); + }); + + it('DELETE /scim/connectors/:orgId/:id rejects active connector', async () => { + repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'active' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ method: 'DELETE', url: '/api/scim/connectors/org_1/scim_1' }); + + expect(res.statusCode).toBe(400); + expect(repoMock.deleteConnector).not.toHaveBeenCalled(); + }); });