Skip to content

Commit bf6e89f

Browse files
test(cluster): fix and add cluster tests in CI (#2017)
* enabled cluster tests * test: add integration test for ssubscribe auto-resubscription behavior * test(cluster): fix and add cluster tests in CI --------- Co-authored-by: Hristo Temelski <hristo.temelski@redis.com>
1 parent b15d7ab commit bf6e89f

File tree

7 files changed

+106
-34
lines changed

7 files changed

+106
-34
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ jobs:
2828
with:
2929
redis-version: latest
3030

31+
- run: npm run cluster:setup
32+
3133
- run: npm install
3234
- run: npm run lint
3335
- run: npm run build
3436
- run: npm run test:tsd
3537
- run: npm run test:cov || npm run test:cov || npm run test:cov
38+
- run: npm run test:cluster

.github/workflows/test_with_cov.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ jobs:
2222
with:
2323
redis-version: latest
2424

25+
- run: npm run cluster:setup
26+
2527
- run: npm install
2628
- run: npm run lint
2729
- run: npm run build
2830
- run: npm run test:tsd
2931
- run: npm run test:cov || npm run test:cov || npm run test:cov
32+
- run: npm run test:cluster
3033
- name: Coveralls
3134
if: matrix.node == '18.x'
3235
uses: coverallsapp/github-action@master

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
"built/"
99
],
1010
"scripts": {
11+
"cluster:setup": "docker compose -f test/cluster/docker-compose.cluster.yml up -d --wait",
12+
"cluster:teardown": "docker compose -f test/cluster/docker-compose.cluster.yml down --volumes --remove-orphans",
1113
"test:tsd": "npm run build && tsd",
1214
"test:js": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/helpers/*.ts\" \"test/unit/**/*.ts\" \"test/functional/**/*.ts\"",
1315
"test:cov": "nyc npm run test:js",
14-
"test:js:cluster": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/cluster/**/*.ts\"",
16+
"test:cluster": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/cluster/**/*.ts\"",
1517
"test": "npm run test:js && npm run test:tsd",
1618
"lint": "eslint --ext .js,.ts ./lib",
1719
"docs": "npx typedoc --logLevel Error --excludeExternals --excludeProtected --excludePrivate --readme none lib/index.ts",

test/cluster/basic.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { expect } from "chai";
22
import Redis, { Cluster } from "../../lib";
33

4-
5-
const masters = [30000, 30001, 30002];
6-
const replicas = [30003, 30004, 30005];
4+
const masters = [3000, 3001, 3002];
5+
const replicas = [3003, 3004, 3005];
76

87
async function cleanup() {
98
for (const port of masters) {
109
const redis = new Redis(port);
1110
await redis.flushall();
1211
await redis.script("FLUSH");
12+
await redis.quit();
1313
}
1414
// Wait for replication
1515
await new Promise((resolve) => setTimeout(resolve, 500));

test/cluster/cluster_subscriber_group.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {expect} from "chai";
22
import Redis, {Cluster} from "../../lib";
3-
import redis from "../../lib";
43

54
const host = "127.0.0.1";
6-
const masters = [30000, 30001, 30002];
5+
const masters = [3000, 3001, 3002];
76
const port: number = masters[0]
87

98
/**
@@ -22,33 +21,44 @@ describe("cluster:ClusterSubscriberGroup", () => {
2221

2322
const cluster: Cluster = new Cluster([{host: host, port: port}], {shardedSubscribers: true});
2423

25-
//Subscribe to the three channels
26-
cluster.ssubscribe("channel:1:{1}", "channel:2:{1}", "channel:3:{1}" ).then( ( count: number ) => {
27-
console.log("Subscribed to 3 channels.");
28-
expect(count).to.equal(3);
29-
});
30-
31-
//Publish a message to one of the channels
32-
cluster.spublish("channel:2:{1}", "This is a test message to channel 2.").then((value: number) => {
33-
console.log("Published a message to channel:2:{1} and expect one subscriber.");
34-
expect(value).to.be.eql(1);
35-
});
24+
// Subscribe to the three channels
25+
const subscribeCount = await cluster.ssubscribe(
26+
"channel:1:{1}",
27+
"channel:2:{1}",
28+
"channel:3:{1}"
29+
);
30+
expect(subscribeCount, "Should be subscribed to 3 channels.").to.equal(3);
31+
32+
// Publish a message to one of the channels
33+
const publishValue = await cluster.spublish(
34+
"channel:2:{1}",
35+
"This is a test message to channel 2."
36+
);
37+
expect(
38+
publishValue,
39+
"Should have published a message to channel:2:{1} and expect one subscriber."
40+
).to.be.eql(1);
3641

3742
await sleep(500);
3843

39-
//Unsubscribe from one of the channels
40-
cluster.sunsubscribe("channel:2:{1}").then( ( count: number ) => {
41-
console.log("Unsubscribed from channel:2:{1}.");
42-
expect(count).to.equal(2);
43-
});
44+
// Unsubscribe from one of the channels
45+
const unsubscribeCount = await cluster.sunsubscribe("channel:2:{1}");
46+
expect(unsubscribeCount, "Should be subscribed from 2 channels.").to.equal(
47+
2
48+
);
4449

4550
await sleep(500);
4651

47-
//Publish a message to the channel from which we unsubscribed
48-
cluster.spublish("channel:2:{1}", "This is a test message to channel 2.").then((value: number) => {
49-
console.log("Published a second message to channel:2:{1} and expect to have nobody listening.");
50-
expect(value).to.be.eql(0);
51-
});
52+
// Publish a message to the channel from which we unsubscribed
53+
const value = await cluster.spublish(
54+
"channel:2:{1}",
55+
"This is a test message to channel 2."
56+
);
57+
58+
expect(
59+
value,
60+
"Published a second message to channel:2:{1} and expect to have nobody listening."
61+
).to.be.eql(0);
5262

5363
await sleep(1000);
5464
await cluster.disconnect();
@@ -167,22 +177,22 @@ describe("cluster:ClusterSubscriberGroup", () => {
167177
const channel = "channel:test:3";
168178

169179
//Used as control connections for orchestrating the slot migration
170-
const source: Redis = new Redis({host: host, port: 30000});
171-
const target: Redis = new Redis({host: host, port: 30001});
180+
const source: Redis = new Redis({host: host, port: 3000});
181+
const target: Redis = new Redis({host: host, port: 3001});
172182

173183
//Initialize the publisher cluster connections and verify that the slot is on node 1
174184
const publisher: Cluster = new Cluster([{host: host, port: port}]);
175185

176186
publisher.on("ready", () => {
177-
expect(publisher.slots[slot][0]).eql("127.0.0.1:30000");
187+
expect(publisher.slots[slot][0]).eql("127.0.0.1:3000");
178188
});
179189

180190

181191
//Initialize the subscriber cluster connections and verify that the slot is on node 1
182192
const subscriber: Cluster = new Cluster([{host: host, port: port}], {shardedSubscribers: true});
183193

184194
subscriber.on("ready", () => {
185-
expect(subscriber.slots[slot][0]).eql("127.0.0.1:30000")
195+
expect(subscriber.slots[slot][0]).eql("127.0.0.1:3000")
186196
});
187197

188198
//The subscription callback. We should receive both messages
@@ -227,14 +237,20 @@ describe("cluster:ClusterSubscriberGroup", () => {
227237
//TODO: What if there is no traffic on the cluster connection?
228238
status = await subscriber.set("match_slot{" + channel + "}", "channel 3");
229239
expect(status).to.eql("OK");
230-
expect(subscriber.slots[slot][0]).eql("127.0.0.1:30001");
240+
expect(subscriber.slots[slot][0]).eql("127.0.0.1:3001");
231241

232242
//Wait a bit to let the subscriber resubscribe to previous channels
233243
await sleep(1000);
234244

235245
const numSubscribers = await publisher.spublish(channel, "This is a test message #2 to slot "
236246
+ slot + " on channel " + channel + ".");
237-
expect(publisher.slots[slot][0]).eql("127.0.0.1:30001");
247+
expect(publisher.slots[slot][0]).eql("127.0.0.1:3001");
238248
expect(numSubscribers).to.eql(1);
249+
250+
await sleep(1000);
251+
subscriber.disconnect();
252+
publisher.disconnect();
253+
source.disconnect();
254+
target.disconnect();
239255
});
240-
});
256+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
services:
2+
redis-cluster:
3+
image: redislabs/client-libs-test:${REDIS_VERSION:-8.2.1-pre}
4+
environment:
5+
REDIS_CLUSTER: "yes"
6+
NODES: "6"
7+
REPLICAS: "1"
8+
ports:
9+
- 3000:3000
10+
- 3001:3001
11+
- 3002:3002
12+
- 3003:3003
13+
- 3004:3004
14+
- 3005:3005
15+
healthcheck:
16+
test: ["CMD-SHELL", "redis-cli -p 3000 cluster info | grep -q 'cluster_state:ok'"]
17+
interval: 1s
18+
timeout: 10s
19+
retries: 5

test/functional/spub_ssub.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as sinon from "sinon";
12
import Redis from "../../lib/Redis";
23
import { expect } from "chai";
34

@@ -143,4 +144,32 @@ describe("spub/ssub", function () {
143144
redis.disconnect(true);
144145
});
145146
});
147+
148+
// This ensures we don't get CROSSSLOT exceptions
149+
it("should call ssubscribe individually for each channel during auto-resubscription", async () => {
150+
const subscriber = new Redis({ autoResubscribe: true });
151+
152+
await subscriber.ping();
153+
154+
subscriber.ssubscribe("shard1");
155+
subscriber.ssubscribe("shard2");
156+
subscriber.ssubscribe("shard3");
157+
158+
const stub = sinon.stub(Redis.prototype, "ssubscribe");
159+
160+
subscriber.disconnect({ reconnect: true });
161+
162+
await new Promise((resolve) => {
163+
subscriber.once("ready", resolve);
164+
});
165+
166+
await subscriber.ping();
167+
168+
expect(stub.getCall(0).args).to.deep.equal(["shard1"]);
169+
expect(stub.getCall(1).args).to.deep.equal(["shard2"]);
170+
expect(stub.getCall(2).args).to.deep.equal(["shard3"]);
171+
172+
stub.restore();
173+
subscriber.disconnect();
174+
});
146175
});

0 commit comments

Comments
 (0)