1
+ jest . mock ( 'listr/lib/renderer' ) ;
2
+ jest . mock ( '@twilio-labs/serverless-api' ) ;
3
+ jest . mock ( 'got' ) ;
4
+ jest . mock ( 'pkg-install' ) ;
5
+ jest . mock ( '../../src/utils/fs' ) ;
6
+ jest . mock ( '../../src/utils/logger' ) ;
7
+
8
+ import path from 'path' ;
9
+ import { install } from 'pkg-install' ;
10
+ import { fsHelpers } from '@twilio-labs/serverless-api' ;
11
+ import got from 'got' ;
12
+ import { mocked } from 'ts-jest/utils' ;
13
+ import { writeFiles } from '../../src/templating/filesystem' ;
14
+ import { downloadFile , fileExists , readFile , writeFile , mkdir } from '../../src/utils/fs' ;
15
+
16
+ beforeEach ( ( ) => {
17
+ // For our test, replace the `listr` renderer with a silent one so the tests
18
+ // don't get confusing output in them.
19
+ const { getRenderer} = jest . requireMock ( 'listr/lib/renderer' ) ;
20
+ mocked ( getRenderer ) . mockImplementation ( ( ) => require ( 'listr-silent-renderer' ) ) ;
21
+ } ) ;
22
+
23
+ afterEach ( ( ) => { jest . resetAllMocks ( ) ; jest . restoreAllMocks ( ) } ) ;
24
+
25
+ test ( 'bubbles up an exception when functions directory is missing' , async ( ) => {
26
+ // For this test, getFirstMatchingDirectory always errors
27
+ mocked ( fsHelpers . getFirstMatchingDirectory ) . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => {
28
+ throw new Error ( `Could not find any of these directories "${ directories . join ( '", "' ) } "` ) ;
29
+ } ) ;
30
+
31
+ await expect ( writeFiles ( [ ] , './testing/' , 'example' ) )
32
+ . rejects . toThrowError ( 'Could not find any of these directories "functions", "src"' ) ;
33
+ } ) ;
34
+
35
+ test ( 'bubbles up an exception when assets directory is missing' , async ( ) => {
36
+ // For this test, getFirstMatchingDirectory only errors on `assets` directory.
37
+ mocked ( fsHelpers . getFirstMatchingDirectory ) . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => {
38
+ if ( directories . includes ( 'functions' ) ) {
39
+ return path . join ( basePath , directories [ 0 ] ) ;
40
+ }
41
+
42
+ throw new Error ( `Could not find any of these directories "${ directories . join ( '", "' ) } "` ) ;
43
+ } ) ;
44
+
45
+ await expect ( writeFiles ( [ ] , './testing/' , 'example' ) )
46
+ . rejects . toThrowError ( 'Could not find any of these directories "assets", "static"' ) ;
47
+ } ) ;
48
+
49
+ test ( 'installation with basic functions' , async ( ) => {
50
+ // For this test, getFirstMatchingDirectory never errors.
51
+ mocked ( fsHelpers . getFirstMatchingDirectory )
52
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
53
+
54
+ await writeFiles (
55
+ [
56
+ { name : 'hello.js' , type : 'functions' , content : 'https://example.com/hello.js' } ,
57
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
58
+ ] ,
59
+ './testing/' ,
60
+ 'example'
61
+ ) ;
62
+
63
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 2 ) ;
64
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/.env' , 'testing/.env' ) ;
65
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/hello.js' , 'testing/functions/example/hello.js' ) ;
66
+
67
+ expect ( mkdir ) . toHaveBeenCalledTimes ( 1 ) ;
68
+ expect ( mkdir ) . toHaveBeenCalledWith ( 'testing/functions/example' , { recursive : true } ) ;
69
+ } ) ;
70
+
71
+ test ( 'installation with functions and assets' , async ( ) => {
72
+ // For this test, getFirstMatchingDirectory never errors.
73
+ mocked ( fsHelpers . getFirstMatchingDirectory )
74
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
75
+
76
+ await writeFiles (
77
+ [
78
+ { name : 'hello.js' , type : 'functions' , content : 'https://example.com/hello.js' } ,
79
+ { name : 'hello.wav' , type : 'assets' , content : 'https://example.com/hello.wav' } ,
80
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
81
+ ] ,
82
+ './testing/' ,
83
+ 'example'
84
+ ) ;
85
+
86
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 3 ) ;
87
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/.env' , 'testing/.env' ) ;
88
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/hello.js' , 'testing/functions/example/hello.js' ) ;
89
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/hello.wav' , 'testing/assets/example/hello.wav' ) ;
90
+
91
+ expect ( mkdir ) . toHaveBeenCalledTimes ( 2 ) ;
92
+ expect ( mkdir ) . toHaveBeenCalledWith ( 'testing/functions/example' , { recursive : true } ) ;
93
+ expect ( mkdir ) . toHaveBeenCalledWith ( 'testing/assets/example' , { recursive : true } ) ;
94
+ } ) ;
95
+
96
+ test ( 'installation without dot-env file causes unexpected crash' , async ( ) => {
97
+ // I don't believe this is the intended behavior but it's the current behavior.
98
+ // As such, let's create a test for it which can be removed / changed later
99
+ // once the behavior is fixed.
100
+
101
+ // For this test, getFirstMatchingDirectory never errors.
102
+ mocked ( fsHelpers . getFirstMatchingDirectory )
103
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
104
+
105
+ const expected = new TypeError ( 'Cannot read property \'newEnvironmentVariableKeys\' of undefined' ) ;
106
+
107
+ await expect ( writeFiles ( [ ] , './testing/' , 'example' ) )
108
+ . rejects . toThrowError ( expected ) ;
109
+ } ) ;
110
+
111
+ test ( 'installation with an empty dependency file' , async ( ) => {
112
+ // The typing of `got` is not exactly correct for this - it expects a
113
+ // buffer but depending on inputs `got` can actually return an object.
114
+ // @ts -ignore
115
+ mocked ( got ) . mockImplementation ( ( ) => Promise . resolve ( { body : { dependencies : { } } } ) ) ;
116
+
117
+ // For this test, getFirstMatchingDirectory never errors.
118
+ mocked ( fsHelpers . getFirstMatchingDirectory )
119
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
120
+
121
+ await writeFiles (
122
+ [
123
+ { name : 'package.json' , type : 'package.json' , content : 'https://example.com/package.json' } ,
124
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
125
+ ] ,
126
+ './testing/' ,
127
+ 'example'
128
+ ) ;
129
+
130
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 1 ) ;
131
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/.env' , 'testing/.env' ) ;
132
+
133
+ expect ( got ) . toHaveBeenCalledTimes ( 1 ) ;
134
+ expect ( got ) . toHaveBeenCalledWith ( 'https://example.com/package.json' , { json : true } ) ;
135
+
136
+ expect ( install ) . not . toHaveBeenCalled ( ) ;
137
+ } ) ;
138
+
139
+ test ( 'installation with a dependency file' , async ( ) => {
140
+ // The typing of `got` is not exactly correct for this - it expects a
141
+ // buffer but depending on inputs `got` can actually return an object.
142
+ // @ts -ignore
143
+ mocked ( got ) . mockImplementation ( ( ) => Promise . resolve ( { body : { dependencies : { foo : '^1.0.0' , got : '^6.9.0' } } } ) ) ;
144
+
145
+ // For this test, getFirstMatchingDirectory never errors.
146
+ mocked ( fsHelpers . getFirstMatchingDirectory )
147
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
148
+
149
+ await writeFiles (
150
+ [
151
+ { name : 'package.json' , type : 'package.json' , content : 'https://example.com/package.json' } ,
152
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
153
+ ] ,
154
+ './testing/' ,
155
+ 'example'
156
+ ) ;
157
+
158
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 1 ) ;
159
+ expect ( downloadFile ) . toHaveBeenCalledWith ( 'https://example.com/.env' , 'testing/.env' ) ;
160
+
161
+ expect ( got ) . toHaveBeenCalledTimes ( 1 ) ;
162
+ expect ( got ) . toHaveBeenCalledWith ( 'https://example.com/package.json' , { json : true } ) ;
163
+
164
+ expect ( install ) . toHaveBeenCalledTimes ( 1 ) ;
165
+ expect ( install ) . toHaveBeenCalledWith ( { foo : '^1.0.0' , got : '^6.9.0' } , { cwd : './testing/' } ) ;
166
+ } ) ;
167
+
168
+ test ( 'installation with an existing dot-env file' , async ( ) => {
169
+ mocked ( fileExists ) . mockReturnValue ( Promise . resolve ( true ) ) ;
170
+ mocked ( readFile ) . mockReturnValue ( Promise . resolve ( '# Comment\nFOO=BAR\n' ) ) ;
171
+
172
+ // The typing of `got` is not exactly correct for this - it expects a
173
+ // buffer but depending on inputs `got` can actually return an object.
174
+ // @ts -ignore
175
+ mocked ( got ) . mockImplementation ( ( ) => Promise . resolve ( { body : 'HELLO=WORLD\n' } ) ) ;
176
+
177
+ // For this test, getFirstMatchingDirectory never errors.
178
+ mocked ( fsHelpers . getFirstMatchingDirectory )
179
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
180
+
181
+ await writeFiles (
182
+ [
183
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
184
+ ] ,
185
+ './testing/' ,
186
+ 'example'
187
+ ) ;
188
+
189
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 0 ) ;
190
+
191
+ expect ( writeFile ) . toHaveBeenCalledTimes ( 1 ) ;
192
+ expect ( writeFile ) . toHaveBeenCalledWith (
193
+ 'testing/.env' ,
194
+ '# Comment\n' +
195
+ 'FOO=BAR\n' +
196
+ '\n\n' +
197
+ '# Variables for function \".env\"\n' + // This seems to be a bug but is the output.
198
+ '# ---\n' +
199
+ 'HELLO=WORLD\n' ,
200
+ "utf8"
201
+ ) ;
202
+ } ) ;
203
+
204
+ test ( 'installation with overlapping function files throws errors before writing' , async ( ) => {
205
+ mocked ( fsHelpers . getFirstMatchingDirectory )
206
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
207
+
208
+ mocked ( fileExists ) . mockImplementation ( ( p ) => Promise . resolve ( p == 'functions/example/hello.js' ) ) ;
209
+
210
+ await expect ( writeFiles (
211
+ [
212
+ { name : 'hello.js' , type : 'functions' , content : 'https://example.com/hello.js' } ,
213
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
214
+ ] ,
215
+ './' ,
216
+ 'example'
217
+ ) ) . rejects . toThrowError ( 'Template with name "example" has duplicate file "hello.js" in "functions"' ) ;
218
+
219
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 0 ) ;
220
+ expect ( writeFile ) . toHaveBeenCalledTimes ( 0 ) ;
221
+
222
+ } ) ;
223
+
224
+ test ( 'installation with overlapping asset files throws errors before writing' , async ( ) => {
225
+ mocked ( fsHelpers . getFirstMatchingDirectory )
226
+ . mockImplementation ( ( basePath : string , directories : Array < string > ) : string => path . join ( basePath , directories [ 0 ] ) ) ;
227
+
228
+ mocked ( fileExists ) . mockImplementation ( ( p ) => Promise . resolve ( p == 'assets/example/hello.wav' ) ) ;
229
+
230
+ await expect ( writeFiles (
231
+ [
232
+ { name : 'hello.js' , type : 'functions' , content : 'https://example.com/hello.js' } ,
233
+ { name : 'hello.wav' , type : 'assets' , content : 'https://example.com/hello.wav' } ,
234
+ { name : '.env' , type : '.env' , content : 'https://example.com/.env' } ,
235
+ ] ,
236
+ './' ,
237
+ 'example'
238
+ ) ) . rejects . toThrowError ( 'Template with name "example" has duplicate file "hello.wav" in "assets"' ) ;
239
+
240
+ expect ( downloadFile ) . toHaveBeenCalledTimes ( 0 ) ;
241
+ expect ( writeFile ) . toHaveBeenCalledTimes ( 0 ) ;
242
+ } ) ;
0 commit comments