Skip to content

Commit ac972bd

Browse files
committed
feat(test-utils): improve cluster testing
- Add support for configuring replica authentication with 'masterauth' - Allow default client configuration during test cluster creation This improves the testing framework's flexibility by automatically configuring replica authentication when '--requirepass' is used and enabling custom client configurations across cluster nodes.
1 parent 0ce5e7a commit ac972bd

File tree

6 files changed

+93
-14
lines changed

6 files changed

+93
-14
lines changed

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/entraid/lib/entraid-credentials-provider.spec.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import { IdentityProvider, TokenManager, TokenResponse, TokenManagerConfig, BasicAuth } from '@redis/authx';
1+
import { AuthenticationResult } from '@azure/msal-node';
2+
import { IdentityProvider, TokenManager, TokenResponse, BasicAuth } from '@redis/authx';
23
import { EntraidCredentialsProvider } from './entraid-credentials-provider';
3-
import { strict as assert } from 'node:assert';
44
import { setTimeout } from 'timers/promises';
5-
import { AuthenticationResult } from '@azure/msal-common/node';
5+
import { strict as assert } from 'node:assert';
6+
import { GLOBAL, testUtils } from './test-utils'
7+
8+
9+
describe('EntraID authentication in cluster mode', () => {
10+
11+
testUtils.testWithCluster('sendCommand', async cluster => {
12+
assert.equal(
13+
await cluster.sendCommand(undefined, true, ['PING']),
14+
'PONG'
15+
);
16+
}, GLOBAL.CLUSTERS.PASSWORD_WITH_REPLICAS);
17+
})
618

719
describe('EntraID CredentialsProvider Subscription Behavior', () => {
820

packages/entraid/lib/test-utils.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { AuthenticationResult } from '@azure/msal-node';
2+
import { IdentityProvider, StreamingCredentialsProvider, TokenManager, TokenResponse } from '@redis/authx';
3+
import TestUtils from '@redis/test-utils';
4+
import { EntraidCredentialsProvider } from './entraid-credentials-provider';
5+
6+
export const testUtils = new TestUtils({
7+
dockerImageName: 'redis/redis-stack',
8+
dockerImageVersionArgument: 'redis-version',
9+
defaultDockerVersion: '7.4.0-v1'
10+
});
11+
12+
const DEBUG_MODE_ARGS = testUtils.isVersionGreaterThan([7]) ?
13+
['--enable-debug-command', 'yes'] :
14+
[];
15+
16+
const idp: IdentityProvider<AuthenticationResult> = {
17+
requestToken(): Promise<TokenResponse<AuthenticationResult>> {
18+
// @ts-ignore
19+
return Promise.resolve({
20+
ttlMs: 100000,
21+
token: {
22+
accessToken: 'password'
23+
}
24+
})
25+
}
26+
}
27+
28+
const tokenManager = new TokenManager<AuthenticationResult>(idp, { expirationRefreshRatio: 0.8 });
29+
const entraIdCredentialsProvider: StreamingCredentialsProvider = new EntraidCredentialsProvider(tokenManager, idp)
30+
31+
const PASSWORD_WITH_REPLICAS = {
32+
serverArguments: ['--requirepass', 'password', ...DEBUG_MODE_ARGS],
33+
numberOfMasters: 2,
34+
numberOfReplicas: 1,
35+
clusterConfiguration: {
36+
defaults: {
37+
credentialsProvider: entraIdCredentialsProvider
38+
}
39+
}
40+
}
41+
42+
export const GLOBAL = {
43+
CLUSTERS: {
44+
PASSWORD_WITH_REPLICAS
45+
}
46+
}

packages/entraid/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"@types/node": "^22.9.0",
2828
"dotenv": "^16.3.1",
2929
"express": "^4.21.1",
30-
"express-session": "^1.18.1"
30+
"express-session": "^1.18.1",
31+
"@redis/test-utils": "*"
3132
},
3233
"engines": {
3334
"node": ">= 18"

packages/test-utils/lib/dockers.ts

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RedisClusterClientOptions } from '@redis/client/dist/lib/cluster';
12
import { createConnection } from 'node:net';
23
import { once } from 'node:events';
34
import { createClient } from '@redis/client/index';
@@ -102,7 +103,8 @@ async function spawnRedisClusterNodeDockers(
102103
dockersConfig: RedisClusterDockersConfig,
103104
serverArguments: Array<string>,
104105
fromSlot: number,
105-
toSlot: number
106+
toSlot: number,
107+
clientConfig?: Partial<RedisClusterClientOptions>
106108
) {
107109
const range: Array<number> = [];
108110
for (let i = fromSlot; i < toSlot; i++) {
@@ -111,7 +113,8 @@ async function spawnRedisClusterNodeDockers(
111113

112114
const master = await spawnRedisClusterNodeDocker(
113115
dockersConfig,
114-
serverArguments
116+
serverArguments,
117+
clientConfig
115118
);
116119

117120
await master.client.clusterAddSlots(range);
@@ -127,7 +130,13 @@ async function spawnRedisClusterNodeDockers(
127130
'yes',
128131
'--cluster-node-timeout',
129132
'5000'
130-
]).then(async replica => {
133+
], clientConfig).then(async replica => {
134+
135+
const requirePassIndex = serverArguments.findIndex((x)=>x==='--requirepass');
136+
if(requirePassIndex!==-1) {
137+
const password = serverArguments[requirePassIndex+1];
138+
await replica.client.configSet({'masterauth': password})
139+
}
131140
await replica.client.clusterMeet('127.0.0.1', master.docker.port);
132141

133142
while ((await replica.client.clusterSlots()).length === 0) {
@@ -151,7 +160,8 @@ async function spawnRedisClusterNodeDockers(
151160

152161
async function spawnRedisClusterNodeDocker(
153162
dockersConfig: RedisClusterDockersConfig,
154-
serverArguments: Array<string>
163+
serverArguments: Array<string>,
164+
clientConfig?: Partial<RedisClusterClientOptions>
155165
) {
156166
const docker = await spawnRedisServerDocker(dockersConfig, [
157167
...serverArguments,
@@ -163,7 +173,8 @@ async function spawnRedisClusterNodeDocker(
163173
client = createClient({
164174
socket: {
165175
port: docker.port
166-
}
176+
},
177+
...clientConfig
167178
});
168179

169180
await client.connect();
@@ -178,7 +189,8 @@ const SLOTS = 16384;
178189

179190
async function spawnRedisClusterDockers(
180191
dockersConfig: RedisClusterDockersConfig,
181-
serverArguments: Array<string>
192+
serverArguments: Array<string>,
193+
clientConfig?: Partial<RedisClusterClientOptions>
182194
): Promise<Array<RedisServerDocker>> {
183195
const numberOfMasters = dockersConfig.numberOfMasters ?? 2,
184196
slotsPerNode = Math.floor(SLOTS / numberOfMasters),
@@ -191,7 +203,8 @@ async function spawnRedisClusterDockers(
191203
dockersConfig,
192204
serverArguments,
193205
fromSlot,
194-
toSlot
206+
toSlot,
207+
clientConfig
195208
)
196209
);
197210
}
@@ -234,13 +247,18 @@ function totalNodes(slots: any) {
234247

235248
const RUNNING_CLUSTERS = new Map<Array<string>, ReturnType<typeof spawnRedisClusterDockers>>();
236249

237-
export function spawnRedisCluster(dockersConfig: RedisClusterDockersConfig, serverArguments: Array<string>): Promise<Array<RedisServerDocker>> {
250+
export function spawnRedisCluster(
251+
dockersConfig: RedisClusterDockersConfig,
252+
serverArguments: Array<string>,
253+
clientConfig?: Partial<RedisClusterClientOptions>): Promise<Array<RedisServerDocker>> {
254+
238255
const runningCluster = RUNNING_CLUSTERS.get(serverArguments);
239256
if (runningCluster) {
240257
return runningCluster;
241258
}
242259

243-
const dockersPromise = spawnRedisClusterDockers(dockersConfig, serverArguments);
260+
const dockersPromise = spawnRedisClusterDockers(dockersConfig, serverArguments,clientConfig);
261+
244262
RUNNING_CLUSTERS.set(serverArguments, dockersPromise);
245263
return dockersPromise;
246264
}

packages/test-utils/lib/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ export default class TestUtils {
290290
...dockerImage,
291291
numberOfMasters: options.numberOfMasters,
292292
numberOfReplicas: options.numberOfReplicas
293-
}, options.serverArguments);
293+
}, options.serverArguments,
294+
options.clusterConfiguration?.defaults);
294295
return dockersPromise;
295296
});
296297
}

0 commit comments

Comments
 (0)