Skip to content

Commit 0283d17

Browse files
nkaradzhovclaude
andcommitted
test(sentinel): cover pubsub mapping on master change
Stubs PubSubProxy.changeNode and drives transform() with a synthetic analyzed object to assert the mapped address is used on failover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b7a34da commit 0283d17

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

packages/client/lib/sentinel/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ export default class RedisSentinel<
621621
}
622622
}
623623

624-
class RedisSentinelInternal<
624+
export class RedisSentinelInternal<
625625
M extends RedisModules,
626626
F extends RedisFunctions,
627627
S extends RedisScripts,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { strict as assert } from 'node:assert';
2+
import { describe, it, beforeEach, afterEach } from 'mocha';
3+
import sinon from 'sinon';
4+
import RedisClient from '../client';
5+
import { PubSubProxy } from './pub-sub-proxy';
6+
import { RedisSentinelInternal } from './index';
7+
8+
describe('pubsub master change applies nodeAddressMap', () => {
9+
const RAW_HOST = '10.0.0.99';
10+
const RAW_PORT = 6390;
11+
const MAPPED_HOST = 'external.example.com';
12+
const MAPPED_PORT = 16390;
13+
14+
let changeNodeStub: sinon.SinonStub;
15+
let clientConnectStub: sinon.SinonStub;
16+
let internal: RedisSentinelInternal<{}, {}, {}, 2, {}>;
17+
18+
beforeEach(() => {
19+
changeNodeStub = sinon.stub(PubSubProxy.prototype, 'changeNode').resolves();
20+
clientConnectStub = sinon.stub(RedisClient.prototype, 'connect').resolves(undefined as any);
21+
22+
internal = new RedisSentinelInternal<{}, {}, {}, 2, {}>({
23+
name: 'mymaster',
24+
sentinelRootNodes: [{ host: '127.0.0.1', port: 26379 }],
25+
nodeAddressMap: {
26+
[`${RAW_HOST}:${RAW_PORT}`]: { host: MAPPED_HOST, port: MAPPED_PORT }
27+
}
28+
});
29+
internal.on('error', () => { });
30+
});
31+
32+
afterEach(() => {
33+
changeNodeStub.restore();
34+
clientConnectStub.restore();
35+
});
36+
37+
it('passes the mapped address (not the raw sentinel-reported one) to PubSubProxy.changeNode', async () => {
38+
await internal.transform({
39+
sentinelList: [],
40+
epoch: 0,
41+
sentinelToOpen: undefined,
42+
masterToOpen: { host: RAW_HOST, port: RAW_PORT },
43+
replicasToClose: [],
44+
replicasToOpen: new Map()
45+
});
46+
47+
assert.equal(changeNodeStub.callCount, 1, 'PubSubProxy.changeNode should be called exactly once');
48+
assert.deepEqual(
49+
changeNodeStub.firstCall.args[0],
50+
{ host: MAPPED_HOST, port: MAPPED_PORT },
51+
'pubsub proxy must reconnect to the mapped address after a sentinel failover'
52+
);
53+
});
54+
});

0 commit comments

Comments
 (0)