|
1 |
| -import { Readable } from 'node:stream'; |
| 1 | +import { Readable, Writable } from 'node:stream'; |
2 | 2 | import { setImmediate as setImmediatePromise } from 'node:timers/promises';
|
3 | 3 | import { expect } from 'chai';
|
4 | 4 | import WebSocket from 'isomorphic-ws';
|
5 |
| -import { WritableStreamBuffer } from 'stream-buffers'; |
| 5 | +import { ReadableStreamBuffer, WritableStreamBuffer } from 'stream-buffers'; |
6 | 6 |
|
7 | 7 | import { V1Status } from './api';
|
8 | 8 | import { KubeConfig } from './config';
|
@@ -117,7 +117,7 @@ describe('WebSocket', () => {
|
117 | 117 |
|
118 | 118 | const handler = new WebSocketHandler(
|
119 | 119 | kc,
|
120 |
| - (uri: string, opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
| 120 | + (uri: string, protocols: string[], opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
121 | 121 | uriOut = uri;
|
122 | 122 | return mockWs as WebSocket.WebSocket;
|
123 | 123 | },
|
@@ -163,7 +163,7 @@ describe('WebSocket', () => {
|
163 | 163 |
|
164 | 164 | const handler = new WebSocketHandler(
|
165 | 165 | kc,
|
166 |
| - (uri: string, opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
| 166 | + (uri: string, protocols: string[], opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
167 | 167 | uriOut = uri;
|
168 | 168 | return mockWs as WebSocket.WebSocket;
|
169 | 169 | },
|
@@ -233,7 +233,7 @@ describe('WebSocket', () => {
|
233 | 233 |
|
234 | 234 | const handler = new WebSocketHandler(
|
235 | 235 | kc,
|
236 |
| - (uri: string, opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
| 236 | + (uri: string, protocols: string[], opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
237 | 237 | uriOut = uri;
|
238 | 238 | return mockWs as WebSocket.WebSocket;
|
239 | 239 | },
|
@@ -314,6 +314,107 @@ describe('WebSocket', () => {
|
314 | 314 | });
|
315 | 315 | });
|
316 | 316 |
|
| 317 | +describe('V5 protocol support', () => { |
| 318 | + it('should handle close', async () => { |
| 319 | + const kc = new KubeConfig(); |
| 320 | + const host = 'foo.company.com'; |
| 321 | + const server = `https://${host}`; |
| 322 | + kc.clusters = [ |
| 323 | + { |
| 324 | + name: 'cluster', |
| 325 | + server, |
| 326 | + } as Cluster, |
| 327 | + ] as Cluster[]; |
| 328 | + kc.contexts = [ |
| 329 | + { |
| 330 | + cluster: 'cluster', |
| 331 | + user: 'user', |
| 332 | + } as Context, |
| 333 | + ] as Context[]; |
| 334 | + kc.users = [ |
| 335 | + { |
| 336 | + name: 'user', |
| 337 | + } as User, |
| 338 | + ]; |
| 339 | + |
| 340 | + const mockWs = { |
| 341 | + protocol: 'v5.channel.k8s.io', |
| 342 | + } as WebSocket.WebSocket; |
| 343 | + let uriOut = ''; |
| 344 | + let endCalled = false; |
| 345 | + const handler = new WebSocketHandler( |
| 346 | + kc, |
| 347 | + (uri: string, protocols: string[], opts: WebSocket.ClientOptions): WebSocket.WebSocket => { |
| 348 | + uriOut = uri; |
| 349 | + return mockWs as WebSocket.WebSocket; |
| 350 | + }, |
| 351 | + { |
| 352 | + stdin: process.stdin, |
| 353 | + stderr: process.stderr, |
| 354 | + stdout: { |
| 355 | + end: () => { |
| 356 | + endCalled = true; |
| 357 | + }, |
| 358 | + } as Writable, |
| 359 | + }, |
| 360 | + ); |
| 361 | + const path = '/some/path'; |
| 362 | + |
| 363 | + const promise = handler.connect(path, null, null); |
| 364 | + await setImmediatePromise(); |
| 365 | + |
| 366 | + expect(uriOut).to.equal(`wss://${host}${path}`); |
| 367 | + |
| 368 | + const event = { |
| 369 | + target: mockWs, |
| 370 | + type: 'open', |
| 371 | + }; |
| 372 | + mockWs.onopen!(event); |
| 373 | + const errEvt = { |
| 374 | + error: {}, |
| 375 | + message: 'some message', |
| 376 | + type: 'some type', |
| 377 | + target: mockWs, |
| 378 | + }; |
| 379 | + const closeBuff = Buffer.alloc(2); |
| 380 | + closeBuff.writeUint8(255, 0); |
| 381 | + closeBuff.writeUint8(WebSocketHandler.StdoutStream, 1); |
| 382 | + |
| 383 | + mockWs.onmessage!({ |
| 384 | + data: closeBuff, |
| 385 | + type: 'type', |
| 386 | + target: mockWs, |
| 387 | + }); |
| 388 | + await promise; |
| 389 | + expect(endCalled).to.be.true; |
| 390 | + }); |
| 391 | + it('should handle closing stdin < v4 protocol', () => { |
| 392 | + const ws = { |
| 393 | + // send is not defined, so this will throw if we try to send the close message. |
| 394 | + close: () => {}, |
| 395 | + } as WebSocket; |
| 396 | + const stdinStream = new ReadableStreamBuffer(); |
| 397 | + WebSocketHandler.handleStandardInput(ws, stdinStream); |
| 398 | + stdinStream.emit('end'); |
| 399 | + }); |
| 400 | + it('should handle closing stdin v5 protocol', () => { |
| 401 | + let sent: Buffer | null = null; |
| 402 | + const ws = { |
| 403 | + protocol: 'v5.channel.k8s.io', |
| 404 | + send: (data) => { |
| 405 | + sent = data; |
| 406 | + }, |
| 407 | + close: () => {}, |
| 408 | + } as WebSocket; |
| 409 | + const stdinStream = new ReadableStreamBuffer(); |
| 410 | + WebSocketHandler.handleStandardInput(ws, stdinStream); |
| 411 | + stdinStream.emit('end'); |
| 412 | + expect(sent).to.not.be.null; |
| 413 | + expect(sent!.readUint8(0)).to.equal(255); // CLOSE signal |
| 414 | + expect(sent!.readUInt8(1)).to.equal(0); // Stdin stream is #0 |
| 415 | + }); |
| 416 | +}); |
| 417 | + |
317 | 418 | describe('Restartable Handle Standard Input', () => {
|
318 | 419 | it('should throw on negative retry', () => {
|
319 | 420 | const p = new Promise<WebSocket.WebSocket>(() => {});
|
|
0 commit comments