keycloak-api-manager 3.2.1 → 4.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 (52) hide show
  1. package/.env.example +27 -0
  2. package/Handlers/clientsHandler.js +240 -30
  3. package/Handlers/groupsHandler.js +16 -1
  4. package/Handlers/httpApiHelper.js +87 -0
  5. package/Handlers/realmsHandler.js +26 -4
  6. package/Handlers/usersHandler.js +43 -10
  7. package/README.md +341 -10
  8. package/index.js +149 -29
  9. package/package.json +3 -14
  10. package/test/.mocharc.json +4 -0
  11. package/test/TESTING.md +327 -0
  12. package/test/config/CONFIGURATION.md +170 -0
  13. package/test/config/default.json +36 -0
  14. package/test/config/local.json.example +7 -0
  15. package/test/config/secrets.json.example +7 -0
  16. package/test/diagnostic-protocol-mappers.js +189 -0
  17. package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +262 -0
  18. package/test/docker-keycloak/certs/.gitkeep +7 -0
  19. package/test/docker-keycloak/docker-compose-https.yml +50 -0
  20. package/test/docker-keycloak/docker-compose.yml +59 -0
  21. package/test/docker-keycloak/setup-keycloak.js +501 -0
  22. package/test/enableServerFeatures.js +315 -0
  23. package/test/helpers/config.js +218 -0
  24. package/test/helpers/docker-helpers.js +513 -0
  25. package/test/helpers/setup.js +186 -0
  26. package/test/package.json +18 -0
  27. package/test/setup.js +194 -0
  28. package/test/specs/authenticationManagement.test.js +224 -0
  29. package/test/specs/clientScopes.test.js +388 -0
  30. package/test/specs/clients.test.js +791 -0
  31. package/test/specs/components.test.js +151 -0
  32. package/test/specs/debugClientLibrary.test.js +88 -0
  33. package/test/specs/groups.test.js +362 -0
  34. package/test/specs/identityProviders.test.js +292 -0
  35. package/test/specs/realms.test.js +390 -0
  36. package/test/specs/roles.test.js +322 -0
  37. package/test/specs/users.test.js +445 -0
  38. package/test/testConfig.js +69 -0
  39. package/.mocharc.json +0 -7
  40. package/docker-compose.yml +0 -27
  41. package/test/authenticationManagement.test.js +0 -329
  42. package/test/clientScopes.test.js +0 -256
  43. package/test/clients.test.js +0 -284
  44. package/test/components.test.js +0 -122
  45. package/test/config.js +0 -137
  46. package/test/docker-helpers.js +0 -111
  47. package/test/groups.test.js +0 -284
  48. package/test/identityProviders.test.js +0 -197
  49. package/test/mocha.env.js +0 -55
  50. package/test/realms.test.js +0 -349
  51. package/test/roles.test.js +0 -215
  52. package/test/users.test.js +0 -405
package/index.js CHANGED
@@ -13,29 +13,70 @@ var authenticationManagementHandler=require('./Handlers/authenticationManagement
13
13
  var request=require('request');
14
14
 
15
15
  let configAdminclient=null;
16
+ let tokenRefreshInterval=null;
16
17
 
17
18
  /**
18
- * ***************************** - ENGLISH - *******************************
19
- *
20
- * Main supported options:
21
- * -baseUrl: Keycloak base Url
22
- * - realmName: [Optional] A String that specifies the realm to authenticate against, if different from the keyCloakConfig.realm parameter.
23
- * If you intend to use Keycloak administrator credentials, this should be set to 'master'.
24
- * - scope: [Optional] A string that specifies The OAuth2 scope requested during authentication (optional).
25
- * Typically not required for administrative clients. example:openid profile
26
- * - requestOptions: [Optional] JSON parameters to configure HTTP requests (such as custom headers, timeouts, etc.).
27
- * It is compatible with the Fetch API standard. Fetch request options
28
- * https://developer.mozilla.org/en-US/docs/Web/API/fetch#options
29
- * - username: [Optional] string username. Required when using the password grant type.
30
- * - password: [Optional] string password. Required when using the password grant type.
31
- * - grantType: The OAuth2 grant type used for authentication.
32
- * Possible values: 'password', 'client_credentials', 'refresh_token', etc.
33
- * - tokenLifeSpan: Lifetime of an access token expressed in seconds.
34
- * - clientId: string containing the client ID configured in Keycloak. Required for all grant types.
35
- * - clientSecret: [Optional] string containing the client secret of the client. Required for client_credentials or confidential clients.
36
- * - totp: string for Time-based One-Time Password (TOTP) for multi-factor authentication (MFA), if enabled for the user.
37
- * - offlineToken: [Optional] boolean value. If true, requests an offline token (used for long-lived refresh tokens). Default is false.
38
- * - refreshToken: [Optional] string containing a valid refresh token to request a new access token when using the refresh_token grant type.
19
+ * Configure and initialize the Keycloak Admin Client for managing Keycloak resources.
20
+ *
21
+ * This function MUST be called before using any administrative functions exposed by this library.
22
+ * It sets up the admin client with proper credentials and establishes automatic token refresh.
23
+ *
24
+ * @param {Object} adminClientCredentials - Configuration object for Keycloak Admin Client
25
+ * @param {string} adminClientCredentials.baseUrl - Keycloak server base URL (e.g., "http://localhost:8080")
26
+ * @param {string} adminClientCredentials.realmName - Realm to authenticate against (use "master" for admin operations)
27
+ * @param {string} adminClientCredentials.clientId - Client ID configured in Keycloak (e.g., "admin-cli")
28
+ * @param {string} adminClientCredentials.grantType - OAuth2 grant type ("password", "client_credentials", etc.)
29
+ * @param {number} adminClientCredentials.tokenLifeSpan - Access token lifetime in seconds (recommended: 60-120)
30
+ * @param {string} [adminClientCredentials.username] - Admin username (required for "password" grant type)
31
+ * @param {string} [adminClientCredentials.password] - Admin password (required for "password" grant type)
32
+ * @param {string} [adminClientCredentials.clientSecret] - Client secret (required for "client_credentials" or confidential clients)
33
+ * @param {string} [adminClientCredentials.scope] - OAuth2 scope (optional, e.g., "openid profile")
34
+ * @param {Object} [adminClientCredentials.requestOptions] - Custom HTTP options (headers, timeout, etc.) compatible with Fetch API
35
+ * @param {string} [adminClientCredentials.totp] - Time-based One-Time Password for MFA (if enabled)
36
+ * @param {boolean} [adminClientCredentials.offlineToken=false] - Request offline token for long-lived refresh tokens
37
+ * @param {string} [adminClientCredentials.refreshToken] - Existing refresh token (for "refresh_token" grant type)
38
+ *
39
+ * @returns {Promise<void>} Resolves when configuration is complete and authentication successful
40
+ *
41
+ * @throws {Error} If authentication fails or required parameters are missing
42
+ *
43
+ * @example
44
+ * // Basic configuration with password grant
45
+ * await KeycloakManager.configure({
46
+ * baseUrl: 'http://localhost:8080',
47
+ * realmName: 'master',
48
+ * clientId: 'admin-cli',
49
+ * username: 'admin',
50
+ * password: 'admin',
51
+ * grantType: 'password',
52
+ * tokenLifeSpan: 120
53
+ * });
54
+ *
55
+ * @example
56
+ * // Configuration with client credentials
57
+ * await KeycloakManager.configure({
58
+ * baseUrl: 'https://auth.example.com',
59
+ * realmName: 'master',
60
+ * clientId: 'service-account',
61
+ * clientSecret: 'secret-key',
62
+ * grantType: 'client_credentials',
63
+ * tokenLifeSpan: 60
64
+ * });
65
+ *
66
+ * @note After successful configuration, all admin handlers are exposed:
67
+ * - KeycloakManager.realms - Realm management
68
+ * - KeycloakManager.users - User management
69
+ * - KeycloakManager.clients - Client management
70
+ * - KeycloakManager.clientScopes - Client scope management
71
+ * - KeycloakManager.identityProviders - Identity provider management
72
+ * - KeycloakManager.groups - Group management
73
+ * - KeycloakManager.roles - Role management
74
+ * - KeycloakManager.components - Component management
75
+ * - KeycloakManager.authenticationManagement - Authentication flow management
76
+ *
77
+ * @note Token Refresh: The client automatically refreshes the access token at intervals
78
+ * calculated as (tokenLifeSpan * 1000) / 2 milliseconds. Call KeycloakManager.stop()
79
+ * to clear the refresh interval and allow graceful process termination.
39
80
  */
40
81
  exports.configure=async function(adminClientCredentials){
41
82
  configAdminclient={
@@ -48,15 +89,25 @@ exports.configure=async function(adminClientCredentials){
48
89
  configAdminclient.clientSecret=adminClientCredentials.clientSecret;
49
90
 
50
91
 
51
- let tokenLifeSpan= (adminClientCredentials.tokenLifeSpan *1000)/2;
92
+ let tokenLifeSpan= Number(adminClientCredentials.tokenLifeSpan);
93
+ tokenLifeSpan = Number.isFinite(tokenLifeSpan) && tokenLifeSpan > 0
94
+ ? (tokenLifeSpan * 1000) / 2
95
+ : 30000;
52
96
  delete adminClientCredentials.baseUrl;
53
97
  delete adminClientCredentials.realmName;
54
98
  delete adminClientCredentials.tokenLifeSpan;
55
99
  await kcAdminClient.auth(adminClientCredentials);
56
100
 
57
- setInterval(async ()=>{
101
+ if (tokenRefreshInterval) {
102
+ clearInterval(tokenRefreshInterval);
103
+ }
104
+
105
+ tokenRefreshInterval = setInterval(async ()=>{
58
106
  await kcAdminClient.auth(adminClientCredentials);
59
107
  },tokenLifeSpan);
108
+ if (tokenRefreshInterval.unref) {
109
+ tokenRefreshInterval.unref();
110
+ }
60
111
 
61
112
  realmHandler.setKcAdminClient(kcAdminClient);
62
113
  exports.realms=realmHandler;
@@ -89,13 +140,39 @@ exports.configure=async function(adminClientCredentials){
89
140
  //exports = kcAdminClient;
90
141
  };
91
142
 
92
-
93
- //TODO: Remove da documentare
143
+ /**
144
+ * Updates the runtime configuration of the Keycloak Admin Client instance.
145
+ * Allows switching the target realm, base URL, or HTTP request options without
146
+ * reinitializing the client or re-authenticating.
147
+ *
148
+ * @param {Object} configToOverride - Configuration object to update
149
+ * @param {string} [configToOverride.realmName] - The name of the target realm for subsequent API requests
150
+ * @param {string} [configToOverride.baseUrl] - The base URL of the Keycloak server (e.g., https://auth.example.com)
151
+ * @param {Object} [configToOverride.requestOptions] - Custom HTTP options (headers, timeout, etc.) applied to API calls
152
+ * @param {string} [configToOverride.realmPath] - A custom realm path if your Keycloak instance uses a non-standard realm route
153
+ * @returns {void}
154
+ *
155
+ * @note Calling setConfig does not perform authentication - it only changes configuration values in memory.
156
+ * The authentication token already stored in the admin client remains active until it expires.
157
+ * Only the properties explicitly passed in the config object are updated; all others remain unchanged.
158
+ */
94
159
  exports.setConfig=function(configToOverride){
95
160
  return(kcAdminClient.setConfig(configToOverride));
96
161
  }
97
- //TODO: Remove da documentare
98
- // restituisce il token utilizzato dalla libreria per comunicare con la keycloak API
162
+
163
+ /**
164
+ * Retrieves the current authentication tokens used by the Keycloak Admin Client.
165
+ * Returns both the access token (used for API authorization) and the refresh token
166
+ * (used to renew the session when the access token expires).
167
+ *
168
+ * @returns {Object} Token object containing:
169
+ * @returns {string} accessToken - The active access token string currently held by the Keycloak Admin Client
170
+ * @returns {string} refreshToken - The corresponding refresh token string, if available
171
+ *
172
+ * @note The tokens are managed internally by the Keycloak Admin Client after successful authentication.
173
+ * The accessToken typically expires after a short period (e.g., 60 seconds by default).
174
+ * If the client is not authenticated or the session has expired, both values may be undefined.
175
+ */
99
176
  exports.getToken=function(){
100
177
  return({
101
178
  accessToken:kcAdminClient.accessToken,
@@ -103,8 +180,51 @@ exports.getToken=function(){
103
180
  });
104
181
  }
105
182
 
106
- //TODO: Remove da documentare
107
- //permette ad un utente o un client di autenticarsi su keycloack ed oottenere un token
183
+ /**
184
+ * Cleanly stops the Keycloak Admin Client by clearing the automatic token refresh interval.
185
+ * When the admin client is configured with tokenLifeSpan, it automatically refreshes
186
+ * the access token at regular intervals to maintain the session.
187
+ * Calling stop() clears this interval, allowing your Node.js process to exit gracefully.
188
+ *
189
+ * @returns {void}
190
+ *
191
+ * @note This method should be called when you're done using the Keycloak Admin Client
192
+ * and want to terminate your application. It's particularly important in test environments
193
+ * or CLI scripts where the process needs to exit cleanly. The method is safe to call
194
+ * multiple times; subsequent calls have no effect.
195
+ *
196
+ * @example
197
+ * // Configure and use the admin client
198
+ * await KeycloakManager.configure({ ... });
199
+ * const users = await KeycloakManager.users.find();
200
+ * // Clean up and allow process to exit
201
+ * KeycloakManager.stop();
202
+ */
203
+ exports.stop=function(){
204
+ if (tokenRefreshInterval) {
205
+ clearInterval(tokenRefreshInterval);
206
+ tokenRefreshInterval=null;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Allows a user or client to authenticate against a Keycloak realm and obtain an access token.
212
+ * Sends a direct HTTP POST request to the Keycloak OpenID Connect token endpoint using the provided credentials.
213
+ *
214
+ * @param {Object} credentials - Authentication details
215
+ * @param {string} [credentials.username] - Username of the user (required for password grant)
216
+ * @param {string} [credentials.password] - Password of the user (required for password grant)
217
+ * @param {string} credentials.grant_type - The OAuth2 grant type (e.g. "password", "client_credentials", "refresh_token")
218
+ * @returns {Promise<Object>} Token response object from Keycloak
219
+ *
220
+ * @example
221
+ * const tokenResponse = await KeycloakManager.auth({
222
+ * username: "demo",
223
+ * password: "demo123",
224
+ * grant_type: "password",
225
+ * });
226
+ * console.log("Access Token:", tokenResponse.access_token);
227
+ */
108
228
  exports.auth=async function(credentials){
109
229
  credentials.client_id=configAdminclient.clientId;
110
230
  credentials.client_secret=configAdminclient.clientSecret;
package/package.json CHANGED
@@ -1,17 +1,11 @@
1
1
  {
2
2
  "name": "keycloak-api-manager",
3
- "version": "3.2.1",
3
+ "version": "4.0.0",
4
4
  "description": "Keycloak-api-manager is a lightweight Node.js wrapper for the Keycloak Admin REST API. It provides an easy-to-use functional methods and functions to manage realms, users, roles, clients, groups, and permissions directly from your application code — just like you would from the Keycloak admin console.",
5
5
  "main": "index.js",
6
- "exports": {
7
- ".": {
8
- "import": "./index.mjs",
9
- "require": "./index.js"
10
- }
11
- },
12
6
  "scripts": {
13
- "test": "mocha",
14
- "test:watch": "mocha --watch --watch-extensions js"
7
+ "test": "npm --prefix test install && npm --prefix test test",
8
+ "setup-keycloak": "node test/docker-keycloak/setup-keycloak.js"
15
9
  },
16
10
  "dependencies": {
17
11
  "@keycloak/keycloak-admin-client": "^26.3.2",
@@ -30,11 +24,6 @@
30
24
  "serve-favicon": "^2.5.0",
31
25
  "underscore": "^1.13.7"
32
26
  },
33
- "devDependencies": {
34
- "chai": "^4.3.10",
35
- "mocha": "^10.2.0",
36
- "dockerode": "^4.0.2"
37
- },
38
27
  "keywords": [
39
28
  "keycloak",
40
29
  "user",
@@ -0,0 +1,4 @@
1
+ {
2
+ "file": ["setup.js"],
3
+ "spec": ["specs/*.test.js"]
4
+ }
@@ -0,0 +1,327 @@
1
+ # Keycloak API Manager - Test Suite
2
+
3
+ Comprehensive test suite for `keycloak-api-manager` library.
4
+
5
+ ## Overview
6
+
7
+ This test workspace validates all administrative functions provided by the library against a real Keycloak instance. Tests cover:
8
+
9
+ - **Authentication Management** - Required actions, authentication flows, executions
10
+ - **Clients** - CRUD operations, roles, secrets, scopes, sessions, authorization services
11
+ - **Client Scopes** - Management, protocol mappers, scope mappings
12
+ - **Components** - Provider listing, CRUD operations, sub-components
13
+ - **Groups** - CRUD, members, role mappings, admin permissions
14
+ - **Identity Providers** - Factory listing, CRUD, mappers
15
+ - **Realms** - Full lifecycle, configuration, events, keys, localization
16
+ - **Roles** - CRUD, composite roles, user assignments
17
+ - **Users** - CRUD, credentials, groups, role mappings, sessions, impersonation
18
+
19
+ ## Architecture
20
+
21
+ ### Shared Test Realm Strategy
22
+
23
+ Unlike traditional test approaches where each suite creates/destroys its own realm, this implementation uses a **shared test realm** created once before all tests:
24
+
25
+ **Benefits**:
26
+ - ⚡ **5-10x faster** - Eliminates repeated realm creation overhead (~2-5s per test)
27
+ - 🔄 **Consistent environment** - All tests run against identical infrastructure
28
+ - 🛡️ **Idempotent setup** - Running tests multiple times doesn't recreate existing resources
29
+ - 🎯 **Isolated resources** - Tests create unique resources (e.g., `user-${timestamp}`) to avoid conflicts
30
+
31
+ **Trade-offs**:
32
+ - Requires cleanup of test-specific resources (most tests handle this)
33
+ - Shared realm means tests can't modify realm-level settings without affecting others
34
+
35
+ ### File Structure
36
+
37
+ ```
38
+ test/
39
+ ├── config/ # Configuration files (propertiesmanager)
40
+ │ ├── default.json # Base configuration (committed)
41
+ │ ├── secrets.json # Passwords (gitignored, required)
42
+ │ ├── local.json # Developer overrides (gitignored, optional)
43
+ │ ├── local.json.example # Template for local.json
44
+ │ └── CONFIGURATION.md # Configuration documentation
45
+ ├── specs/ # Test suites
46
+ │ ├── authenticationManagement.test.js
47
+ │ ├── clients.test.js
48
+ │ ├── clientScopes.test.js
49
+ │ ├── components.test.js
50
+ │ ├── groups.test.js
51
+ │ ├── identityProviders.test.js
52
+ │ ├── realms.test.js
53
+ │ ├── roles.test.js
54
+ │ └── users.test.js
55
+ ├── .mocharc.json # Mocha configuration
56
+ ├── enableServerFeatures.js # Creates shared test infrastructure
57
+ ├── package.json # Test dependencies
58
+ ├── setup.js # Global Mocha hooks
59
+ └── testConfig.js # Configuration loader and exports
60
+ ```
61
+
62
+ ### Key Components
63
+
64
+ #### `testConfig.js`
65
+ Centralized configuration module that:
66
+ - Sets up propertiesmanager environment
67
+ - Loads and merges config files (default → secrets → local)
68
+ - Exports standardized constants for tests:
69
+ - `KEYCLOAK_CONFIG` - Admin connection settings
70
+ - `TEST_REALM` - Shared realm name
71
+ - `TEST_CLIENT_*` - Client configuration
72
+ - `TEST_USER_*` - User details and credentials
73
+ - `TEST_ROLES` - Array of test roles
74
+ - `TEST_GROUP_NAME` - Test group name
75
+ - `TEST_CLIENT_SCOPE` - Client scope name
76
+ - `generateUniqueName()` - Helper for unique resource names
77
+
78
+ #### `setup.js`
79
+ Global Mocha hooks loaded via `.mocharc.json`:
80
+ - Runs `enableServerFeatures()` once in `before()` hook
81
+ - Sets 30s timeout for realm creation
82
+ - Logs setup progress to console
83
+
84
+ #### `enableServerFeatures.js`
85
+ Infrastructure setup script that creates (if not exists):
86
+ 1. **Test Realm** - Isolated environment for all tests
87
+ 2. **Test Client** - Service account client for auth tests
88
+ 3. **Test User** - Standard user with credentials
89
+ 4. **Test Roles** - Multiple roles for RBAC testing
90
+ 5. **Test Group** - Group for membership tests
91
+ 6. **Fine-grained Permissions** - Admin permissions (if server supports)
92
+ 7. **Client Scope** - For scope mapping tests
93
+
94
+ Runs idempotently - safe to execute multiple times.
95
+
96
+ #### `.mocharc.json`
97
+ Mocha configuration:
98
+ ```json
99
+ {
100
+ "file": ["setup.js"], // Load global hooks before tests
101
+ "spec": ["specs/*.test.js"] // Run all test suites
102
+ }
103
+ ```
104
+
105
+ ## Running Tests
106
+
107
+ ### Prerequisites
108
+
109
+ 1. **Keycloak Instance**: Running and accessible
110
+ 2. **Configuration**: Create `test/config/secrets.json` with admin password
111
+ 3. **SSH Tunnel** (if remote): Ensure tunnel is active before running tests
112
+
113
+ ### Commands
114
+
115
+ ```bash
116
+ # Install dependencies and run all tests
117
+ npm test
118
+
119
+ # Or step-by-step:
120
+ npm --prefix test install # Install test dependencies
121
+ npm --prefix test test # Run all tests
122
+
123
+ # Run specific test suite
124
+ npm --prefix test test -- --grep "Users Handler"
125
+
126
+ # Run specific test
127
+ npm --prefix test test -- --grep "should create, find, update, and delete clients"
128
+ ```
129
+
130
+ ## Diagnostics
131
+
132
+ ### Protocol Mapper Diagnostic Script
133
+
134
+ The file [test/diagnostic-protocol-mappers.js](test/diagnostic-protocol-mappers.js) is a manual troubleshooting script.
135
+ It is not part of the automated Mocha test suite. It is used to validate protocol mapper creation via:
136
+
137
+ - Direct Admin REST API calls
138
+ - The library client (`keycloak-api-manager`)
139
+
140
+ Run it manually when you need to debug protocol mapper behavior:
141
+
142
+ ```bash
143
+ node test/diagnostic-protocol-mappers.js
144
+ ```
145
+
146
+ It uses the same `test/config` configuration and the shared test realm.
147
+
148
+ ### Expected Output
149
+
150
+ ```
151
+ propertiesmanager Configuration loaded successfully for environment: test
152
+
153
+ === Running global test setup ===
154
+
155
+ Configuring Keycloak Admin Client...
156
+
157
+ 1. Setting up test realm: keycloak-api-manager-test-realm
158
+ ✓ Test realm already exists: keycloak-api-manager-test-realm
159
+
160
+ 2. Setting up test client...
161
+ ✓ Test client updated: test-client
162
+
163
+ [... additional setup output ...]
164
+
165
+ ✓ Keycloak server configuration complete!
166
+
167
+ === Global setup complete ===
168
+
169
+ Users Handler
170
+ ✔ should find, findOne, count and update users
171
+ ✔ should manage password and credentials
172
+ [... more tests ...]
173
+
174
+ 59 passing (9s)
175
+ 7 pending
176
+ ```
177
+
178
+ ## Configuration
179
+
180
+ Configuration uses [propertiesmanager](https://www.npmjs.com/package/propertiesmanager) for hierarchical config management.
181
+
182
+ **Quick Setup**:
183
+
184
+ 1. Copy secrets template:
185
+ ```bash
186
+ cp test/config/local.json.example test/config/secrets.json
187
+ ```
188
+
189
+ 2. Edit `test/config/secrets.json`:
190
+ ```json
191
+ {
192
+ "test": {
193
+ "keycloak": {
194
+ "password": "your-admin-password"
195
+ },
196
+ "realm": {
197
+ "user": {
198
+ "password": "test-user-password"
199
+ }
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ 3. (Optional) Override baseUrl in `test/config/local.json`:
206
+ ```json
207
+ {
208
+ "test": {
209
+ "keycloak": {
210
+ "baseUrl": "http://localhost:8080"
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ See [config/CONFIGURATION.md](config/CONFIGURATION.md) for detailed configuration documentation.
217
+
218
+ ## Test Writing Guidelines
219
+
220
+ ### Use Shared Configuration
221
+
222
+ ```javascript
223
+ const {
224
+ TEST_REALM,
225
+ TEST_CLIENT_ID,
226
+ TEST_USER_USERNAME,
227
+ generateUniqueName
228
+ } = require('../testConfig');
229
+ ```
230
+
231
+ ### Create Unique Resources
232
+
233
+ Avoid conflicts by using timestamps or UUIDs:
234
+
235
+ ```javascript
236
+ const userName = generateUniqueName('test-user');
237
+ const roleName = `role-${Date.now()}`;
238
+ ```
239
+
240
+ ### Respect Shared Realm
241
+
242
+ Don't:
243
+ - Modify realm-level settings (will affect other tests)
244
+ - Delete the shared realm
245
+ - Assume exclusive access to shared resources (client, user, roles)
246
+
247
+ Do:
248
+ - Create test-specific resources with unique names
249
+ - Clean up resources created during test (in `after()` hooks)
250
+ - Use the shared infrastructure for read operations
251
+
252
+ ### Handle Feature Availability
253
+
254
+ Some Keycloak features may not be available on all server versions:
255
+
256
+ ```javascript
257
+ try {
258
+ await keycloakManager.users.listConsents({ realm: TEST_REALM, id: userId });
259
+ } catch (err) {
260
+ if (err.response?.status === 404) {
261
+ this.skip(); // Skip test if feature not available
262
+ }
263
+ throw err;
264
+ }
265
+ ```
266
+
267
+ ## Troubleshooting
268
+
269
+ ### Tests hang indefinitely
270
+ - Check Keycloak instance is running and accessible
271
+ - Verify SSH tunnel is active (if using remote Keycloak)
272
+ - Check `baseUrl` in configuration matches your setup
273
+
274
+ ### "Configuration loading failed"
275
+ - Ensure `test/config/secrets.json` exists
276
+ - Verify JSON syntax (no trailing commas)
277
+ - Check environment wrapper present: `{ "test": { ... } }`
278
+
279
+ ### "Access denied" or 403 errors
280
+ - Verify admin credentials in `secrets.json`
281
+ - Ensure admin user has realm-management permissions
282
+ - Check `realmName` is correct (usually "master" for admin user)
283
+
284
+ ### "Realm not found" errors
285
+ - Check global setup ran successfully (see console output)
286
+ - Verify shared realm created: look for "✓ Test realm created/exists"
287
+ - Ensure realm name matches configuration
288
+
289
+ ### Slow test execution
290
+ - First run is slower (~10-20s) due to realm setup
291
+ - Subsequent runs should be faster (~5-10s)
292
+ - If still slow, check network latency to Keycloak instance
293
+ - Consider using local Keycloak instance instead of remote tunnel
294
+
295
+ ### Tests fail inconsistently
296
+ - May indicate race conditions or shared resource conflicts
297
+ - Ensure test resources use unique names
298
+ - Check cleanup logic in `after()` hooks
299
+ - Run single test in isolation to verify: `--grep "test name"`
300
+
301
+ ## Performance Metrics
302
+
303
+ Typical performance on 2020 MacBook Pro with local Keycloak:
304
+
305
+ - **Global Setup**: ~10-15 seconds (runs once)
306
+ - **Full Suite**: ~9 seconds (59 tests)
307
+ - **Individual Test**: ~50-200ms average
308
+
309
+ Performance with remote Keycloak over SSH tunnel:
310
+ - **Global Setup**: ~15-25 seconds
311
+ - **Full Suite**: ~15-30 seconds
312
+
313
+ ## Contributing
314
+
315
+ When adding new tests:
316
+
317
+ 1. Follow existing patterns in `specs/` directory
318
+ 2. Import shared config from `testConfig.js`
319
+ 3. Use `generateUniqueName()` for test resources
320
+ 4. Add cleanup in `after()` hooks
321
+ 5. Handle optional features gracefully (use `this.skip()`)
322
+ 6. Document any new configuration requirements
323
+ 7. Update this README if adding new test categories
324
+
325
+ ## License
326
+
327
+ Same as parent project.