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.
- package/.env.example +27 -0
- package/Handlers/clientsHandler.js +240 -30
- package/Handlers/groupsHandler.js +16 -1
- package/Handlers/httpApiHelper.js +87 -0
- package/Handlers/realmsHandler.js +26 -4
- package/Handlers/usersHandler.js +43 -10
- package/README.md +341 -10
- package/index.js +149 -29
- package/package.json +3 -14
- package/test/.mocharc.json +4 -0
- package/test/TESTING.md +327 -0
- package/test/config/CONFIGURATION.md +170 -0
- package/test/config/default.json +36 -0
- package/test/config/local.json.example +7 -0
- package/test/config/secrets.json.example +7 -0
- package/test/diagnostic-protocol-mappers.js +189 -0
- package/test/docker-keycloak/DEPLOYMENT_GUIDE.md +262 -0
- package/test/docker-keycloak/certs/.gitkeep +7 -0
- package/test/docker-keycloak/docker-compose-https.yml +50 -0
- package/test/docker-keycloak/docker-compose.yml +59 -0
- package/test/docker-keycloak/setup-keycloak.js +501 -0
- package/test/enableServerFeatures.js +315 -0
- package/test/helpers/config.js +218 -0
- package/test/helpers/docker-helpers.js +513 -0
- package/test/helpers/setup.js +186 -0
- package/test/package.json +18 -0
- package/test/setup.js +194 -0
- package/test/specs/authenticationManagement.test.js +224 -0
- package/test/specs/clientScopes.test.js +388 -0
- package/test/specs/clients.test.js +791 -0
- package/test/specs/components.test.js +151 -0
- package/test/specs/debugClientLibrary.test.js +88 -0
- package/test/specs/groups.test.js +362 -0
- package/test/specs/identityProviders.test.js +292 -0
- package/test/specs/realms.test.js +390 -0
- package/test/specs/roles.test.js +322 -0
- package/test/specs/users.test.js +445 -0
- package/test/testConfig.js +69 -0
- package/.mocharc.json +0 -7
- package/docker-compose.yml +0 -27
- package/test/authenticationManagement.test.js +0 -329
- package/test/clientScopes.test.js +0 -256
- package/test/clients.test.js +0 -284
- package/test/components.test.js +0 -122
- package/test/config.js +0 -137
- package/test/docker-helpers.js +0 -111
- package/test/groups.test.js +0 -284
- package/test/identityProviders.test.js +0 -197
- package/test/mocha.env.js +0 -55
- package/test/realms.test.js +0 -349
- package/test/roles.test.js +0 -215
- 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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
+
"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": "
|
|
14
|
-
"
|
|
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",
|
package/test/TESTING.md
ADDED
|
@@ -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.
|