1
1
import { anything , anyFunction , instance , mock , verify , when } from 'ts-mockito' ;
2
2
import * as querystring from 'querystring' ;
3
+ import { expect } from 'chai' ;
3
4
import WebSocket = require( 'isomorphic-ws' ) ;
4
-
5
+ import * as fs from 'node:fs' ;
6
+ import * as path from 'node:path' ;
7
+ import { tmpdir } from 'os' ;
8
+ import * as tar from 'tar' ;
5
9
import { CallAwaiter } from '../test' ;
6
10
import { KubeConfig } from './config' ;
7
11
import { Exec } from './exec' ;
8
12
import { Cp } from './cp' ;
9
- import { WebSocketHandler , WebSocketInterface } from './web-socket-handler' ;
13
+ import { BinaryHandler , WebSocketHandler , WebSocketInterface } from './web-socket-handler' ;
14
+ import { V1Status } from './api' ;
15
+ import { randomUUID } from 'crypto' ;
16
+ import { sleep } from './util' ;
10
17
11
18
describe ( 'Cp' , ( ) => {
19
+ let tmpDir : string | undefined ;
20
+
21
+ beforeEach ( ( ) => {
22
+ tmpDir = `${ tmpdir ( ) } /${ randomUUID ( ) } ` ;
23
+ fs . mkdirSync ( tmpDir ) ;
24
+ } ) ;
25
+
26
+ afterEach ( ( ) => {
27
+ if ( tmpDir ) {
28
+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
29
+ }
30
+ } ) ;
31
+
12
32
describe ( 'cpFromPod' , ( ) => {
13
33
it ( 'should run create tar command to a url' , async ( ) => {
34
+ // make the compile happy
35
+ if ( ! tmpDir ) {
36
+ throw new Error ( 'tmpDir not initialized' ) ;
37
+ }
38
+
14
39
const kc = new KubeConfig ( ) ;
15
- const fakeWebSocket : WebSocketInterface = mock ( WebSocketHandler ) ;
16
- const exec = new Exec ( kc , instance ( fakeWebSocket ) ) ;
40
+ const fakeWebSocketInterface : WebSocketInterface = mock ( WebSocketHandler ) ;
41
+ const fakeWebSocket : WebSocket . WebSocket = mock ( WebSocket ) ;
42
+ const fakeConn : WebSocket . WebSocket = instance ( fakeWebSocket ) ;
43
+ const callAwaiter : CallAwaiter = new CallAwaiter ( ) ;
44
+ const exec = new Exec ( kc , instance ( fakeWebSocketInterface ) ) ;
17
45
const cp = new Cp ( kc , exec ) ;
18
46
19
47
const namespace = 'somenamespace' ;
20
48
const pod = 'somepod' ;
21
49
const container = 'container' ;
22
50
const srcPath = '/' ;
23
- const tgtPath = '/' ;
24
51
const cmdArray = [ 'tar' , 'zcf' , '-' , srcPath ] ;
25
- const path = `/api/v1/namespaces/${ namespace } /pods/${ pod } /exec` ;
52
+ const queryPath = `/api/v1/namespaces/${ namespace } /pods/${ pod } /exec` ;
26
53
27
54
const query = {
28
55
stdout : true ,
@@ -34,14 +61,56 @@ describe('Cp', () => {
34
61
} ;
35
62
const queryStr = querystring . stringify ( query ) ;
36
63
37
- await cp . cpFromPod ( namespace , pod , container , srcPath , tgtPath ) ;
38
- // tslint:disable-next-line:max-line-length
39
- verify ( fakeWebSocket . connect ( `${ path } ?${ queryStr } ` , null , anyFunction ( ) ) ) . called ( ) ;
64
+ when ( fakeWebSocketInterface . connect ( `${ queryPath } ?${ queryStr } ` , null , anyFunction ( ) ) ) . thenCall (
65
+ callAwaiter . resolveCall ( 'connect' , fakeConn ) ,
66
+ ) ;
67
+ when ( fakeWebSocket . close ( ) ) . thenCall ( callAwaiter . resolveCall ( 'close' ) ) ;
68
+
69
+ let complete = false ;
70
+ let lastErr = undefined ;
71
+ const promise = cp
72
+ . cpFromPod ( namespace , pod , container , srcPath , tmpDir )
73
+ . then ( ( ) => {
74
+ complete = true ;
75
+ } )
76
+ . catch ( ( err ) => {
77
+ lastErr = err ;
78
+ } ) ;
79
+ expect ( lastErr ) . to . be . undefined ;
80
+ expect ( complete ) . to . be . false ;
81
+
82
+ const binaryHandler : BinaryHandler = ( await callAwaiter . awaitCall ( 'connect' ) ) [ 2 ] ;
83
+
84
+ // simulate a network hope with a sleep
85
+ await sleep ( 1 ) ;
86
+ const contents = fs . readFileSync ( 'testdata/archive.tgz' ) ;
87
+ binaryHandler ( WebSocketHandler . StdoutStream , contents ) ;
88
+
89
+ // simulate a network hope with a sleep
90
+ await sleep ( 1 ) ;
91
+ const status : V1Status = {
92
+ status : 'Success' ,
93
+ } ;
94
+ binaryHandler ( WebSocketHandler . StatusStream , Buffer . from ( JSON . stringify ( status ) ) ) ;
95
+
96
+ await promise ;
97
+
98
+ expect ( lastErr ) . to . be . undefined ;
99
+ expect ( complete ) . to . be . true ;
100
+
101
+ const found = fs . readFileSync ( path . join ( tmpDir , 'archive.txt' ) ) . toString ( 'utf8' ) ;
102
+ const expected = fs . readFileSync ( 'testdata/archive.txt' ) . toString ( 'utf8' ) ;
103
+ expect ( found ) . to . eq ( expected ) ;
40
104
} ) ;
41
105
} ) ;
42
106
43
107
describe ( 'cpToPod' , ( ) => {
44
108
it ( 'should run extract tar command to a url' , async ( ) => {
109
+ // make the compile happy
110
+ if ( ! tmpDir ) {
111
+ throw new Error ( 'tmpDir not initialized' ) ;
112
+ }
113
+
45
114
const kc = new KubeConfig ( ) ;
46
115
const fakeWebSocketInterface : WebSocketInterface = mock ( WebSocketHandler ) ;
47
116
const fakeWebSocket : WebSocket . WebSocket = mock ( WebSocket ) as WebSocket . WebSocket ;
@@ -52,10 +121,11 @@ describe('Cp', () => {
52
121
const namespace = 'somenamespace' ;
53
122
const pod = 'somepod' ;
54
123
const container = 'container' ;
55
- const srcPath = 'testdata/ archive.txt' ;
124
+ const srcPath = 'archive.txt' ;
56
125
const tgtPath = '/' ;
57
126
const cmdArray = [ 'tar' , 'xf' , '-' , '-C' , tgtPath ] ;
58
- const path = `/api/v1/namespaces/${ namespace } /pods/${ pod } /exec` ;
127
+ const cwd = 'testdata/' ;
128
+ const queryPath = `/api/v1/namespaces/${ namespace } /pods/${ pod } /exec` ;
59
129
60
130
const query = {
61
131
stdout : false ,
@@ -68,14 +138,56 @@ describe('Cp', () => {
68
138
const queryStr = querystring . stringify ( query ) ;
69
139
70
140
const fakeConn : WebSocket . WebSocket = instance ( fakeWebSocket ) ;
71
- when ( fakeWebSocketInterface . connect ( `${ path } ?${ queryStr } ` , null , anyFunction ( ) ) ) . thenResolve (
72
- fakeConn ,
141
+ when ( fakeWebSocketInterface . connect ( `${ queryPath } ?${ queryStr } ` , null , anyFunction ( ) ) ) . thenCall (
142
+ callAwaiter . resolveCall ( 'connect' , fakeConn ) ,
73
143
) ;
74
- when ( fakeWebSocket . send ( anything ( ) ) ) . thenCall ( callAwaiter . resolveCall ( 'send' ) ) ;
144
+
145
+ const outFilename = path . join ( tmpDir , 'send-data.tar' ) ;
146
+ const out = fs . createWriteStream ( outFilename ) ;
147
+ when ( fakeWebSocket . send ( anything ( ) ) ) . thenCall ( ( data ) => {
148
+ const streamNum = data . readInt8 ( 0 ) ;
149
+ if ( streamNum === WebSocketHandler . StdinStream ) {
150
+ out . write ( data . subarray ( 1 ) ) ;
151
+ } else {
152
+ console . log ( streamNum ) ;
153
+ }
154
+ } ) ;
155
+
75
156
when ( fakeWebSocket . close ( ) ) . thenCall ( callAwaiter . resolveCall ( 'close' ) ) ;
76
157
77
- await cp . cpToPod ( namespace , pod , container , srcPath , tgtPath ) ;
78
- verify ( fakeWebSocketInterface . connect ( `${ path } ?${ queryStr } ` , null , anyFunction ( ) ) ) . called ( ) ;
158
+ let complete = false ;
159
+ let lastErr = undefined ;
160
+ const promise = cp
161
+ . cpToPod ( namespace , pod , container , srcPath , tgtPath , cwd )
162
+ . then ( ( ) => {
163
+ complete = true ;
164
+ } )
165
+ . catch ( ( err ) => {
166
+ lastErr = err ;
167
+ } ) ;
168
+ expect ( lastErr ) . to . be . undefined ;
169
+ expect ( complete ) . to . be . false ;
170
+
171
+ const binaryHandler : BinaryHandler = ( await callAwaiter . awaitCall ( 'connect' ) ) [ 2 ] ;
172
+
173
+ // wait for all data to be written and close called
174
+ await callAwaiter . awaitCall ( 'close' ) ;
175
+ out . close ( ) ;
176
+ await tar . x ( { f : outFilename , cwd : tmpDir } ) ;
177
+
178
+ // simulate a network hope with a sleep
179
+ await sleep ( 1 ) ;
180
+ const status : V1Status = {
181
+ status : 'Success' ,
182
+ } ;
183
+ binaryHandler ( WebSocketHandler . StatusStream , Buffer . from ( JSON . stringify ( status ) ) ) ;
184
+
185
+ await promise ;
186
+
187
+ expect ( lastErr ) . to . be . undefined ;
188
+ expect ( complete ) . to . be . true ;
189
+
190
+ verify ( fakeWebSocketInterface . connect ( `${ queryPath } ?${ queryStr } ` , null , anyFunction ( ) ) ) . called ( ) ;
79
191
} ) ;
80
192
} ) ;
81
193
} ) ;
0 commit comments