Skip to content

Commit 0fb62bd

Browse files
authored
test: refactor connection handoff tests with enhanced spy utility (#2)
1 parent 953fb9a commit 0fb62bd

File tree

1 file changed

+128
-41
lines changed

1 file changed

+128
-41
lines changed

packages/client/lib/tests/test-scenario/connection-handoff.e2e.ts

Lines changed: 128 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,56 @@ import {
66
getEnvConfig,
77
RedisConnectionConfig,
88
} from "./test-scenario.util";
9-
import { createClient } from "../../..";
9+
import { createClient, RedisClientOptions } from "../../..";
1010
import { before } from "mocha";
11-
import { spy } from "sinon";
11+
import Sinon, { SinonSpy, spy, stub } from "sinon";
1212
import assert from "node:assert";
13-
import net from "node:net";
13+
14+
/**
15+
* Creates a spy on a duplicated client method
16+
* @param client - The Redis client instance
17+
* @param funcName - The name of the method to spy on
18+
* @returns Object containing the promise that resolves with the spy and restore function
19+
*/
20+
const spyOnTemporaryClientInstanceMethod = (
21+
client: ReturnType<typeof createClient<any, any, any, any>>,
22+
methodName: string
23+
) => {
24+
const { promise, resolve } = (
25+
Promise as typeof Promise & {
26+
withResolvers: () => {
27+
promise: Promise<{ spy: SinonSpy<any[], any>; restore: () => void }>;
28+
resolve: (value: any) => void;
29+
};
30+
}
31+
).withResolvers();
32+
33+
const originalDuplicate = client.duplicate.bind(client);
34+
35+
const duplicateStub: Sinon.SinonStub<any[], any> = stub(
36+
// Temporary clients (in the context of hitless upgrade)
37+
// are created by calling the duplicate method on the client.
38+
Object.getPrototypeOf(client),
39+
"duplicate"
40+
).callsFake((opts) => {
41+
const tmpClient = originalDuplicate(opts);
42+
resolve({
43+
spy: spy(tmpClient, methodName),
44+
restore: duplicateStub.restore,
45+
});
46+
47+
return tmpClient;
48+
});
49+
50+
return {
51+
getSpy: () => promise,
52+
};
53+
};
1454

1555
describe("Connection Handoff", () => {
1656
let clientConfig: RedisConnectionConfig;
1757
let client: ReturnType<typeof createClient<any, any, any, any>>;
1858
let faultInjectorClient: FaultInjectorClient;
19-
let connectSpy = spy(net, "createConnection");
2059

2160
before(() => {
2261
const envConfig = getEnvConfig();
@@ -28,62 +67,110 @@ describe("Connection Handoff", () => {
2867
clientConfig = getDatabaseConfig(redisConfig);
2968
});
3069

31-
beforeEach(async () => {
32-
connectSpy.resetHistory();
33-
34-
client = await createTestClient(clientConfig);
35-
36-
await client.flushAll();
37-
});
38-
39-
afterEach(() => {
70+
afterEach(async () => {
4071
if (client && client.isOpen) {
72+
await client.flushAll();
4173
client.destroy();
4274
}
4375
});
4476

45-
describe("New Connection Establishment", () => {
46-
it("should establish new connection", async () => {
47-
assert.equal(connectSpy.callCount, 1);
48-
49-
const { action_id: lowTimeoutBindAndMigrateActionId } =
50-
await faultInjectorClient.migrateAndBindAction({
51-
bdbId: clientConfig.bdbId,
52-
clusterIndex: 0,
53-
});
54-
55-
await faultInjectorClient.waitForAction(lowTimeoutBindAndMigrateActionId);
56-
57-
assert.equal(connectSpy.callCount, 2);
58-
});
77+
describe("New Connection Establishment & Traffic Resumption", () => {
78+
const cases: Array<{
79+
name: string;
80+
clientOptions: Partial<RedisClientOptions>;
81+
}> = [
82+
{
83+
name: "default options",
84+
clientOptions: {},
85+
},
86+
{
87+
name: "external-ip",
88+
clientOptions: {
89+
maintMovingEndpointType: "external-ip",
90+
},
91+
},
92+
{
93+
name: "external-fqdn",
94+
clientOptions: {
95+
maintMovingEndpointType: "external-fqdn",
96+
},
97+
},
98+
{
99+
name: "auto",
100+
clientOptions: {
101+
maintMovingEndpointType: "auto",
102+
},
103+
},
104+
{
105+
name: "none",
106+
clientOptions: {
107+
maintMovingEndpointType: "none",
108+
},
109+
},
110+
];
111+
112+
for (const { name, clientOptions } of cases) {
113+
it.only(`should establish new connection and resume traffic afterwards - ${name}`, async () => {
114+
client = await createTestClient(clientConfig, clientOptions);
115+
116+
const spyObject = spyOnTemporaryClientInstanceMethod(client, "connect");
117+
118+
// PART 1 Establish initial connection
119+
const { action_id: lowTimeoutBindAndMigrateActionId } =
120+
await faultInjectorClient.migrateAndBindAction({
121+
bdbId: clientConfig.bdbId,
122+
clusterIndex: 0,
123+
});
124+
125+
await faultInjectorClient.waitForAction(
126+
lowTimeoutBindAndMigrateActionId
127+
);
128+
129+
const spyResult = await spyObject.getSpy();
130+
131+
assert.strictEqual(spyResult.spy.callCount, 1);
132+
133+
// PART 2 Verify traffic resumption
134+
const currentTime = Date.now().toString();
135+
await client.set("key", currentTime);
136+
const result = await client.get("key");
137+
138+
assert.strictEqual(result, currentTime);
139+
140+
spyResult.restore();
141+
});
142+
}
59143
});
60144

61145
describe("TLS Connection Handoff", () => {
62-
it("TODO receiveMessagesWithTLSEnabledTest", async () => {
146+
it.skip("TODO receiveMessagesWithTLSEnabledTest", async () => {
63147
//
64148
});
65-
it("TODO connectionHandoffWithStaticInternalNameTest", async () => {
149+
it.skip("TODO connectionHandoffWithStaticInternalNameTest", async () => {
66150
//
67151
});
68-
it("TODO connectionHandoffWithStaticExternalNameTest", async () => {
152+
it.skip("TODO connectionHandoffWithStaticExternalNameTest", async () => {
69153
//
70154
});
71155
});
72156

73-
describe("Traffic Resumption", () => {
74-
it("Traffic resumed after handoff", async () => {
75-
const { action_id } = await faultInjectorClient.migrateAndBindAction({
76-
bdbId: clientConfig.bdbId,
77-
clusterIndex: 0,
78-
});
157+
describe("Connection Cleanup", () => {
158+
it("should shut down old connection", async () => {
159+
const spyObject = spyOnTemporaryClientInstanceMethod(client, "destroy");
160+
161+
const { action_id: lowTimeoutBindAndMigrateActionId } =
162+
await faultInjectorClient.migrateAndBindAction({
163+
bdbId: clientConfig.bdbId,
164+
clusterIndex: 0,
165+
});
166+
167+
await faultInjectorClient.waitForAction(lowTimeoutBindAndMigrateActionId);
79168

80-
await faultInjectorClient.waitForAction(action_id);
169+
const spyResult = await spyObject.getSpy();
81170

82-
const currentTime = Date.now().toString();
83-
await client.set("key", currentTime);
84-
const result = await client.get("key");
171+
assert.equal(spyResult.spy.callCount, 1);
85172

86-
assert.strictEqual(result, currentTime);
173+
spyResult.restore();
87174
});
88175
});
89176
});

0 commit comments

Comments
 (0)