Skip to content

Commit 2f39426

Browse files
committed
tests: add testWithProxiedCluster() function
1 parent a265d79 commit 2f39426

File tree

3 files changed

+134
-17
lines changed

3 files changed

+134
-17
lines changed

packages/client/lib/cluster/cluster-slots.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { strict as assert } from 'node:assert';
22
import { EventEmitter } from 'node:events';
3-
import { RedisClusterOptions, RedisClusterClientOptions } from './index';
3+
import { RedisClusterClientOptions } from './index';
44
import RedisClusterSlots from './cluster-slots';
5+
import TestUtils, { GLOBAL } from '../test-utils'
56

67
describe('RedisClusterSlots', () => {
78
describe('initialization', () => {

packages/test-utils/lib/dockers.ts

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { promisify } from 'node:util';
99
import * as fs from 'node:fs';
1010
import * as os from 'node:os';
1111
import * as path from 'node:path';
12+
import assert from 'node:assert';
1213

1314
const execAsync = promisify(execFileCallback);
1415

@@ -116,6 +117,69 @@ options: RedisServerDockerOptions, serverArguments: Array<string>): Promise<Redi
116117
}
117118
const RUNNING_SERVERS = new Map<Array<string>, ReturnType<typeof spawnRedisServerDocker>>();
118119

120+
export interface ProxiedRedisServerDocker {
121+
ports: number[],
122+
dockerId: string
123+
}
124+
125+
export interface ProxiedRedisServerConfig {
126+
nOfProxies: number,
127+
defaultInterceptors: ('cluster'|'hitless'|'logger')[]
128+
}
129+
130+
const RUNNING_PROXIED_SERVERS = new Map<string, Promise<ProxiedRedisServerDocker>>();
131+
132+
export async function spawnProxiedRedisServer(config: ProxiedRedisServerConfig): Promise<ProxiedRedisServerDocker> {
133+
const key = JSON.stringify(config);
134+
const runningServer = RUNNING_PROXIED_SERVERS.get(key);
135+
if (runningServer) {
136+
return runningServer;
137+
}
138+
139+
const server = spawnProxiedRedisServerDocker(config);
140+
RUNNING_PROXIED_SERVERS.set(key, server);
141+
return server;
142+
}
143+
144+
export async function spawnProxiedRedisServerDocker(
145+
config: ProxiedRedisServerConfig,
146+
): Promise<ProxiedRedisServerDocker> {
147+
148+
assert(config.nOfProxies > 0, 'At least one proxy should be started');
149+
const ports: number[] = [];
150+
for (let i = 0; i < config.nOfProxies; i++) {
151+
ports.push((await portIterator.next()).value);
152+
}
153+
154+
const dockerArgs =[
155+
"run",
156+
"-d",
157+
"--network", "host",
158+
"-e", `LISTEN_PORT=${ports.join(',')}`,
159+
"-e", "TIEOUT=0",
160+
"-e", `DEFAULT_INTERCEPTORS=${config.defaultInterceptors.join(',')}`,
161+
"-e", "ENABLE_LOGGING=true",
162+
"cae-resp-proxy-standalone"
163+
]
164+
165+
console.log(`[Docker] Spawning Proxy container`, dockerArgs.join(' '));
166+
167+
const { stdout, stderr } = await execAsync("docker", dockerArgs);
168+
169+
if (!stdout) {
170+
throw new Error(`docker run error - ${stderr}`);
171+
}
172+
173+
while (await isPortAvailable(ports[0])) {
174+
await setTimeout(50);
175+
}
176+
177+
return {
178+
ports,
179+
dockerId: stdout.trim(),
180+
};
181+
}
182+
119183
export function spawnRedisServer(dockerConfig: RedisServerDockerOptions, serverArguments: Array<string>): Promise<RedisServerDocker> {
120184
const runningServer = RUNNING_SERVERS.get(serverArguments);
121185
if (runningServer) {
@@ -319,7 +383,7 @@ export async function spawnRedisSentinel(
319383
if (runningNodes) {
320384
return runningNodes;
321385
}
322-
386+
323387
const passIndex = serverArguments.indexOf('--requirepass')+1;
324388
let password: string | undefined = undefined;
325389
if (passIndex != 0) {
@@ -329,7 +393,7 @@ export async function spawnRedisSentinel(
329393
const master = await spawnRedisServerDocker(dockerConfigs, serverArguments);
330394
const redisNodes: Array<RedisServerDocker> = [master];
331395
const replicaPromises: Array<Promise<RedisServerDocker>> = [];
332-
396+
333397
const replicasCount = 2;
334398
for (let i = 0; i < replicasCount; i++) {
335399
replicaPromises.push((async () => {
@@ -344,37 +408,37 @@ export async function spawnRedisSentinel(
344408
await client.connect();
345409
await client.replicaOf("127.0.0.1", master.port);
346410
await client.close();
347-
411+
348412
return replica;
349413
})());
350414
}
351-
415+
352416
const replicas = await Promise.all(replicaPromises);
353417
redisNodes.push(...replicas);
354418
RUNNING_NODES.set(serverArguments, redisNodes);
355419

356420
const sentinelPromises: Array<Promise<RedisServerDocker>> = [];
357421
const sentinelCount = 3;
358-
422+
359423
const appPrefix = 'sentinel-config-dir';
360424
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), appPrefix));
361425

362426
for (let i = 0; i < sentinelCount; i++) {
363427
sentinelPromises.push(
364428
spawnSentinelNode(
365-
dockerConfigs,
366-
serverArguments,
429+
dockerConfigs,
430+
serverArguments,
367431
master.port,
368432
"mymaster",
369433
path.join(tmpDir, i.toString()),
370434
password,
371435
),
372436
)
373437
}
374-
438+
375439
const sentinelNodes = await Promise.all(sentinelPromises);
376440
RUNNING_SENTINELS.set(serverArguments, sentinelNodes);
377-
441+
378442
if (tmpDir) {
379443
fs.rmSync(tmpDir, { recursive: true });
380444
}
@@ -396,14 +460,17 @@ after(() => {
396460
...Array.from(RUNNING_SENTINELS.values()).map(dockersPromise =>
397461
Promise.all(dockersPromise.map(({ dockerId }) => dockerRemove(dockerId)))
398462
),
463+
...Array.from(RUNNING_PROXIED_SERVERS.values()).map(async dockerPromise =>
464+
dockerRemove((await dockerPromise).dockerId)
465+
)
399466
]);
400467
});
401468

402469

403470
export async function spawnSentinelNode(
404471
dockerConfigs: RedisServerDockerOptions,
405472
serverArguments: Array<string>,
406-
masterPort: number,
473+
masterPort: number,
407474
sentinelName: string,
408475
tmpDir: string,
409476
password?: string,
@@ -429,12 +496,12 @@ sentinel failover-timeout ${sentinelName} 1000
429496

430497
return await spawnRedisServerDocker(
431498
{
432-
image: dockerConfigs.image,
433-
version: dockerConfigs.version,
499+
image: dockerConfigs.image,
500+
version: dockerConfigs.version,
434501
mode: "sentinel",
435-
mounts: [`${dir}/redis.conf:/redis/config/node-sentinel-1/redis.conf`],
502+
mounts: [`${dir}/redis.conf:/redis/config/node-sentinel-1/redis.conf`],
436503
port: port,
437-
},
504+
},
438505
serverArguments,
439506
);
440-
}
507+
}

packages/test-utils/lib/index.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
RedisClusterType
2020
} from '@redis/client/index';
2121
import { RedisNode } from '@redis/client/lib/sentinel/types'
22-
import { spawnRedisServer, spawnRedisCluster, spawnRedisSentinel, RedisServerDockerOptions, RedisServerDocker, spawnSentinelNode, spawnRedisServerDocker } from './dockers';
22+
import { spawnRedisServer, spawnRedisCluster, spawnRedisSentinel, RedisServerDockerOptions, RedisServerDocker, spawnSentinelNode, spawnRedisServerDocker, spawnProxiedRedisServer } from './dockers';
2323
import yargs from 'yargs';
2424
import { hideBin } from 'yargs/helpers';
2525

@@ -28,6 +28,7 @@ import * as os from 'node:os';
2828
import * as path from 'node:path';
2929
import { RedisProxy, getFreePortNumber } from './proxy/redis-proxy';
3030

31+
3132
interface TestUtilsConfig {
3233
/**
3334
* The name of the Docker image to use for spawning Redis test instances.
@@ -298,6 +299,54 @@ export default class TestUtils {
298299
}
299300
});
300301
}
302+
303+
testWithProxiedCluster<
304+
M extends RedisModules = {},
305+
F extends RedisFunctions = {},
306+
S extends RedisScripts = {},
307+
RESP extends RespVersions = 2,
308+
TYPE_MAPPING extends TypeMapping = {}
309+
>(
310+
title: string,
311+
fn: (proxiedClusterClient: RedisClusterType<M, F, S, RESP, TYPE_MAPPING>, proxyUrl: string) => unknown,
312+
options: Omit<ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING>, 'numberOfReplicas' | 'minimumDockerVersion' | 'serverArguments'>
313+
) {
314+
let spawnPromise: ReturnType<typeof spawnProxiedRedisServer>;
315+
before(function () {
316+
this.timeout(30000);
317+
spawnPromise = spawnProxiedRedisServer({ nOfProxies: options.numberOfMasters ?? 3, defaultInterceptors: ['cluster', 'hitless'] });
318+
});
319+
320+
it(title, async function () {
321+
if (!spawnPromise) return this.skip();
322+
const { ports } = await spawnPromise;
323+
324+
const cluster = createCluster({
325+
rootNodes: ports.map(port => ({
326+
socket: {
327+
port
328+
}
329+
})),
330+
minimizeConnections: options.clusterConfiguration?.minimizeConnections ?? true,
331+
...options.clusterConfiguration
332+
});
333+
334+
if(options.disableClusterSetup) {
335+
return fn(cluster, 'hh');
336+
}
337+
338+
await cluster.connect();
339+
340+
try {
341+
await TestUtils.#clusterFlushAll(cluster);
342+
await fn(cluster, 'hi');
343+
} finally {
344+
await TestUtils.#clusterFlushAll(cluster);
345+
cluster.destroy();
346+
}
347+
});
348+
}
349+
301350
testWithProxiedClient(
302351
title: string,
303352
fn: (proxiedClient: RedisClientType<any, any, any, any, any>, proxy: RedisProxy) => unknown,

0 commit comments

Comments
 (0)