Skip to content

Commit b97bbbe

Browse files
refactor(test): improve test scenario reliability and maintainability (#3077)
* refactor(test): improve test scenario reliability and maintainability * tests: add resp3 check test (#1) * test: refactor connection handoff tests with enhanced spy utility (#2) * test: add comprehensive push notification disabled scenarios (#3) * tests: add params config tests (#4) * tests: add feature enablement tests (#5) --------- Co-authored-by: Nikolay Karadzhov <[email protected]>
1 parent 073db12 commit b97bbbe

10 files changed

+930
-414
lines changed

packages/client/lib/client/enterprise-maintenance-manager.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ interface Client {
5151
_pause: () => void;
5252
_unpause: () => void;
5353
_maintenanceUpdate: (update: MaintenanceUpdate) => void;
54-
duplicate: (options: RedisClientOptions) => Client;
54+
duplicate: () => Client;
5555
connect: () => Promise<Client>;
5656
destroy: () => void;
57+
on: (event: string, callback: (value: unknown) => void) => void;
5758
}
5859

5960
export default class EnterpriseMaintenanceManager {
@@ -211,21 +212,25 @@ export default class EnterpriseMaintenanceManager {
211212
dbgMaintenance("Creating new tmp client");
212213
let start = performance.now();
213214

214-
const tmpOptions = this.#options;
215215
// If the URL is provided, it takes precedense
216-
if(tmpOptions.url) {
217-
const u = new URL(tmpOptions.url);
216+
// the options object could just be mutated
217+
if(this.#options.url) {
218+
const u = new URL(this.#options.url);
218219
u.hostname = host;
219220
u.port = String(port);
220-
tmpOptions.url = u.toString();
221+
this.#options.url = u.toString();
221222
} else {
222-
tmpOptions.socket = {
223-
...tmpOptions.socket,
223+
this.#options.socket = {
224+
...this.#options.socket,
224225
host,
225226
port
226227
}
227228
}
228-
const tmpClient = this.#client.duplicate(tmpOptions);
229+
const tmpClient = this.#client.duplicate();
230+
tmpClient.on('error', (error: unknown) => {
231+
//We dont know how to handle tmp client errors
232+
dbgMaintenance(`[ERR]`, error)
233+
});
229234
dbgMaintenance(`Tmp client created in ${( performance.now() - start ).toFixed(2)}ms`);
230235
dbgMaintenance(
231236
`Set timeout for tmp client to ${this.#options.maintRelaxedSocketTimeout}`,
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import assert from "node:assert";
2+
import diagnostics_channel from "node:diagnostics_channel";
3+
import { DiagnosticsEvent } from "../../client/enterprise-maintenance-manager";
4+
5+
import {
6+
RedisConnectionConfig,
7+
createTestClient,
8+
getDatabaseConfig,
9+
getDatabaseConfigFromEnv,
10+
getEnvConfig,
11+
} from "./test-scenario.util";
12+
import { createClient } from "../../..";
13+
import { FaultInjectorClient } from "./fault-injector-client";
14+
import { MovingEndpointType } from "../../../dist/lib/client/enterprise-maintenance-manager";
15+
import { RedisTcpSocketOptions } from "../../client/socket";
16+
17+
describe("Client Configuration and Handshake", () => {
18+
let clientConfig: RedisConnectionConfig;
19+
let client: ReturnType<typeof createClient<any, any, any, any>>;
20+
let faultInjectorClient: FaultInjectorClient;
21+
let log: DiagnosticsEvent[] = [];
22+
23+
before(() => {
24+
const envConfig = getEnvConfig();
25+
const redisConfig = getDatabaseConfigFromEnv(
26+
envConfig.redisEndpointsConfigPath,
27+
);
28+
29+
faultInjectorClient = new FaultInjectorClient(envConfig.faultInjectorUrl);
30+
clientConfig = getDatabaseConfig(redisConfig);
31+
32+
diagnostics_channel.subscribe("redis.maintenance", (event) => {
33+
log.push(event as DiagnosticsEvent);
34+
});
35+
});
36+
37+
beforeEach(() => {
38+
log.length = 0;
39+
});
40+
41+
afterEach(async () => {
42+
if (client && client.isOpen) {
43+
await client.flushAll();
44+
client.destroy();
45+
}
46+
});
47+
48+
describe("Parameter Configuration", () => {
49+
const endpoints: MovingEndpointType[] = [
50+
"auto",
51+
// "internal-ip",
52+
// "internal-fqdn",
53+
"external-ip",
54+
"external-fqdn",
55+
"none",
56+
];
57+
58+
for (const endpointType of endpoints) {
59+
it(`clientHandshakeWithEndpointType '${endpointType}'`, async () => {
60+
try {
61+
client = await createTestClient(clientConfig, {
62+
maintMovingEndpointType: endpointType,
63+
});
64+
client.on("error", () => {});
65+
66+
//need to copy those because they will be mutated later
67+
const oldOptions = JSON.parse(JSON.stringify(client.options));
68+
assert.ok(oldOptions);
69+
70+
const { action_id } = await faultInjectorClient.migrateAndBindAction({
71+
bdbId: clientConfig.bdbId,
72+
clusterIndex: 0,
73+
});
74+
75+
await faultInjectorClient.waitForAction(action_id);
76+
77+
const movingEvent = log.find((event) => event.type === "MOVING");
78+
assert(!!movingEvent, "Didnt receive moving PN");
79+
80+
let endpoint: string | undefined;
81+
try {
82+
//@ts-ignore
83+
endpoint = movingEvent.data.push[3];
84+
} catch (err) {
85+
assert(
86+
false,
87+
`couldnt get endpoint from event ${JSON.stringify(movingEvent)}`,
88+
);
89+
}
90+
91+
assert(endpoint !== undefined, "no endpoint");
92+
93+
const newOptions = client.options;
94+
assert.ok(newOptions);
95+
96+
if (oldOptions?.url) {
97+
if (endpointType === "none") {
98+
assert.equal(
99+
newOptions!.url,
100+
oldOptions.url,
101+
"For movingEndpointTpe 'none', we expect old and new url to be the same",
102+
);
103+
} else {
104+
assert.equal(
105+
newOptions.url,
106+
endpoint,
107+
"Expected what came through the wire to be set in the new client",
108+
);
109+
assert.notEqual(
110+
newOptions!.url,
111+
oldOptions.url,
112+
`For movingEndpointTpe ${endpointType}, we expect old and new url to be different`,
113+
);
114+
}
115+
} else {
116+
const oldSocket = oldOptions.socket as RedisTcpSocketOptions;
117+
const newSocket = newOptions.socket as RedisTcpSocketOptions;
118+
assert.ok(oldSocket);
119+
assert.ok(newSocket);
120+
121+
if (endpointType === "none") {
122+
assert.equal(
123+
newSocket.host,
124+
oldSocket.host,
125+
"For movingEndpointTpe 'none', we expect old and new host to be the same",
126+
);
127+
} else {
128+
assert.equal(
129+
newSocket.host + ":" + newSocket.port,
130+
endpoint,
131+
"Expected what came through the wire to be set in the new client",
132+
);
133+
assert.notEqual(
134+
newSocket.host,
135+
oldSocket.host,
136+
`For movingEndpointTpe ${endpointType}, we expect old and new host to be different`,
137+
);
138+
}
139+
}
140+
} catch (error: any) {
141+
if (
142+
endpointType === "internal-fqdn" ||
143+
endpointType === "internal-ip"
144+
) {
145+
// errors are expected here, because we cannot connect to internal endpoints unless we are deployed in the same place as the server
146+
} else {
147+
assert(false, error);
148+
}
149+
}
150+
});
151+
}
152+
});
153+
154+
describe("Feature Enablement", () => {
155+
it("connectionHandshakeIncludesEnablingNotifications", async () => {
156+
client = await createTestClient(clientConfig, {
157+
maintPushNotifications: "enabled",
158+
});
159+
160+
const { action_id } = await faultInjectorClient.migrateAndBindAction({
161+
bdbId: clientConfig.bdbId,
162+
clusterIndex: 0,
163+
});
164+
165+
await faultInjectorClient.waitForAction(action_id);
166+
167+
let movingEvent = false;
168+
let migratingEvent = false;
169+
let migratedEvent = false;
170+
for (const event of log) {
171+
if (event.type === "MOVING") movingEvent = true;
172+
if (event.type === "MIGRATING") migratingEvent = true;
173+
if (event.type === "MIGRATED") migratedEvent = true;
174+
}
175+
assert.ok(movingEvent, "didnt receive MOVING PN");
176+
assert.ok(migratingEvent, "didnt receive MIGRATING PN");
177+
assert.ok(migratedEvent, "didnt receive MIGRATED PN");
178+
});
179+
180+
it("disabledDontReceiveNotifications", async () => {
181+
try {
182+
client = await createTestClient(clientConfig, {
183+
maintPushNotifications: "disabled",
184+
socket: {
185+
reconnectStrategy: false
186+
}
187+
});
188+
client.on('error', console.log.bind(console))
189+
190+
const { action_id } = await faultInjectorClient.migrateAndBindAction({
191+
bdbId: clientConfig.bdbId,
192+
clusterIndex: 0,
193+
});
194+
195+
await faultInjectorClient.waitForAction(action_id);
196+
197+
assert.equal(log.length, 0, "received a PN while feature is disabled");
198+
} catch (error: any) { }
199+
});
200+
});
201+
});

0 commit comments

Comments
 (0)