From 368b4967adc24c3be1a0c2c786ff67f9f97b5d45 Mon Sep 17 00:00:00 2001 From: borislav ivanov Date: Tue, 16 Dec 2025 10:23:44 +0200 Subject: [PATCH] feat(test-utils): add testAllAuto method for automatic cluster test generation Created a new `testAllAuto()` helper method in the test-utils package that automatically runs tests against both single-node and cluster instances with auto-generated cluster configuration from client configuration. Previously, tests only ran against single-node clients using `testWithClient()`. To test both scenarios, developers had to either manually call `testWithCluster()` separately with hand-crafted cluster config, or use `testAll()` with both client and cluster configurations defined. The new `testAllAuto()` method converts client configuration to cluster configuration by separating cluster-level properties (`modules`, `functions`, `scripts`) from client-level properties (`password`, `socket`, etc.), placing them at `clusterConfiguration` level and `clusterConfiguration.defaults` respectively. Example usage: Before: ``` testUtils.testWithClient('client.ft.aggregate', async client => {...}, GLOBAL.SERVERS.OPEN) ``` After: ``` testUtils.testAllAuto('client.ft.aggregate', async client => {...}, GLOBAL.SERVERS.OPEN) ``` Changes: - Added `testAllAuto()` method in packages/test-utils/lib/index.ts - Updated AGGREGATE.spec.ts to use the new method as example --- .../search/lib/commands/AGGREGATE.spec.ts | 6 +- packages/test-utils/lib/index.ts | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/packages/search/lib/commands/AGGREGATE.spec.ts b/packages/search/lib/commands/AGGREGATE.spec.ts index 420911c5600..3c7a24ad4ab 100644 --- a/packages/search/lib/commands/AGGREGATE.spec.ts +++ b/packages/search/lib/commands/AGGREGATE.spec.ts @@ -4,7 +4,7 @@ import AGGREGATE from './AGGREGATE'; import { parseArgs } from '@redis/client/lib/commands/generic-transformers'; import { DEFAULT_DIALECT } from '../dialect/default'; -describe('AGGREGATE', () => { +describe('AGGREGATE', () => { describe('transformArguments', () => { it('without options', () => { assert.deepEqual( @@ -27,7 +27,7 @@ describe('AGGREGATE', () => { parseArgs(AGGREGATE, 'index', '*', { ADDSCORES: true }), ['FT.AGGREGATE', 'index', '*', 'ADDSCORES', 'DIALECT', DEFAULT_DIALECT] ); - }); + }); describe('with LOAD', () => { describe('single', () => { @@ -476,7 +476,7 @@ describe('AGGREGATE', () => { }); }); - testUtils.testWithClient('client.ft.aggregate', async client => { + testUtils.testAllAuto('client.ft.aggregate', async client => { await Promise.all([ client.ft.create('index', { field: 'NUMERIC' diff --git a/packages/test-utils/lib/index.ts b/packages/test-utils/lib/index.ts index 1a9d1c9845a..7cb16bfa49c 100644 --- a/packages/test-utils/lib/index.ts +++ b/packages/test-utils/lib/index.ts @@ -590,6 +590,61 @@ export default class TestUtils { this.testWithCluster(`cluster.${title}`, fn, options.cluster); } + /** + * Tests with both a regular client and a cluster client, automatically generating the cluster + * configuration from the client configuration. + * + * Modules, functions, and scripts are placed at the cluster level, while other client options + * (password, socket, etc.) are placed under cluster defaults. + * + * @param title - The test title + * @param fn - The test function + * @param clientOptions - Client test options (cluster config will be auto-generated) + */ + testAllAuto< + M extends RedisModules = {}, + F extends RedisFunctions = {}, + S extends RedisScripts = {}, + RESP extends RespVersions = 2, + TYPE_MAPPING extends TypeMapping = {} + // POLICIES extends CommandPolicies = {} + >( + title: string, + fn: (client: RedisClientType | RedisClusterType) => unknown, + clientOptions: ClientTestOptions + ) { + this.testWithClient(`client.${title}`, fn, clientOptions); + + // Auto-generate cluster configuration from client options + const clusterOptions: ClusterTestOptions = { + serverArguments: clientOptions.serverArguments, + minimumDockerVersion: clientOptions.minimumDockerVersion, + skipTest: clientOptions.skipTest, + clusterConfiguration: {} + }; + + if (clientOptions.clientOptions) { + const { modules, functions, scripts, ...clientDefaults } = clientOptions.clientOptions as any; + + if (modules) { + clusterOptions.clusterConfiguration!.modules = modules; + } + if (functions) { + clusterOptions.clusterConfiguration!.functions = functions; + } + if (scripts) { + clusterOptions.clusterConfiguration!.scripts = scripts; + } + + // Other client options go under defaults + if (Object.keys(clientDefaults).length > 0) { + clusterOptions.clusterConfiguration!.defaults = clientDefaults; + } + } + + this.testWithCluster(`cluster.${title}`, fn, clusterOptions); + } + spawnRedisServer< M extends RedisModules = {},