diff --git a/src/tools/auth0/handlers/scimHandler.ts b/src/tools/auth0/handlers/scimHandler.ts index 3c9bb4216..582c2787e 100644 --- a/src/tools/auth0/handlers/scimHandler.ts +++ b/src/tools/auth0/handlers/scimHandler.ts @@ -40,7 +40,7 @@ export default class ScimHandler { constructor(config, connectionsManager, poolClient: PromisePoolExecutor) { this.config = config; this.connectionsManager = connectionsManager; - this.scimClient = connectionsManager._getRestClient('/connections/:id/scim-configuration'); + this.scimClient = connectionsManager?._getRestClient('/connections/:id/scim-configuration'); this.poolClient = poolClient; this.idMap = new Map(); } @@ -166,7 +166,7 @@ export default class ScimHandler { return null; } - log.error(`SCIM request failed with statusCode ${ error.statusCode }. ${ error.message || error.toString() }.`); + log.error(`SCIM request failed with status code ${ error.statusCode }. ${ error.message || error.toString() }.`); throw error; } diff --git a/test/context/yaml/context.test.js b/test/context/yaml/context.test.js index 4f6021579..7481811a6 100644 --- a/test/context/yaml/context.test.js +++ b/test/context/yaml/context.test.js @@ -590,7 +590,8 @@ describe('#YAML context validation', () => { }, }, ], - }) + }), + _getRestClient: (path) => ({}) } } ); diff --git a/test/tools/auth0/handlers/connections.tests.js b/test/tools/auth0/handlers/connections.tests.js index 2c3537a0c..60837e055 100644 --- a/test/tools/auth0/handlers/connections.tests.js +++ b/test/tools/auth0/handlers/connections.tests.js @@ -95,6 +95,7 @@ describe('#connections handler', () => { update: () => Promise.resolve([]), delete: () => Promise.resolve([]), getAll: () => [], + _getRestClient: (path) => ({}), }, clients: { getAll: () => [], @@ -117,6 +118,7 @@ describe('#connections handler', () => { { strategy: 'github', name: 'github', enabled_clients: [clientId] }, { strategy: 'auth0', name: 'db-should-be-ignored', enabled_clients: [] }, ], + _getRestClient: () => ({}), }, clients: { getAll: () => [{ name: 'test client', client_id: clientId }], @@ -152,6 +154,7 @@ describe('#connections handler', () => { }, delete: () => Promise.resolve([]), getAll: () => [{ name: 'someConnection', id: 'con1', strategy: 'custom' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [{ name: 'client1', client_id: 'YwqVtt8W3pw5AuEz3B2Kse9l2Ruy7Tec' }], @@ -215,6 +218,7 @@ describe('#connections handler', () => { }, delete: () => Promise.resolve([]), getAll: () => [{ name: 'someSamlConnection', id: 'con1', strategy: 'samlp' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [ @@ -300,6 +304,7 @@ describe('#connections handler', () => { }, delete: () => Promise.resolve([]), getAll: () => [{ name: 'someSamlConnection', id: 'con1', strategy: 'samlp' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [ @@ -375,6 +380,7 @@ describe('#connections handler', () => { enabled_clients: ['excluded-one-id'], }, ], + _getRestClient: () => ({}), }, clients: { getAll: () => [ @@ -422,6 +428,7 @@ describe('#connections handler', () => { return Promise.resolve([]); }, getAll: () => [{ id: 'con1', name: 'existingConnection', strategy: 'custom' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [], @@ -455,6 +462,7 @@ describe('#connections handler', () => { return Promise.resolve([]); }, getAll: () => [{ id: 'con1', name: 'existingConnection', strategy: 'custom' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [], @@ -484,6 +492,7 @@ describe('#connections handler', () => { return Promise.resolve([]); }, getAll: () => [{ id: 'con1', name: 'existingConnection', strategy: 'custom' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [], @@ -517,6 +526,7 @@ describe('#connections handler', () => { return Promise.resolve([]); }, getAll: () => [{ id: 'con1', name: 'existingConnection', strategy: 'custom' }], + _getRestClient: () => ({}), }, clients: { getAll: () => [], @@ -554,6 +564,7 @@ describe('#connections handler', () => { { id: 'con1', name: 'existing1', strategy: 'custom' }, { id: 'con2', name: 'existing2', strategy: 'custom' }, ], + _getRestClient: () => ({}), }, clients: { getAll: () => [], diff --git a/test/tools/auth0/handlers/scimHandler.tests.js b/test/tools/auth0/handlers/scimHandler.tests.js index 96592ddda..e4d681dd4 100644 --- a/test/tools/auth0/handlers/scimHandler.tests.js +++ b/test/tools/auth0/handlers/scimHandler.tests.js @@ -1,421 +1,289 @@ -const { expect } = require('chai'); -// eslint-disable-next-line import/no-extraneous-dependencies -const axios = require('axios'); +/* eslint-disable no-unused-expressions, no-underscore-dangle */ +const { PromisePoolExecutor } = require('promise-pool-executor'); +const chaiAsPromised = require('chai-as-promised'); const sinon = require('sinon'); +const chai = require('chai'); const ScimHandler = require('../../../../src/tools/auth0/handlers/scimHandler').default; -let scimHandler; -beforeEach(() => { - const connectionResponse = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const connectionsManagerMock = { - update: sinon.stub().resolves(connectionResponse), - create: sinon.stub().resolves(connectionResponse) - }; - - scimHandler = new ScimHandler( - function() { return 'https://test-host.auth0.com'; }, - { - getAccessToken: async function () { - return 'mock_access_token'; - } - }, - connectionsManagerMock - ); -}); +const { expect } = chai.use(chaiAsPromised); + +// Mock data and functions +let mockConfig; +let mockConnectionsManager; + +const mockPoolClient = new PromisePoolExecutor(); describe('ScimHandler', () => { - describe('#isScimStrategy', () => { - it('should return true for SCIM strategy', () => { - const response = scimHandler.isScimStrategy('samlp'); - // eslint-disable-next-line no-unused-expressions - expect(response).to.be.true; - }); + let handler; + + beforeEach(() => { + mockConfig = sinon.stub(); + mockConnectionsManager = { + _getRestClient: sinon.stub().returns({ + get: sinon.stub(), + create: sinon.stub(), + patch: sinon.stub(), + delete: sinon.stub(), + }), + getAll: sinon.stub(), + update: sinon.stub(), + create: sinon.stub(), + }; + handler = new ScimHandler(mockConfig, mockConnectionsManager, mockPoolClient); + }); + + afterEach(() =>{ + sinon.restore(); + }); - it('should return false for non-SCIM strategy', () => { - const response = scimHandler.isScimStrategy('oauth'); - // eslint-disable-next-line no-unused-expressions - expect(response).to.be.false; + describe('isScimStrategy', () => { + it('should return true for supported SCIM strategies', () => { + expect(handler.isScimStrategy('samlp')).to.be.true; + expect(handler.isScimStrategy('oidc')).to.be.true; + expect(handler.isScimStrategy('okta')).to.be.true; + expect(handler.isScimStrategy('waad')).to.be.true; + }); + + it('should return false for unsupported strategies', () => { + expect(handler.isScimStrategy('google-auth')).to.be.false; + expect(handler.isScimStrategy('auth0')).to.be.false; + expect(handler.isScimStrategy('unsupported-any')).to.be.false; }); }); - describe('#createIdMap', () => { - it('should create id map with SCIM configuration', async () => { + describe('createIdMap', () => { + it('should create an id map for SCIM connections', async () => { const connections = [ - { id: 'con_KYp633cmKtnEQ31C', strategy: 'samlp' }, // SCIM connection. - { id: 'con_Njd1bxE3QTqTRwAk', strategy: 'auth0' }, // Non-SCIM connection. - { id: 'con_d3tmuoAkaUQgxN1f', strategy: 'gmail' }, // Connection which doesn't exist. + { id: 'con_kzpLY0Afi4I8lvwM', strategy: 'samlp' }, + { id: 'con_ilYXDjpVjU6GMnmk', strategy: 'auth0' }, // Un-supported strategy ]; - const getScimConfigurationStub = sinon.stub(scimHandler, 'getScimConfiguration'); - getScimConfigurationStub.withArgs({ id: 'con_KYp633cmKtnEQ31C' }).resolves({ user_id_attribute: 'externalId-115', mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] }); - getScimConfigurationStub.withArgs({ id: 'con_Njd1bxE3QTqTRwAk' }).rejects({ response: { data: { statusCode: 404 } } }); - getScimConfigurationStub.withArgs({ id: 'con_d3tmuoAkaUQgxN1f' }).rejects({ response: { data: { statusCode: 404 } } }); - await scimHandler.createIdMap(connections); - // eslint-disable-next-line no-unused-expressions - expect(scimHandler.idMap.get('con_KYp633cmKtnEQ31C')).to.deep.equal({ strategy: 'samlp', hasConfig: true }); - - // eslint-disable-next-line no-unused-expressions - expect(scimHandler.idMap.get('con_Njd1bxE3QTqTRwAk')).to.be.undefined; // Because, it's a Non-SCIM connection. - - // eslint-disable-next-line no-unused-expressions - expect(scimHandler.idMap.get('con_d3tmuoAkaUQgxN1f')).to.be.undefined; - - getScimConfigurationStub.restore(); + const response = { + mapping: [{ scim: 'userName', auth0: 'preferred_username' }], + user_id_attribute: 'externalId', + connection_id: 'con_kzpLY0Afi4I8lvwM' + }; + const expectedScimConfiguration = { mapping: response.mapping, user_id_attribute: response.user_id_attribute }; + + handler.getScimConfiguration = sinon.stub().resolves(response); + await handler.createIdMap(connections); + expect(handler.idMap.size).to.equal(1); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.have.property('strategy', 'samlp'); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.have.property('scimConfiguration').that.deep.equals(expectedScimConfiguration); + expect(handler.idMap.get('con_ilYXDjpVjU6GMnmk')).to.be.undefined; }); - }); - describe('#applyScimConfiguration', () => { - it('should apply SCIM configuration to connections', async () => { - const connections = [ - { id: 'con_KYp633cmKtnEQ31C', strategy: 'samlp' }, - { id: 'con_Njd1bxE3QTqTRwAk', strategy: 'oidc' }, - { id: 'con_d3tmuoAkaUQgxN1f', strategy: 'gmail' } - ]; - const getScimConfigurationStub = sinon.stub(scimHandler, 'getScimConfiguration'); - getScimConfigurationStub.withArgs({ id: 'con_KYp633cmKtnEQ31C' }).resolves({ user_id_attribute: 'externalId-1', mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] }); - getScimConfigurationStub.withArgs({ id: 'con_Njd1bxE3QTqTRwAk' }).resolves({ user_id_attribute: 'externalId-2', mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] }); - getScimConfigurationStub.withArgs({ id: 'con_d3tmuoAkaUQgxN1f' }).rejects({ response: { data: { statusCode: 404 } } }); + it('should not create an id map when getScimConfiguration returns null', async () => { + const connections = [{ id: 'con_kzpLY0Afi4I8lvwM', strategy: 'samlp' }]; - await scimHandler.applyScimConfiguration(connections); + handler.getScimConfiguration = sinon.stub().resolves(null); + await expect(handler.createIdMap(connections)); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.have.property('strategy', 'samlp'); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.not.have.property('scimConfiguration'); + }); - // eslint-disable-next-line no-unused-expressions - expect(connections[0].scim_configuration).to.deep.equal({ user_id_attribute: 'externalId-1', mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] }); + it('should handle errors from getScimConfiguration gracefully', async () => { + const connections = [{ id: 'con_kzpLY0Afi4I8lvwM', strategy: 'samlp' }]; - // eslint-disable-next-line no-unused-expressions - expect(connections[1].scim_configuration).to.deep.equal({ user_id_attribute: 'externalId-2', mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] }); + handler.getScimConfiguration = sinon.stub().rejects({ statusCode: 404 }); + await expect(handler.createIdMap(connections)); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.have.property('strategy', 'samlp'); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.not.have.property('scimConfiguration'); + + handler.getScimConfiguration = sinon.stub().rejects({ statusCode: 403 }); + await expect(handler.createIdMap(connections)); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.have.property('strategy', 'samlp'); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.not.have.property('scimConfiguration'); - // eslint-disable-next-line no-unused-expressions - expect(connections[2].scim_configuration).to.be.undefined; + handler.getScimConfiguration = sinon.stub().rejects({ statusCode: 429 }); + await expect(handler.createIdMap(connections)); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.have.property('strategy', 'samlp'); + expect(handler.idMap.get('con_kzpLY0Afi4I8lvwM')).to.not.have.property('scimConfiguration'); - getScimConfigurationStub.restore(); + handler.getScimConfiguration = sinon.stub().rejects(new Error('Unexpected error')); + await expect(handler.createIdMap(connections)).to.be.rejectedWith('Unexpected error'); }); }); - describe('#scimHttpRequest', () => { - it('should make HTTP request with correct authorization header', async () => { - const accessToken = 'mock_access_token'; - const axiosStub = sinon.stub(axios, 'get').resolves({ data: {} }); - const response = await scimHandler.scimHttpRequest('get', ['https://mock-domain/api/v2/connections/1/scim-configuration']); - - // eslint-disable-next-line no-unused-expressions - expect(response).to.exist; + describe('applyScimConfiguration', () => { + it('should apply SCIM configuration to SCIM connections', async () => { + const connections = [{ id: 'con_kzpLY0Afi4I8lvwM', strategy: 'samlp' }]; + const expectedScimConfiguration = { + mapping: [{ scim: 'userName', auth0: 'preferred_username' }], + user_id_attribute: 'externalId' + }; - // eslint-disable-next-line no-unused-expressions - expect(axiosStub.calledOnce).to.be.true; + handler.idMap.set('con_kzpLY0Afi4I8lvwM', { strategy: 'samlp', scimConfiguration: expectedScimConfiguration }); + await handler.applyScimConfiguration(connections); + expect(connections[0]).to.have.property('scim_configuration').that.deep.equals(expectedScimConfiguration); + }); - // eslint-disable-next-line no-unused-expressions - expect(axiosStub.firstCall.args[1].headers.Authorization).to.equal(`Bearer ${accessToken}`); + it('should not modify connections if idMap is empty', async () => { + const connections = [{ id: 'con_kzpLY0Afi4I8lvwM', strategy: 'samlp' }]; - axiosStub.restore(); + await handler.applyScimConfiguration(connections); + expect(connections[0]).to.not.have.property('scim_configuration'); }); }); - describe('#getScimConfiguration', () => { - it('should return SCIM configuration for existing connection', async () => { - const requestParams = { id: 'con_KYp633cmKtnEQ31C' }; - const scimConfiguration = { - connection_id: 'con_KYp633cmKtnEQ31C', - connection_name: 'okta', - strategy: 'okta', - tenant_name: 'test-tenant', - user_id_attribute: 'externalId-1', - mapping: [ - { - scim: 'scim_id', - auth0: 'auth0_id' - } - ] - }; + describe('createScimConfiguration', () => { + it('should create SCIM configuration', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; + const body = { user_id_attribute: 'id', mapping: [] }; - const axiosStub = sinon.stub(axios, 'get').resolves({ data: scimConfiguration, status: 201 }); - const response = await scimHandler.getScimConfiguration(requestParams); - // eslint-disable-next-line no-unused-expressions - expect(response).to.deep.equal(scimConfiguration); + mockConnectionsManager._getRestClient().create.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); + const result = await handler.createScimConfiguration(params, body); + expect(result).to.deep.equal({ id: 'con_kzpLY0Afi4I8lvwM' }); + }); - axiosStub.restore(); + it('should handle errors during creation', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; + const body = { user_id_attribute: 'id', mapping: [] }; + + mockConnectionsManager._getRestClient().create.rejects(new Error('Unexpected error')); + await expect(handler.createScimConfiguration(params, body)).to.be.rejectedWith('Unexpected error'); }); + }); - it('should throw error for non-existing SCIM configuration', async () => { - const requestParams = { id: 'con_KYp633cmKtnEQ31C' }; - const axiosStub = sinon.stub(axios, 'get').rejects({ response: { status: 404, errorCode: 'not_found', statusText: 'The connection does not exist.' } }); + describe('getScimConfiguration', () => { + it('should retrieve SCIM configuration', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; - try { - await scimHandler.getScimConfiguration(requestParams); - expect.fail('Expected getScimConfiguration to throw an error'); - } catch (error) { - // eslint-disable-next-line no-unused-expressions - expect(error.response.status).to.equal(404); - } + mockConnectionsManager._getRestClient().get.resolves({ mapping: [], user_id_attribute: 'id' }); - axiosStub.restore(); + const result = await handler.getScimConfiguration(params); + expect(result).to.deep.equal({ mapping: [], user_id_attribute: 'id' }); }); - it('should not throw error for scim connections when SCIM permissions disabled.', async () => { - const requestParams = { id: 'con_KYp633cmKtnEQ31C' }; - const axiosStub = sinon.stub(axios, 'get').rejects({ response: { data: { statusCode: 403, errorCode: 'insufficient_scope', statusText: 'Insufficient scope, expected any of: read:scim_config.' } } }); + it('should handle errors during retrieval', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; - const data = await scimHandler.getScimConfiguration(requestParams); - // eslint-disable-next-line no-unused-expressions - expect(data).to.be.null; - - axiosStub.restore(); + mockConnectionsManager._getRestClient().get.rejects(new Error('Unexpected error')); + await expect(handler.getScimConfiguration(params)).to.be.rejectedWith('Unexpected error'); }); }); - describe('#createScimConfiguration', () => { - const requestParams = { - id: 'con_PKp644cmKtnEB11J' - }; - const payload = { - user_id_attribute: 'externalId-5', - mapping: [ - { - scim: 'scim_key', - auth0: 'auth0_key' - } - ] - }; - const responseBody = { - connection_id: 'con_PKp644cmKtnEB11J', - connection_name: 'okta-new-connection', - strategy: 'okta', - tenant_name: 'test-tenant', - ...requestParams, - ...payload, - created_at: new Date().getTime(), - updated_on: new Date().getTime() - }; + describe('updateScimConfiguration', () => { + it('should update SCIM configuration', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; + const body = { user_id_attribute: 'id', mapping: [] }; - it('should create new SCIM configuration', async () => { - const axiosStub = sinon.stub(axios, 'post').resolves({ data: responseBody, status: 201 }); - const response = await scimHandler.createScimConfiguration(requestParams, payload); + mockConnectionsManager._getRestClient().patch.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); - expect(response.connection_id).to.equal(requestParams.id); - expect(response.user_id_attribute).to.equal(responseBody.user_id_attribute); - expect(response.mapping).to.deep.equal(responseBody.mapping); + const result = await handler.updateScimConfiguration(params, body); + expect(result).to.deep.equal({ id: 'con_kzpLY0Afi4I8lvwM' }); + }); - axiosStub.restore(); + it('should handle errors during update', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; + const body = { user_id_attribute: 'id', mapping: [] }; + + mockConnectionsManager._getRestClient().patch.rejects(new Error('Unexpected error')); + await expect(handler.updateScimConfiguration(params, body)).to.be.rejectedWith('Unexpected error'); }); }); - describe('#updateScimConfiguration', () => { - it('should update existing SCIM configuration', async () => { - const requestParams = { - id: 'con_PKp644cmKtnEB11J' - }; - const payload = { - user_id_attribute: 'externalId-5', - mapping: [ - { - scim: 'scim_key', - auth0: 'auth0_key' - } - ] - }; - const responseBody = { - connection_id: 'con_PKp644cmKtnEB11J', - connection_name: 'okta-new-connection', - strategy: 'okta', - tenant_name: 'test-tenant', - ...requestParams, - ...payload, - created_at: new Date().getTime(), - updated_on: new Date().getTime() - }; - const axiosStub = sinon.stub(axios, 'patch').resolves({ data: responseBody, status: 200 }); - const response = await scimHandler.updateScimConfiguration(requestParams, payload); + describe('deleteScimConfiguration', () => { + it('should delete SCIM configuration', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; - expect(response.connection_id).to.equal(requestParams.id); - expect(response.user_id_attribute).to.equal(responseBody.user_id_attribute); - expect(response.mapping).to.deep.equal(responseBody.mapping); + mockConnectionsManager._getRestClient().delete.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); - axiosStub.restore(); + const result = await handler.deleteScimConfiguration(params); + expect(result).to.deep.equal({ id: 'con_kzpLY0Afi4I8lvwM' }); }); - }); - describe('#deleteScimConfiguration', () => { - it('should delete existing SCIM configuration', async () => { - const requestParams = { - id: 'con_PKp644cmKtnEB11J' - }; - const axiosStub = sinon.stub(axios, 'delete').resolves({ data: {}, status: 204 }); - const response = await scimHandler.deleteScimConfiguration(requestParams); - expect(response).to.deep.equal({}); + it('should handle errors during deletion', async () => { + const params = { id: 'con_kzpLY0Afi4I8lvwM' }; - axiosStub.restore(); + mockConnectionsManager._getRestClient().delete.rejects(new Error('Unexpected error')); + await expect(handler.deleteScimConfiguration(params)).to.be.rejectedWith('Unexpected error'); }); }); - describe('#updateOverride', () => { - it('should \'update\' connection and \'update\' SCIM configuration', async () => { - const requestParams = { id: 'con_PKp644cmKtnEB11J' }; - const bodyParams = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection', - scim_configuration: { - user_id_attribute: 'externalId-115', - mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] - } - }; - const connectionUpdatePayload = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const { scim_configuration: scimConfiguration } = bodyParams; - const idMapEntry = { - strategy: 'samlp', - hasConfig: true - }; - const idMapMock = new Map(); - idMapMock.set(requestParams.id, idMapEntry); - scimHandler.idMap = idMapMock; + describe('updateOverride', () => { + it('should update SCIM configuration when updating connection during updateOverride', async () => { + const requestParams = { id: 'con_kzpLY0Afi4I8lvwM' }; + const bodyParams = { scim_configuration: { mapping: [], user_id_attribute: 'id' } }; - const updateScimStub = sinon.stub(scimHandler, 'updateScimConfiguration').resolves({ data: {} }); - const response = await scimHandler.updateOverride(requestParams, bodyParams); - - // eslint-disable-next-line no-unused-expressions - expect(response).to.deep.equal(connectionUpdatePayload); + mockConnectionsManager.update.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); + handler.updateScimConfiguration = sinon.stub().resolves({ connection_id: 'con_kzpLY0Afi4I8lvwM' }); + handler.idMap.set('con_kzpLY0Afi4I8lvwM', { strategy: 'samlp', scimConfiguration: bodyParams.scim_configuration }); - // eslint-disable-next-line no-unused-expressions - expect(updateScimStub.calledOnceWith(requestParams, scimConfiguration)).to.be.true; - - updateScimStub.restore(); + await handler.updateOverride(requestParams, bodyParams); + expect(mockConnectionsManager.update.calledOnce).to.be.true; + expect(handler.updateScimConfiguration.calledOnce).to.be.true; }); - it('should \'update\' connection and \'create\' SCIM configuration', async () => { - const requestParams = { id: 'con_PKp644cmKtnEB11J' }; - const bodyParams = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection', - scim_configuration: { - user_id_attribute: 'externalId-115', - mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] - } - }; - const connectionUpdatePayload = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const { scim_configuration: scimConfiguration } = bodyParams; - const idMapEntry = { - strategy: 'samlp', - hasConfig: false - }; - const idMapMock = new Map(); - idMapMock.set(requestParams.id, idMapEntry); - scimHandler.idMap = idMapMock; - - const createScimStub = sinon.stub(scimHandler, 'createScimConfiguration').resolves({ data: {} }); - const response = await scimHandler.updateOverride(requestParams, bodyParams); - - // eslint-disable-next-line no-unused-expressions - expect(response).to.deep.equal(connectionUpdatePayload); + it('should create SCIM configuration when updating connection during updateOverride', async () => { + const requestParams = { id: 'con_kzpLY0Afi4I8lvwM' }; + const bodyParams = { scim_configuration: { mapping: [], user_id_attribute: 'id' } }; - // eslint-disable-next-line no-unused-expressions - expect(createScimStub.calledOnceWith(requestParams, scimConfiguration)).to.be.true; - - createScimStub.restore(); + mockConnectionsManager.update.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); + handler.createScimConfiguration = sinon.stub().resolves({ connection_id: 'con_kzpLY0Afi4I8lvwM' }); + + await handler.updateOverride(requestParams, bodyParams); + expect(mockConnectionsManager.update.calledOnce).to.be.true; + expect(handler.createScimConfiguration.calledOnce).to.be.true; }); - it('should \'update\' connection and \'delete\' SCIM configuration', async () => { - const requestParams = { id: 'con_PKp644cmKtnEB11J' }; - const bodyParams = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const connectionUpdatePayload = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const idMapEntry = { - strategy: 'samlp', - hasConfig: true - }; - const idMapMock = new Map(); - idMapMock.set(requestParams.id, idMapEntry); - - scimHandler.idMap = idMapMock; + it('should delete SCIM configuration when updating connection during updateOverride', async () => { + const requestParams = { id: 'con_kzpLY0Afi4I8lvwM' }; + const bodyParams = {}; - const deleteScimStub = sinon.stub(scimHandler, 'deleteScimConfiguration').resolves({ data: {} }); - const response = await scimHandler.updateOverride(requestParams, bodyParams); - - // eslint-disable-next-line no-unused-expressions - expect(response).to.deep.equal(connectionUpdatePayload); + mockConnectionsManager.update.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); + handler.idMap.set('con_kzpLY0Afi4I8lvwM', { strategy: 'samlp', scimConfiguration: { mapping: [], user_id_attribute: 'id' } }); + handler.deleteScimConfiguration = sinon.stub().resolves({ connection_id: 'con_kzpLY0Afi4I8lvwM' }); + handler.config.returns(true); // Setting `AUTH0_ALLOW_DELETE` to true. + + await handler.updateOverride(requestParams, bodyParams); + expect(mockConnectionsManager.update.calledOnce).to.be.true; + expect(handler.deleteScimConfiguration.calledOnce).to.be.true; + }); + + it('should not delete SCIM configuration when updating connection during updateOverride when AUTH0_ALLOW_DELETE is false', async () => { + const requestParams = { id: 'con_kzpLY0Afi4I8lvwM' }; + const bodyParams = {}; + + mockConnectionsManager.update.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); + handler.idMap.set('con_kzpLY0Afi4I8lvwM', { strategy: 'samlp', scimConfiguration: { mapping: [], user_id_attribute: 'id' } }); + handler.deleteScimConfiguration = sinon.stub().resolves({ connection_id: 'con_kzpLY0Afi4I8lvwM' }); + handler.config.returns(false); // Setting `AUTH0_ALLOW_DELETE` to false. + + await handler.updateOverride(requestParams, bodyParams); + expect(mockConnectionsManager.update.calledOnce).to.be.true; + expect(handler.deleteScimConfiguration.called).to.be.false; + }); - // eslint-disable-next-line no-unused-expressions - expect(deleteScimStub.calledOnceWith(requestParams)).to.be.true; + it('should handle errors gracefully during updateOverride', async () => { + const requestParams = { id: 'con_kzpLY0Afi4I8lvwM' }; + const bodyParams = { scim_configuration: { mapping: [], user_id_attribute: 'id' } }; - deleteScimStub.restore(); + mockConnectionsManager.update.rejects(new Error('Unexpected error')); + handler.updateScimConfiguration = sinon.stub().resolves({ connection_id: 'con_kzpLY0Afi4I8lvwM' }); + + await expect(handler.updateOverride(requestParams, bodyParams)).to.be.rejectedWith('Unexpected error'); + expect(handler.updateScimConfiguration.called).to.be.false; }); }); - - describe('#createOverride', () => { - it('should \'create\' connection and \'create\' SCIM configuration', async () => { - const requestParams = { id: 'con_PKp644cmKtnEB11J' }; - const bodyParams = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection', - scim_configuration: { - user_id_attribute: 'externalId-115', - mapping: [{ auth0: 'auth0_key', scim: 'scim_key' }] - } - }; - const connectionCreatePayload = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const { scim_configuration: scimConfiguration } = bodyParams; - const idMapEntry = { - strategy: 'samlp', - hasConfig: false - }; - const idMapMock = new Map(); - idMapMock.set(requestParams.id, idMapEntry); - scimHandler.idMap = idMapMock; - const createScimStub = sinon.stub(scimHandler, 'createScimConfiguration').resolves({ data: {} }); - const response = await scimHandler.createOverride(bodyParams); - - // eslint-disable-next-line no-unused-expressions - expect(response).to.deep.equal(connectionCreatePayload); + describe('createOverride', () => { + it('should create SCIM configuration when creating connection', async () => { + const bodyParams = { scim_configuration: { mapping: [], user_id_attribute: 'id' } }; - // eslint-disable-next-line no-unused-expressions - expect(createScimStub.calledOnceWith(requestParams, scimConfiguration)).to.be.true; - - createScimStub.restore(); - }); + mockConnectionsManager.create.resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); + handler.createScimConfiguration = sinon.stub().resolves({ id: 'con_kzpLY0Afi4I8lvwM' }); - it('should \'create\' connection without SCIM configuration', async () => { - const requestParams = { id: 'con_PKp644cmKtnEB11J' }; - const bodyParams = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const connectionUpdatePayload = { - id: 'con_PKp644cmKtnEB11J', - name: 'test-connection' - }; - const idMapEntry = { - strategy: 'samlp', - hasConfig: false - }; - const idMapMock = new Map(); - idMapMock.set(requestParams.id, idMapEntry); - scimHandler.idMap = idMapMock; + await handler.createOverride(bodyParams); + expect(mockConnectionsManager.create.calledOnce).to.be.true; + expect(handler.createScimConfiguration.calledOnce).to.be.true; + }); - const createScimStub = sinon.stub(scimHandler, 'createScimConfiguration').resolves({ data: {} }); - const response = await scimHandler.createOverride(requestParams, bodyParams); - - // eslint-disable-next-line no-unused-expressions - expect(response).to.deep.equal(connectionUpdatePayload); + it('should handle errors gracefully during createOverride', async () => { + const bodyParams = { scim_configuration: { mapping: [], user_id_attribute: 'id' } }; - // eslint-disable-next-line no-unused-expressions - expect(createScimStub.calledOnce).to.be.false; - - createScimStub.restore(); + mockConnectionsManager.create.rejects(new Error('Unexpected error')); + await expect(handler.createOverride(bodyParams)).to.be.rejectedWith('Unexpected error'); }); }); }); diff --git a/test/tools/auth0/handlers/themes.tests.js b/test/tools/auth0/handlers/themes.tests.js index 12dd241dc..10bdea8c8 100644 --- a/test/tools/auth0/handlers/themes.tests.js +++ b/test/tools/auth0/handlers/themes.tests.js @@ -1,6 +1,10 @@ -const { expect, assert } = require('chai'); +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); const { omit, cloneDeep } = require('lodash'); const { default: ThemesHandler } = require('../../../../src/tools/auth0/handlers/themes'); +chai.use(chaiAsPromised); + +const { expect, assert } = chai; function stub() { const s = function (...args) { diff --git a/test/tools/auth0/validator.tests.js b/test/tools/auth0/validator.tests.js index f88a2be73..6faeebdc3 100644 --- a/test/tools/auth0/validator.tests.js +++ b/test/tools/auth0/validator.tests.js @@ -15,6 +15,7 @@ describe('#schema validation tests', () => { }, connections: { getAll: async () => ({ connections: [] }), + _getRestClient: (path) => ({}), }, resourceServers: { getAll: async () => ({ resource_servers: [] }), diff --git a/test/utils.js b/test/utils.js index 4a61197a3..2cbad7ef6 100644 --- a/test/utils.js +++ b/test/utils.js @@ -20,7 +20,7 @@ export function mockMgmtClient() { hooks: { getAll: () => ({ hooks: [] }) }, actions: { getAll: () => ({ actions: [] }) }, databases: { getAll: () => ({ databases: [] }) }, - connections: { getAll: () => ({ connections: [] }) }, + connections: { getAll: () => ({ connections: [] }), _getRestClient: (path) => ({}) }, resourceServers: { getAll: () => ({ resourceServers: [] }) }, rulesConfigs: { getAll: () => ({ rulesConfigs: [] }) }, emailProvider: {