@@ -6,17 +6,56 @@ import {
6
6
getEnvConfig ,
7
7
RedisConnectionConfig ,
8
8
} from "./test-scenario.util" ;
9
- import { createClient } from "../../.." ;
9
+ import { createClient , RedisClientOptions } from "../../.." ;
10
10
import { before } from "mocha" ;
11
- import { spy } from "sinon" ;
11
+ import Sinon , { SinonSpy , spy , stub } from "sinon" ;
12
12
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
+ } ;
14
54
15
55
describe ( "Connection Handoff" , ( ) => {
16
56
let clientConfig : RedisConnectionConfig ;
17
57
let client : ReturnType < typeof createClient < any , any , any , any > > ;
18
58
let faultInjectorClient : FaultInjectorClient ;
19
- let connectSpy = spy ( net , "createConnection" ) ;
20
59
21
60
before ( ( ) => {
22
61
const envConfig = getEnvConfig ( ) ;
@@ -28,62 +67,110 @@ describe("Connection Handoff", () => {
28
67
clientConfig = getDatabaseConfig ( redisConfig ) ;
29
68
} ) ;
30
69
31
- beforeEach ( async ( ) => {
32
- connectSpy . resetHistory ( ) ;
33
-
34
- client = await createTestClient ( clientConfig ) ;
35
-
36
- await client . flushAll ( ) ;
37
- } ) ;
38
-
39
- afterEach ( ( ) => {
70
+ afterEach ( async ( ) => {
40
71
if ( client && client . isOpen ) {
72
+ await client . flushAll ( ) ;
41
73
client . destroy ( ) ;
42
74
}
43
75
} ) ;
44
76
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
+ }
59
143
} ) ;
60
144
61
145
describe ( "TLS Connection Handoff" , ( ) => {
62
- it ( "TODO receiveMessagesWithTLSEnabledTest" , async ( ) => {
146
+ it . skip ( "TODO receiveMessagesWithTLSEnabledTest" , async ( ) => {
63
147
//
64
148
} ) ;
65
- it ( "TODO connectionHandoffWithStaticInternalNameTest" , async ( ) => {
149
+ it . skip ( "TODO connectionHandoffWithStaticInternalNameTest" , async ( ) => {
66
150
//
67
151
} ) ;
68
- it ( "TODO connectionHandoffWithStaticExternalNameTest" , async ( ) => {
152
+ it . skip ( "TODO connectionHandoffWithStaticExternalNameTest" , async ( ) => {
69
153
//
70
154
} ) ;
71
155
} ) ;
72
156
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 ) ;
79
168
80
- await faultInjectorClient . waitForAction ( action_id ) ;
169
+ const spyResult = await spyObject . getSpy ( ) ;
81
170
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 ) ;
85
172
86
- assert . strictEqual ( result , currentTime ) ;
173
+ spyResult . restore ( ) ;
87
174
} ) ;
88
175
} ) ;
89
176
} ) ;
0 commit comments