keycloak-api-manager 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/Handlers/attackDetectionHandler.js +64 -0
  2. package/Handlers/clientPoliciesHandler.js +120 -0
  3. package/Handlers/groupsHandler.js +32 -0
  4. package/Handlers/organizationsHandler.js +243 -0
  5. package/Handlers/serverInfoHandler.js +36 -0
  6. package/Handlers/userProfileHandler.js +121 -0
  7. package/README.md +83 -7157
  8. package/docs/architecture.md +47 -0
  9. package/docs/deployment.md +32 -0
  10. package/docs/keycloak-setup.md +47 -0
  11. package/docs/test-configuration.md +43 -0
  12. package/docs/testing.md +60 -0
  13. package/index.js +156 -240
  14. package/package.json +28 -15
  15. package/test/.mocharc.json +2 -2
  16. package/test/config/secrets.json +12 -0
  17. package/test/docker-keycloak/certs/keycloak.crt +58 -0
  18. package/test/docker-keycloak/certs/keycloak.key +28 -0
  19. package/test/docker-keycloak/docker-compose-https.yml +2 -0
  20. package/test/docker-keycloak/docker-compose.yml +4 -4
  21. package/test/helpers/matrix.js +16 -0
  22. package/test/matrix/auth.json +27 -0
  23. package/test/matrix/clients.json +45 -0
  24. package/test/matrix/realms-components-idp.json +37 -0
  25. package/test/matrix/users-roles-groups.json +26 -0
  26. package/test/package-lock.json +3032 -0
  27. package/test/specs/attackDetection.test.js +102 -0
  28. package/test/specs/clientCredentials.test.js +79 -0
  29. package/test/specs/clientPolicies.test.js +162 -0
  30. package/test/specs/{debugClientLibrary.test.js → diagnostics/debugClientLibrary.test.js} +2 -2
  31. package/test/specs/groupPermissions.test.js +87 -0
  32. package/test/specs/matrix/matrix-auth.test.js +112 -0
  33. package/test/specs/matrix/matrix-clients.test.js +59 -0
  34. package/test/specs/matrix/matrix-realms-components-idp.test.js +111 -0
  35. package/test/specs/matrix/matrix-users-roles-groups.test.js +68 -0
  36. package/test/specs/organizations.test.js +183 -0
  37. package/test/specs/serverInfo.test.js +140 -0
  38. package/test/specs/userProfile.test.js +135 -0
  39. package/test/{enableServerFeatures.js → support/enableServerFeatures.js} +43 -26
  40. package/test/{setup.js → support/setup.js} +3 -3
  41. package/test/support/testConfig.js +69 -0
  42. package/test/testConfig.js +1 -69
  43. package/test-output.log +72 -0
  44. package/test/TESTING.md +0 -327
  45. package/test/config/CONFIGURATION.md +0 -170
  46. package/test/diagnostic-protocol-mappers.js +0 -189
  47. package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +0 -262
  48. package/test/helpers/setup.js +0 -186
@@ -0,0 +1,102 @@
1
+ const path = require('path');
2
+ const { expect } = require('chai');
3
+
4
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
5
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
6
+
7
+ const { conf } = require('propertiesmanager');
8
+ const KeycloakManager = require('../../index');
9
+ const { TEST_REALM, generateUniqueName } = require('../testConfig');
10
+
11
+ const config = {
12
+ baseUrl: conf.keycloak.baseUrl,
13
+ realmName: conf.keycloak.realmName,
14
+ clientId: conf.keycloak.clientId,
15
+ clientSecret: conf.keycloak.clientSecret,
16
+ grantType: conf.keycloak.grantType,
17
+ username: conf.keycloak.username,
18
+ password: conf.keycloak.password,
19
+ tokenLifeSpan: conf.keycloak.tokenLifeSpan
20
+ };
21
+
22
+ /**
23
+ * Integration tests for Attack Detection Handler
24
+ * Tests brute force detection, login failure management
25
+ */
26
+ describe('Attack Detection Handler Tests', function () {
27
+ this.timeout(10000);
28
+
29
+ const testUserData = {
30
+ username: 'test-bruteforce-user',
31
+ email: 'bruteforce@test.com',
32
+ enabled: true,
33
+ credentials: [{ type: 'password', value: 'wrongpassword123', temporary: false }]
34
+ };
35
+
36
+ let createdUserId;
37
+
38
+ before(async function () {
39
+ await KeycloakManager.configure(config);
40
+
41
+ // Create a test user for brute force testing
42
+ const result = await KeycloakManager.users.create(testUserData);
43
+ createdUserId = result.id;
44
+ });
45
+
46
+ after(async function () {
47
+ // Clean up test user
48
+ if (createdUserId) {
49
+ await KeycloakManager.users.del({ id: createdUserId });
50
+ }
51
+ KeycloakManager.stop();
52
+ });
53
+
54
+ describe('Brute Force Status', function () {
55
+ it('should get brute force status for user', async function () {
56
+ try {
57
+ const status = await KeycloakManager.attackDetection.getUserBruteForceStatus({
58
+ id: createdUserId
59
+ });
60
+
61
+ // Status should exist (even if no failures yet)
62
+ expect(status).to.exist;
63
+ } catch (error) {
64
+ // API might not be available in all Keycloak versions
65
+ if (error.response?.status === 404) {
66
+ this.skip();
67
+ }
68
+ throw error;
69
+ }
70
+ });
71
+
72
+ it('should clear user login failures', async function () {
73
+ try {
74
+ await KeycloakManager.attackDetection.clearUserLoginFailures({
75
+ id: createdUserId
76
+ });
77
+ // If no error thrown, operation succeeded
78
+ expect(true).to.be.true;
79
+ } catch (error) {
80
+ // API might not be available in all Keycloak versions
81
+ if (error.response?.status === 404) {
82
+ this.skip();
83
+ }
84
+ throw error;
85
+ }
86
+ });
87
+
88
+ it('should clear all login failures in realm', async function () {
89
+ try {
90
+ await KeycloakManager.attackDetection.clearAllLoginFailures({});
91
+ // If no error thrown, operation succeeded
92
+ expect(true).to.be.true;
93
+ } catch (error) {
94
+ // API might not be available in all Keycloak versions
95
+ if (error.response?.status === 404) {
96
+ this.skip();
97
+ }
98
+ throw error;
99
+ }
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,79 @@
1
+ const path = require('path');
2
+ const { expect } = require('chai');
3
+
4
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
5
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
6
+
7
+ const keycloakManager = require('keycloak-api-manager');
8
+ const { KEYCLOAK_CONFIG, TEST_CLIENT_ID, TEST_REALM } = require('../testConfig');
9
+
10
+ function buildConfig(overrides = {}) {
11
+ return {
12
+ ...KEYCLOAK_CONFIG,
13
+ ...overrides,
14
+ };
15
+ }
16
+
17
+ describe('Authentication - client_credentials grant', function () {
18
+ this.timeout(20000);
19
+
20
+ let testClientId = null;
21
+ let testClientSecret = null;
22
+
23
+ before(async function () {
24
+ // Ensure we're using the test realm
25
+ keycloakManager.setConfig({ realmName: TEST_REALM });
26
+
27
+ const clients = await keycloakManager.clients.find({ clientId: TEST_CLIENT_ID });
28
+ const testClient = clients.find((client) => client.clientId === TEST_CLIENT_ID);
29
+
30
+ if (!testClient) {
31
+ this.skip();
32
+ return;
33
+ }
34
+
35
+ testClientId = testClient.clientId;
36
+ const secret = await keycloakManager.clients.getClientSecret({ id: testClient.id });
37
+ testClientSecret = secret?.value;
38
+
39
+ if (!testClientSecret) {
40
+ this.skip();
41
+ }
42
+ });
43
+
44
+ after(async function () {
45
+ if (!KEYCLOAK_CONFIG?.username || !KEYCLOAK_CONFIG?.password) {
46
+ return;
47
+ }
48
+
49
+ await keycloakManager.configure(
50
+ buildConfig({
51
+ grantType: KEYCLOAK_CONFIG.grantType || 'password',
52
+ tokenLifeSpan: KEYCLOAK_CONFIG.tokenLifeSpan || 60,
53
+ })
54
+ );
55
+ });
56
+
57
+ it('authenticates without refresh token errors', async function () {
58
+ if (!testClientId || !testClientSecret) {
59
+ this.skip();
60
+ return;
61
+ }
62
+
63
+ await keycloakManager.configure(
64
+ buildConfig({
65
+ realmName: TEST_REALM,
66
+ clientId: testClientId,
67
+ clientSecret: testClientSecret,
68
+ grantType: 'client_credentials',
69
+ tokenLifeSpan: 60,
70
+ username: undefined,
71
+ password: undefined,
72
+ })
73
+ );
74
+
75
+ const token = keycloakManager.getToken();
76
+ expect(token).to.have.property('accessToken');
77
+ expect(token.accessToken).to.be.a('string').and.to.have.length.greaterThan(0);
78
+ });
79
+ });
@@ -0,0 +1,162 @@
1
+ const path = require('path');
2
+ const { expect } = require('chai');
3
+
4
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
5
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
6
+
7
+ const { conf } = require('propertiesmanager');
8
+ const KeycloakManager = require('../../index');
9
+ const { TEST_REALM, generateUniqueName } = require('../testConfig');
10
+
11
+ const config = {
12
+ baseUrl: conf.keycloak.baseUrl,
13
+ realmName: conf.keycloak.realmName,
14
+ clientId: conf.keycloak.clientId,
15
+ clientSecret: conf.keycloak.clientSecret,
16
+ grantType: conf.keycloak.grantType,
17
+ username: conf.keycloak.username,
18
+ password: conf.keycloak.password,
19
+ tokenLifeSpan: conf.keycloak.tokenLifeSpan
20
+ };
21
+
22
+ /**
23
+ * Integration tests for Client Policies Handler
24
+ * Tests client policies and profiles configuration (Keycloak 12+)
25
+ */
26
+ describe('Client Policies Handler Tests', function () {
27
+ this.timeout(10000);
28
+
29
+ before(async function () {
30
+ await KeycloakManager.configure(config);
31
+ });
32
+
33
+ after(async function () {
34
+ KeycloakManager.stop();
35
+ });
36
+
37
+ describe('Client Policies', function () {
38
+ let originalPolicies;
39
+
40
+ it('should get client policies', async function () {
41
+ try {
42
+ const policiesResult = await KeycloakManager.clientPolicies.getPolicies({});
43
+
44
+ expect(policiesResult).to.exist;
45
+ expect(policiesResult).to.have.property('policies');
46
+ expect(policiesResult.policies).to.be.an('array');
47
+
48
+ originalPolicies = policiesResult;
49
+ } catch (error) {
50
+ // Client Policies API only available in Keycloak 12+
51
+ if (error.response?.status === 404 || error.message?.includes('clientPolicies')) {
52
+ this.skip();
53
+ }
54
+ throw error;
55
+ }
56
+ });
57
+
58
+ it('should update client policies', async function () {
59
+ if (!originalPolicies) this.skip();
60
+
61
+ const updatedPolicies = JSON.parse(JSON.stringify(originalPolicies));
62
+
63
+ // Add a test policy if it doesn't exist
64
+ const testPolicyExists = updatedPolicies.policies.some(
65
+ p => p.name === 'test-policy'
66
+ );
67
+
68
+ if (!testPolicyExists) {
69
+ updatedPolicies.policies.push({
70
+ name: 'test-policy',
71
+ description: 'Test policy for integration tests',
72
+ enabled: false,
73
+ conditions: [],
74
+ profiles: []
75
+ });
76
+ }
77
+
78
+ try {
79
+ await KeycloakManager.clientPolicies.updatePolicies({}, updatedPolicies);
80
+
81
+ // Verify the update
82
+ const verifyPolicies = await KeycloakManager.clientPolicies.getPolicies({});
83
+ const testPolicyFound = verifyPolicies.policies.some(
84
+ p => p.name === 'test-policy'
85
+ );
86
+
87
+ expect(testPolicyFound).to.be.true;
88
+
89
+ // Restore original policies
90
+ await KeycloakManager.clientPolicies.updatePolicies({}, originalPolicies);
91
+ } catch (error) {
92
+ // Restore on error
93
+ if (originalPolicies) {
94
+ await KeycloakManager.clientPolicies.updatePolicies({}, originalPolicies);
95
+ }
96
+ throw error;
97
+ }
98
+ });
99
+ });
100
+
101
+ describe('Client Profiles', function () {
102
+ let originalProfiles;
103
+
104
+ it('should get client profiles', async function () {
105
+ try {
106
+ const profilesResult = await KeycloakManager.clientPolicies.getProfiles({});
107
+
108
+ expect(profilesResult).to.exist;
109
+ expect(profilesResult).to.have.property('profiles');
110
+ expect(profilesResult.profiles).to.be.an('array');
111
+
112
+ originalProfiles = profilesResult;
113
+ } catch (error) {
114
+ // API might not be available
115
+ if (error.response?.status === 404) {
116
+ this.skip();
117
+ }
118
+ throw error;
119
+ }
120
+ });
121
+
122
+ it('should update client profiles', async function () {
123
+ if (!originalProfiles) this.skip();
124
+
125
+ const updatedProfiles = JSON.parse(JSON.stringify(originalProfiles));
126
+
127
+ // Add a test profile if it doesn't exist
128
+ const testProfileExists = updatedProfiles.profiles.some(
129
+ p => p.name === 'test-profile'
130
+ );
131
+
132
+ if (!testProfileExists) {
133
+ updatedProfiles.profiles.push({
134
+ name: 'test-profile',
135
+ description: 'Test profile for integration tests',
136
+ executors: []
137
+ });
138
+ }
139
+
140
+ try {
141
+ await KeycloakManager.clientPolicies.updateProfiles({}, updatedProfiles);
142
+
143
+ // Verify the update
144
+ const verifyProfiles = await KeycloakManager.clientPolicies.getProfiles({});
145
+ const testProfileFound = verifyProfiles.profiles.some(
146
+ p => p.name === 'test-profile'
147
+ );
148
+
149
+ expect(testProfileFound).to.be.true;
150
+
151
+ // Restore original profiles
152
+ await KeycloakManager.clientPolicies.updateProfiles({}, originalProfiles);
153
+ } catch (error) {
154
+ // Restore on error
155
+ if (originalProfiles) {
156
+ await KeycloakManager.clientPolicies.updateProfiles({}, originalProfiles);
157
+ }
158
+ throw error;
159
+ }
160
+ });
161
+ });
162
+ });
@@ -4,11 +4,11 @@ const https = require('https');
4
4
  const { expect } = require('chai');
5
5
 
6
6
  process.env.NODE_ENV = process.env.NODE_ENV || 'test';
7
- process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
7
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', '..', 'config');
8
8
 
9
9
  const { conf } = require('propertiesmanager');
10
10
  const keycloakManager = require('keycloak-api-manager');
11
- const { TEST_REALM } = require('../testConfig');
11
+ const { TEST_REALM } = require('../../testConfig');
12
12
 
13
13
  describe('Protocol Mappers - Debug Client Library', function () {
14
14
  this.timeout(10000);
@@ -0,0 +1,87 @@
1
+ const path = require('path');
2
+ const { expect } = require('chai');
3
+
4
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
5
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', 'config');
6
+
7
+ const { conf } = require('propertiesmanager');
8
+ const KeycloakManager = require('../../index');
9
+ const { TEST_REALM, generateUniqueName } = require('../testConfig');
10
+
11
+ const config = {
12
+ baseUrl: conf.keycloak.baseUrl,
13
+ realmName: conf.keycloak.realmName,
14
+ clientId: conf.keycloak.clientId,
15
+ clientSecret: conf.keycloak.clientSecret,
16
+ grantType: conf.keycloak.grantType,
17
+ username: conf.keycloak.username,
18
+ password: conf.keycloak.password,
19
+ tokenLifeSpan: conf.keycloak.tokenLifeSpan
20
+ };
21
+
22
+ /**
23
+ * Integration tests for Group Permissions
24
+ * Tests new permission management methods added to groupsHandler
25
+ */
26
+ describe('Group Permissions Tests', function () {
27
+ this.timeout(10000);
28
+
29
+ const testGroupData = {
30
+ name: `test-permissions-group-${Date.now()}`
31
+ };
32
+
33
+ let createdGroupId;
34
+
35
+ before(async function () {
36
+ await KeycloakManager.configure(config);
37
+
38
+ // Create a test group
39
+ const result = await KeycloakManager.groups.create(testGroupData);
40
+ createdGroupId = result.id;
41
+ });
42
+
43
+ after(async function () {
44
+ // Clean up test group
45
+ if (createdGroupId) {
46
+ await KeycloakManager.groups.del({ id: createdGroupId });
47
+ }
48
+ KeycloakManager.stop();
49
+ });
50
+
51
+ describe('Group Permission Management', function () {
52
+ it('should enable permissions for a group', async function () {
53
+ await KeycloakManager.groups.setPermissions(
54
+ { id: createdGroupId },
55
+ { enabled: true }
56
+ );
57
+
58
+ expect(true).to.be.true;
59
+ });
60
+
61
+ it('should get group permissions', async function () {
62
+ const permissions = await KeycloakManager.groups.listPermissions({
63
+ id: createdGroupId
64
+ });
65
+
66
+ expect(permissions).to.exist;
67
+ expect(permissions).to.have.property('enabled');
68
+ expect(permissions.enabled).to.be.true;
69
+
70
+ // Should have resource information when enabled
71
+ expect(permissions).to.have.property('resource');
72
+ });
73
+
74
+ it('should disable permissions for a group', async function () {
75
+ await KeycloakManager.groups.setPermissions(
76
+ { id: createdGroupId },
77
+ { enabled: false }
78
+ );
79
+
80
+ const permissions = await KeycloakManager.groups.listPermissions({
81
+ id: createdGroupId
82
+ });
83
+
84
+ expect(permissions.enabled).to.be.false;
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,112 @@
1
+ const path = require('path');
2
+ const { expect } = require('chai');
3
+
4
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
5
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', '..', 'config');
6
+
7
+ const keycloakManager = require('keycloak-api-manager');
8
+ const { KEYCLOAK_CONFIG, TEST_REALM, TEST_CLIENT_ID, TEST_USER_USERNAME, TEST_USER_PASSWORD } = require('../../testConfig');
9
+ const { loadMatrix } = require('../../helpers/matrix');
10
+
11
+ function buildConfig(overrides = {}) {
12
+ return {
13
+ ...KEYCLOAK_CONFIG,
14
+ ...overrides,
15
+ };
16
+ }
17
+
18
+ describe('Matrix - Authentication', function () {
19
+ this.timeout(30000);
20
+
21
+ const matrix = loadMatrix('auth');
22
+
23
+ let clientSecret = null;
24
+
25
+ before(async function () {
26
+ // Ensure we're using the test realm
27
+ keycloakManager.setConfig({ realmName: TEST_REALM });
28
+
29
+ const clients = await keycloakManager.clients.find({ clientId: TEST_CLIENT_ID });
30
+ const testClient = clients.find((client) => client.clientId === TEST_CLIENT_ID);
31
+ if (!testClient) {
32
+ this.skip();
33
+ return;
34
+ }
35
+
36
+ const secret = await keycloakManager.clients.getClientSecret({ id: testClient.id });
37
+ clientSecret = secret?.value || null;
38
+ });
39
+
40
+ after(async function () {
41
+ await keycloakManager.configure(
42
+ buildConfig({
43
+ grantType: KEYCLOAK_CONFIG.grantType || 'password',
44
+ tokenLifeSpan: KEYCLOAK_CONFIG.tokenLifeSpan || 60,
45
+ })
46
+ );
47
+ });
48
+
49
+ matrix.cases.forEach((testCase) => {
50
+ it(`auth case: ${testCase.name}`, async function () {
51
+ if (testCase.grantType === 'client_credentials' && !clientSecret) {
52
+ this.skip();
53
+ return;
54
+ }
55
+
56
+ if (testCase.grantType === 'refresh_token') {
57
+ // First obtain a refresh token using password grant
58
+ await keycloakManager.configure(
59
+ buildConfig({
60
+ realmName: TEST_REALM,
61
+ username: TEST_USER_USERNAME,
62
+ password: TEST_USER_PASSWORD,
63
+ grantType: 'password',
64
+ tokenLifeSpan: 60,
65
+ })
66
+ );
67
+ const token = keycloakManager.getToken();
68
+ if (!token?.refreshToken) {
69
+ this.skip();
70
+ return;
71
+ }
72
+
73
+ await keycloakManager.configure(
74
+ buildConfig({
75
+ realmName: TEST_REALM,
76
+ grantType: 'refresh_token',
77
+ refreshToken: token.refreshToken,
78
+ tokenLifeSpan: 60,
79
+ })
80
+ );
81
+ } else if (testCase.grantType === 'client_credentials') {
82
+ await keycloakManager.configure(
83
+ buildConfig({
84
+ realmName: TEST_REALM,
85
+ clientId: TEST_CLIENT_ID,
86
+ clientSecret,
87
+ grantType: 'client_credentials',
88
+ tokenLifeSpan: 60,
89
+ username: undefined,
90
+ password: undefined,
91
+ })
92
+ );
93
+ } else {
94
+ await keycloakManager.configure(
95
+ buildConfig({
96
+ realmName: TEST_REALM,
97
+ username: TEST_USER_USERNAME,
98
+ password: TEST_USER_PASSWORD,
99
+ grantType: testCase.grantType,
100
+ offlineToken: testCase.offlineToken || false,
101
+ scope: testCase.scope,
102
+ tokenLifeSpan: 60,
103
+ })
104
+ );
105
+ }
106
+
107
+ const token = keycloakManager.getToken();
108
+ expect(token).to.have.property('accessToken');
109
+ expect(token.accessToken).to.be.a('string').and.to.have.length.greaterThan(0);
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,59 @@
1
+ const path = require('path');
2
+ const { expect } = require('chai');
3
+
4
+ process.env.NODE_ENV = process.env.NODE_ENV || 'test';
5
+ process.env.PROPERTIES_PATH = path.join(__dirname, '..', '..', 'config');
6
+
7
+ const keycloakManager = require('keycloak-api-manager');
8
+ const { TEST_REALM } = require('../../testConfig');
9
+ const { loadMatrix, uniqueName } = require('../../helpers/matrix');
10
+
11
+ describe('Matrix - Clients', function () {
12
+ this.timeout(30000);
13
+
14
+ const matrix = loadMatrix('clients');
15
+
16
+ before(function () {
17
+ keycloakManager.setConfig({ realmName: TEST_REALM });
18
+ });
19
+
20
+ matrix.cases.forEach((testCase) => {
21
+ it(`client case: ${testCase.name}`, async function () {
22
+ const clientId = uniqueName(`matrix-client-${testCase.name}`);
23
+
24
+ const created = await keycloakManager.clients.create({
25
+ clientId,
26
+ enabled: true,
27
+ protocol: 'openid-connect',
28
+ publicClient: testCase.publicClient,
29
+ serviceAccountsEnabled: testCase.serviceAccountsEnabled,
30
+ directAccessGrantsEnabled: testCase.directAccessGrantsEnabled,
31
+ standardFlowEnabled: testCase.standardFlowEnabled,
32
+ consentRequired: testCase.consentRequired,
33
+ redirectUris: ['http://localhost:*'],
34
+ webOrigins: ['*'],
35
+ });
36
+
37
+ expect(created).to.exist;
38
+
39
+ const found = await keycloakManager.clients.find({ clientId });
40
+ expect(found).to.be.an('array');
41
+ const stored = found.find((item) => item.clientId === clientId);
42
+ expect(stored).to.exist;
43
+
44
+ await keycloakManager.clients.update(
45
+ { id: stored.id },
46
+ {
47
+ description: `updated-${clientId}`,
48
+ consentRequired: false,
49
+ }
50
+ );
51
+
52
+ const updated = await keycloakManager.clients.findOne({ id: stored.id });
53
+ expect(updated).to.exist;
54
+ expect(updated.description).to.equal(`updated-${clientId}`);
55
+
56
+ await keycloakManager.clients.del({ id: stored.id });
57
+ });
58
+ });
59
+ });