UNPKG

46.3 kBJavaScriptView Raw
1'use strict'
2
3const path = require('path')
4const os = require('os')
5const fs = require('fs-extra')
6const lambda = require(path.join(__dirname, '..', 'lib', 'main'))
7const Zip = require('node-zip')
8const { assert } = require('chai')
9const awsMock = require('aws-sdk-mock')
10awsMock.setSDK(path.resolve('node_modules/aws-sdk'))
11
12const originalProgram = {
13 environment: 'development',
14 accessKey: 'key',
15 secretKey: 'secret',
16 sessionToken: 'token',
17 functionName: '___node-lambda',
18 handler: 'index.handler',
19 role: 'some:arn:aws:iam::role',
20 memorySize: 128,
21 timeout: 3,
22 description: '',
23 runtime: 'nodejs14.x',
24 deadLetterConfigTargetArn: '',
25 tracingConfig: '',
26 Layers: '',
27 retentionInDays: 30,
28 region: 'us-east-1,us-west-2,eu-west-1',
29 eventFile: 'event.json',
30 eventSourceFile: '',
31 contextFile: 'context.json',
32 deployTimeout: 120000,
33 prebuiltDirectory: '',
34 proxy: ''
35}
36
37let program = {}
38let codeDirectory = lambda._codeDirectory()
39
40const _timeout = function (params) {
41 // Even if timeout is set for the whole test for Windows,
42 // if it is set in local it will be valid.
43 // For Windows, do not set it with local.
44 if (process.platform !== 'win32') {
45 params.this.timeout(params.sec * 1000)
46 }
47}
48
49// It does not completely reproduce the response of the actual API.
50const lambdaMockSettings = {
51 addPermission: {},
52 getFunction: {
53 Code: {},
54 Configuration: {},
55 FunctionArn: 'Lambda.getFunction.mock.FunctionArn'
56 },
57 createFunction: {
58 FunctionArn: 'Lambda.createFunction.mock.FunctionArn',
59 FunctionName: 'Lambda.createFunction.mock.FunctionName'
60 },
61 listEventSourceMappings: {
62 EventSourceMappings: [{
63 EventSourceArn: 'Lambda.listEventSourceMappings.mock.EventSourceArn',
64 UUID: 'Lambda.listEventSourceMappings.mock.UUID'
65 }]
66 },
67 updateFunctionCode: {
68 FunctionArn: 'Lambda.updateFunctionCode.mock.FunctionArn',
69 FunctionName: 'Lambda.updateFunctionCode.mock.FunctionName'
70 },
71 updateFunctionConfiguration: {
72 FunctionArn: 'Lambda.updateFunctionConfiguration.mock.FunctionArn',
73 FunctionName: 'Lambda.updateFunctionConfiguration.mock.FunctionName'
74 },
75 createEventSourceMapping: {
76 EventSourceArn: 'Lambda.createEventSourceMapping.mock.EventSourceArn',
77 FunctionName: 'Lambda.createEventSourceMapping.mock.EventSourceArn'
78 },
79 updateEventSourceMapping: {
80 EventSourceArn: 'Lambda.updateEventSourceMapping.mock.EventSourceArn',
81 FunctionName: 'Lambda.updateEventSourceMapping.mock.EventSourceArn'
82 },
83 deleteEventSourceMapping: {
84 EventSourceArn: 'Lambda.deleteEventSourceMapping.mock.EventSourceArn',
85 FunctionName: 'Lambda.deleteEventSourceMapping.mock.EventSourceArn'
86 },
87 listTags: {
88 Tags: { tag1: 'key1' }
89 },
90 untagResource: {},
91 tagResource: {}
92}
93
94const _mockSetting = () => {
95 awsMock.mock('CloudWatchEvents', 'putRule', (params, callback) => {
96 callback(null, {})
97 })
98 awsMock.mock('CloudWatchEvents', 'putTargets', (params, callback) => {
99 callback(null, {})
100 })
101 awsMock.mock('CloudWatchLogs', 'createLogGroup', (params, callback) => {
102 callback(null, {})
103 })
104 awsMock.mock('CloudWatchLogs', 'putRetentionPolicy', (params, callback) => {
105 callback(null, {})
106 })
107 awsMock.mock('S3', 'putBucketNotificationConfiguration', (params, callback) => {
108 callback(null, {})
109 })
110 awsMock.mock('S3', 'putObject', (params, callback) => {
111 callback(null, { test: 'putObject' })
112 })
113
114 Object.keys(lambdaMockSettings).forEach((method) => {
115 awsMock.mock('Lambda', method, (params, callback) => {
116 callback(null, lambdaMockSettings[method])
117 })
118 })
119
120 return require('aws-sdk')
121}
122
123const _awsRestore = () => {
124 awsMock.restore('CloudWatchEvents')
125 awsMock.restore('CloudWatchLogs')
126 awsMock.restore('S3')
127 awsMock.restore('Lambda')
128}
129
130/* global before, after, beforeEach, afterEach, describe, it */
131describe('lib/main', function () {
132 if (process.platform === 'win32') {
133 // It seems that it takes time for file operation in Windows.
134 // So set `timeout(60000)` for the whole test.
135 this.timeout(60000)
136 }
137
138 let aws = null // mock
139 let awsLambda = null // mock
140 before(() => {
141 aws = _mockSetting()
142 awsLambda = new aws.Lambda({ apiVersion: '2015-03-31' })
143 })
144 after(() => _awsRestore())
145
146 beforeEach(() => {
147 program = Object.assign({}, originalProgram) // clone
148 })
149
150 it('version should be set', () => {
151 assert.equal(lambda.version, '0.18.0')
152 })
153
154 describe('_codeDirectory', () => {
155 it('Working directory in the /tmp directory', () => {
156 assert.equal(
157 lambda._codeDirectory(),
158 path.join(fs.realpathSync(os.tmpdir()), 'node-lambda-lambda')
159 )
160 })
161 })
162
163 describe('_runHandler', () => {
164 it('context methods is a function', (done) => {
165 const handler = (event, context, callback) => {
166 assert.isFunction(context.succeed)
167 assert.isFunction(context.fail)
168 assert.isFunction(context.done)
169 done()
170 }
171 lambda._runHandler(handler, {}, program, {})
172 })
173 })
174
175 describe('_isFunctionDoesNotExist', () => {
176 it('=== true', () => {
177 const err = {
178 code: 'ResourceNotFoundException',
179 message: 'Function not found: arn:aws:lambda:XXX'
180 }
181 assert.isTrue(lambda._isFunctionDoesNotExist(err))
182 })
183
184 it('=== false', () => {
185 const err = {
186 code: 'MissingRequiredParameter',
187 message: 'Missing required key \'FunctionName\' in params'
188 }
189 assert.isFalse(lambda._isFunctionDoesNotExist(err))
190 })
191 })
192
193 describe('_isUseS3', () => {
194 it('=== true', () => {
195 assert.isTrue(lambda._isUseS3({ deployUseS3: true }))
196 assert.isTrue(lambda._isUseS3({ deployUseS3: 'true' }))
197 })
198
199 it('=== false', () => {
200 [
201 {},
202 { deployUseS3: false },
203 { deployUseS3: 'false' },
204 { deployUseS3: 'foo' }
205 ].forEach((params) => {
206 assert.isFalse(lambda._isUseS3(params), params)
207 })
208 })
209 })
210
211 describe('_useECR', () => {
212 it('=== true', () => {
213 assert.isTrue(lambda._useECR({ imageUri: 'xxx' }))
214 })
215
216 it('=== false', () => {
217 [
218 {},
219 { imageUri: null },
220 { imageUri: '' }
221 ].forEach((params) => {
222 assert.isFalse(lambda._useECR(params), params)
223 })
224 })
225 })
226
227 describe('_params', () => {
228 // http://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-FunctionName
229 const functionNamePattern =
230 /(arn:aws:lambda:)?([a-z]{2}-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?/
231 it('appends environment to original functionName', () => {
232 const params = lambda._params(program)
233 assert.equal(params.FunctionName, '___node-lambda-development')
234 assert.match(params.FunctionName, functionNamePattern)
235 })
236
237 it('appends environment to original functionName (production)', () => {
238 program.environment = 'production'
239 const params = lambda._params(program)
240 assert.equal(params.FunctionName, '___node-lambda-production')
241 assert.match(params.FunctionName, functionNamePattern)
242 })
243
244 it('appends version to original functionName', () => {
245 program.lambdaVersion = '2015-02-01'
246 const params = lambda._params(program)
247 assert.equal(params.FunctionName, '___node-lambda-development-2015-02-01')
248 assert.match(params.FunctionName, functionNamePattern)
249 })
250
251 it('appends version to original functionName (value not allowed by AWS)', () => {
252 program.lambdaVersion = '2015.02.01'
253 const params = lambda._params(program)
254 assert.equal(params.FunctionName, '___node-lambda-development-2015_02_01')
255 assert.match(params.FunctionName, functionNamePattern)
256 })
257
258 it('appends VpcConfig to params when vpc params set', () => {
259 program.vpcSubnets = 'subnet-00000000,subnet-00000001,subnet-00000002'
260 program.vpcSecurityGroups = 'sg-00000000,sg-00000001,sg-00000002'
261 const params = lambda._params(program)
262 assert.equal(params.VpcConfig.SubnetIds[0], program.vpcSubnets.split(',')[0])
263 assert.equal(params.VpcConfig.SubnetIds[1], program.vpcSubnets.split(',')[1])
264 assert.equal(params.VpcConfig.SubnetIds[2], program.vpcSubnets.split(',')[2])
265 assert.equal(params.VpcConfig.SecurityGroupIds[0], program.vpcSecurityGroups.split(',')[0])
266 assert.equal(params.VpcConfig.SecurityGroupIds[1], program.vpcSecurityGroups.split(',')[1])
267 assert.equal(params.VpcConfig.SecurityGroupIds[2], program.vpcSecurityGroups.split(',')[2])
268 })
269
270 it('does not append VpcConfig when params are not set', () => {
271 const params = lambda._params(program)
272 assert.equal(Object.keys(params.VpcConfig.SubnetIds).length, 0)
273 assert.equal(Object.keys(params.VpcConfig.SecurityGroupIds).length, 0)
274 })
275
276 it('appends KMSKeyArn to params when KMS params set', () => {
277 ['', 'arn:aws:kms:test'].forEach((v) => {
278 program.kmsKeyArn = v
279 const params = lambda._params(program)
280 assert.equal(params.KMSKeyArn, v, v)
281 })
282 })
283
284 it('does not append KMSKeyArn when params are not set', () => {
285 const params = lambda._params(program)
286 assert.isUndefined(params.KMSKeyArn)
287 })
288
289 it('appends DeadLetterConfig to params when DLQ params set', () => {
290 ['', 'arn:aws:sqs:test'].forEach((v) => {
291 program.deadLetterConfigTargetArn = v
292 const params = lambda._params(program)
293 assert.equal(params.DeadLetterConfig.TargetArn, v, v)
294 })
295 })
296
297 it('does not append DeadLetterConfig when params are not set', () => {
298 delete program.deadLetterConfigTargetArn
299 const params = lambda._params(program)
300 assert.isNull(params.DeadLetterConfig.TargetArn)
301 })
302
303 it('appends TracingConfig to params when params set', () => {
304 program.tracingConfig = 'Active'
305 const params = lambda._params(program)
306 assert.equal(params.TracingConfig.Mode, 'Active')
307 })
308
309 it('does not append TracingConfig when params are not set', () => {
310 program.tracingConfig = ''
311 const params = lambda._params(program)
312 assert.isNull(params.TracingConfig.Mode)
313 })
314
315 it('appends Layers to params when params set', () => {
316 program.layers = 'Layer1,Layer2'
317 const params = lambda._params(program)
318 assert.deepEqual(params.Layers, ['Layer1', 'Layer2'])
319 })
320
321 it('does not append Layers when params are not set', () => {
322 program.layers = ''
323 const params = lambda._params(program)
324 assert.deepEqual(params.Layers, [])
325 })
326
327 describe('S3 deploy', () => {
328 it('Do not use S3 deploy', () => {
329 const params = lambda._params(program, 'Buffer')
330 assert.deepEqual(
331 params.Code,
332 { ZipFile: 'Buffer' }
333 )
334 })
335
336 it('Use S3 deploy', () => {
337 const params = lambda._params(Object.assign({ deployUseS3: true }, program), 'Buffer')
338 assert.deepEqual(
339 params.Code,
340 {
341 S3Bucket: null,
342 S3Key: null
343 }
344 )
345 })
346 })
347
348 describe('PackageType: Zip|Image', () => {
349 it('PackageType: Zip', () => {
350 const params = lambda._params(program, 'Buffer')
351 assert.equal(params.PackageType, 'Zip')
352 assert.deepEqual(
353 params.Code,
354 { ZipFile: 'Buffer' }
355 )
356 })
357
358 it('PackageType: Image', () => {
359 program.imageUri = 'xxx'
360 const params = lambda._params(program, 'Buffer')
361 assert.equal(params.PackageType, 'Image')
362
363 assert.isUndefined(params.Handler)
364 assert.isUndefined(params.Runtime)
365 assert.isUndefined(params.KMSKeyArn)
366
367 assert.deepEqual(
368 params.Code,
369 { ImageUri: 'xxx' }
370 )
371 })
372 })
373
374 describe('params.Publish', () => {
375 describe('boolean', () => {
376 it('If true, it is set to true', () => {
377 program.publish = true
378 const params = lambda._params(program)
379 assert.isTrue(params.Publish)
380 })
381 it('If false, it is set to false', () => {
382 program.publish = false
383 const params = lambda._params(program)
384 assert.isFalse(params.Publish)
385 })
386 })
387
388 describe('string', () => {
389 it('If "true", it is set to true', () => {
390 program.publish = 'true'
391 const params = lambda._params(program)
392 assert.isTrue(params.Publish)
393 })
394 it('If not "true", it is set to false', () => {
395 program.publish = 'false'
396 assert.isFalse(lambda._params(program).Publish)
397 program.publish = 'aaa'
398 assert.isFalse(lambda._params(program).Publish)
399 })
400 })
401 })
402
403 describe('configFile', () => {
404 beforeEach(() => {
405 // Prep...
406 fs.writeFileSync('tmp.env', 'FOO=bar\nBAZ=bing\n')
407 fs.writeFileSync('empty.env', '')
408 })
409
410 afterEach(() => {
411 fs.unlinkSync('tmp.env')
412 fs.unlinkSync('empty.env')
413 })
414
415 it('adds variables when configFile param is set', () => {
416 program.configFile = 'tmp.env'
417 const params = lambda._params(program)
418 assert.equal(params.Environment.Variables.FOO, 'bar')
419 assert.equal(params.Environment.Variables.BAZ, 'bing')
420 })
421
422 it('when configFile param is set but it is an empty file', () => {
423 program.configFile = 'empty.env'
424 const params = lambda._params(program)
425 assert.equal(Object.keys(params.Environment.Variables).length, 0)
426 })
427
428 it('does not add when configFile param is not set', () => {
429 const params = lambda._params(program)
430 assert.isNull(params.Environment.Variables)
431 })
432 })
433 })
434
435 describe('_cleanDirectory', () => {
436 it('`codeDirectory` is empty', () => {
437 return lambda._cleanDirectory(codeDirectory).then(() => {
438 assert.isTrue(fs.existsSync(codeDirectory))
439 const contents = fs.readdirSync(codeDirectory)
440 assert.equal(contents.length, 0)
441 })
442 })
443
444 it('`codeDirectory` is empty. (For `codeDirectory` where the file was present)', () => {
445 return lambda._fileCopy(program, '.', codeDirectory, true).then(() => {
446 const contents = fs.readdirSync(codeDirectory)
447 assert.isTrue(contents.length > 0)
448 return lambda._cleanDirectory(codeDirectory).then(() => {
449 assert.isTrue(fs.existsSync(codeDirectory))
450 const contents = fs.readdirSync(codeDirectory)
451 assert.equal(contents.length, 0)
452 })
453 })
454 })
455 })
456
457 describe('_fileCopy', () => {
458 before(() => {
459 fs.mkdirSync('build')
460 fs.mkdirsSync(path.join('__unittest', 'hoge'))
461 fs.mkdirsSync(path.join('__unittest', 'fuga'))
462 fs.writeFileSync(path.join('__unittest', 'hoge', 'piyo'), '')
463 fs.writeFileSync(path.join('__unittest', 'hoge', 'package.json'), '')
464 fs.writeFileSync('fuga', '')
465 })
466 after(() => {
467 ['build', 'fuga', '__unittest'].forEach((path) => {
468 fs.removeSync(path)
469 })
470 })
471
472 beforeEach(() => lambda._cleanDirectory(codeDirectory))
473
474 it('_fileCopy an index.js as well as other files', () => {
475 return lambda._fileCopy(program, '.', codeDirectory, true).then(() => {
476 const contents = fs.readdirSync(codeDirectory);
477 ['index.js', 'package.json'].forEach((needle) => {
478 assert.include(contents, needle, `Target: "${needle}"`)
479 });
480 ['node_modules', 'build'].forEach((needle) => {
481 assert.notInclude(contents, needle, `Target: "${needle}"`)
482 })
483 })
484 })
485
486 describe('when there are excluded files', () => {
487 beforeEach((done) => {
488 // *main* => lib/main.js
489 // In case of specifying files under the directory with wildcards
490 program.excludeGlobs = [
491 '*.png',
492 'test',
493 '*main*',
494 path.join('__unittest', 'hoge', '*'),
495 path.join('fuga', path.sep)
496 ].join(' ')
497 done()
498 })
499
500 it('_fileCopy an index.js as well as other files', () => {
501 return lambda._fileCopy(program, '.', codeDirectory, true).then(() => {
502 const contents = fs.readdirSync(codeDirectory);
503 ['index.js', 'package.json'].forEach((needle) => {
504 assert.include(contents, needle, `Target: "${needle}"`)
505 })
506 })
507 })
508
509 it('_fileCopy excludes files matching excludeGlobs', () => {
510 return lambda._fileCopy(program, '.', codeDirectory, true).then(() => {
511 let contents = fs.readdirSync(codeDirectory);
512 ['__unittest', 'fuga'].forEach((needle) => {
513 assert.include(contents, needle, `Target: "${needle}"`)
514 });
515
516 ['node-lambda.png', 'test'].forEach((needle) => {
517 assert.notInclude(contents, needle, `Target: "${needle}"`)
518 })
519
520 contents = fs.readdirSync(path.join(codeDirectory, 'lib'))
521 assert.notInclude(contents, 'main.js', 'Target: "lib/main.js"')
522
523 contents = fs.readdirSync(path.join(codeDirectory, '__unittest'))
524 assert.include(contents, 'hoge', 'Target: "__unittest/hoge"')
525 assert.notInclude(contents, 'fuga', 'Target: "__unittest/fuga"')
526
527 contents = fs.readdirSync(path.join(codeDirectory, '__unittest', 'hoge'))
528 assert.equal(contents.length, 0, 'directory:__unittest/hoge is empty')
529 })
530 })
531
532 it('_fileCopy should not exclude package.json, even when excluded by excludeGlobs', () => {
533 program.excludeGlobs = '*.json'
534 return lambda._fileCopy(program, '.', codeDirectory, true).then(() => {
535 const contents = fs.readdirSync(codeDirectory)
536 assert.include(contents, 'package.json')
537 })
538 })
539 it('_fileCopy should not exclude package-lock.json, even when excluded by excludeGlobs', () => {
540 program.excludeGlobs = '*.json'
541 return lambda._fileCopy(program, '.', codeDirectory, true).then(() => {
542 const contents = fs.readdirSync(codeDirectory)
543 assert.include(contents, 'package-lock.json')
544 })
545 })
546
547 it('_fileCopy should not include package.json when --prebuiltDirectory is set', () => {
548 const buildDir = '.build_' + Date.now()
549 after(() => fs.removeSync(buildDir))
550
551 fs.mkdirSync(buildDir)
552 fs.writeFileSync(path.join(buildDir, 'testa'), '')
553 fs.writeFileSync(path.join(buildDir, 'package.json'), '')
554
555 program.excludeGlobs = '*.json'
556 program.prebuiltDirectory = buildDir
557 return lambda._fileCopy(program, buildDir, codeDirectory, true).then(() => {
558 const contents = fs.readdirSync(codeDirectory)
559 assert.notInclude(contents, 'package.json', 'Target: "packages.json"')
560 assert.include(contents, 'testa', 'Target: "testa"')
561 })
562 })
563 })
564 })
565
566 describe('_shouldUseNpmCi', () => {
567 beforeEach(() => {
568 return lambda._cleanDirectory(codeDirectory)
569 })
570
571 describe('when package-lock.json exists', () => {
572 beforeEach(() => {
573 fs.writeFileSync(path.join(codeDirectory, 'package-lock.json'), JSON.stringify({}))
574 })
575
576 it('returns true', () => {
577 assert.isTrue(lambda._shouldUseNpmCi(codeDirectory))
578 })
579 })
580
581 describe('when package-lock.json does not exist', () => {
582 beforeEach(() => {
583 fs.removeSync(path.join(codeDirectory, 'package-lock.json'))
584 })
585
586 it('returns false', () => {
587 assert.isFalse(lambda._shouldUseNpmCi(codeDirectory))
588 })
589 })
590 })
591
592 describe('_npmInstall', function () {
593 _timeout({ this: this, sec: 30 }) // ci should be faster than install
594
595 // npm treats files as packages when installing, and so removes them.
596 // Test with `devDependencies` packages that are not installed with the `--production` option.
597 const nodeModulesMocha = path.join(codeDirectory, 'node_modules', 'mocha')
598
599 beforeEach(() => {
600 return lambda._cleanDirectory(codeDirectory).then(() => {
601 fs.copySync(
602 path.join('node_modules', 'aws-sdk'),
603 path.join(codeDirectory, 'node_modules', 'aws-sdk')
604 )
605 return lambda._fileCopy(program, '.', codeDirectory, true)
606 })
607 })
608
609 describe('when package-lock.json does exist', () => {
610 it('should use "npm ci"', () => {
611 const beforeAwsSdkStat = fs.statSync(path.join(codeDirectory, 'node_modules', 'aws-sdk'))
612 return lambda._npmInstall(program, codeDirectory).then(() => {
613 const contents = fs.readdirSync(path.join(codeDirectory, 'node_modules'))
614 assert.include(contents, 'dotenv')
615
616 // To remove and then install.
617 // beforeAwsSdkStat.ctimeMs < afterAwsSdkStat.ctimeMs
618 const afterAwsSdkStat = fs.statSync(path.join(codeDirectory, 'node_modules', 'aws-sdk'))
619 assert.isBelow(beforeAwsSdkStat.ctimeMs, afterAwsSdkStat.ctimeMs)
620
621 // Not installed with the `--production` option.
622 assert.isFalse(fs.existsSync(nodeModulesMocha))
623 })
624 })
625 })
626
627 describe('when package-lock.json does not exist', () => {
628 beforeEach(() => {
629 return fs.removeSync(path.join(codeDirectory, 'package-lock.json'))
630 })
631
632 it('should use "npm install"', () => {
633 const beforeAwsSdkStat = fs.statSync(path.join(codeDirectory, 'node_modules', 'aws-sdk'))
634 return lambda._npmInstall(program, codeDirectory).then(() => {
635 const contents = fs.readdirSync(path.join(codeDirectory, 'node_modules'))
636 assert.include(contents, 'dotenv')
637
638 // Installed packages will remain intact.
639 // beforeAwsSdkStat.ctimeMs === afterAwsSdkStat.ctimeMs
640 const afterAwsSdkStat = fs.statSync(path.join(codeDirectory, 'node_modules', 'aws-sdk'))
641 assert.equal(beforeAwsSdkStat.ctimeMs, afterAwsSdkStat.ctimeMs)
642
643 // Not installed with the `--production` option.
644 assert.isFalse(fs.existsSync(nodeModulesMocha))
645 })
646 })
647 })
648 })
649
650 describe('_npmInstall (When codeDirectory contains characters to be escaped)', () => {
651 beforeEach(() => {
652 // Since '\' can not be included in the file or directory name in Windows
653 const directoryName = process.platform === 'win32'
654 ? 'hoge fuga\' piyo'
655 : 'hoge "fuga\' \\piyo'
656 codeDirectory = path.join(os.tmpdir(), directoryName)
657 return lambda._cleanDirectory(codeDirectory).then(() => {
658 return lambda._fileCopy(program, '.', codeDirectory, true)
659 })
660 })
661
662 afterEach(() => {
663 fs.removeSync(codeDirectory)
664 codeDirectory = lambda._codeDirectory()
665 })
666
667 it('_npm adds node_modules', function () {
668 _timeout({ this: this, sec: 30 }) // give it time to build the node modules
669
670 return lambda._npmInstall(program, codeDirectory).then(() => {
671 const contents = fs.readdirSync(codeDirectory)
672 assert.include(contents, 'node_modules')
673 })
674 })
675 })
676
677 describe('_postInstallScript', () => {
678 if (process.platform === 'win32') {
679 return it('`_postInstallScript` test does not support Windows.')
680 }
681
682 const postInstallScriptPath = path.join(codeDirectory, 'post_install.sh')
683 let hook
684 /**
685 * Capture console output
686 */
687 const captureStream = function (stream) {
688 const oldWrite = stream.write
689 let buf = ''
690 stream.write = function (chunk, encoding, callback) {
691 buf += chunk.toString() // chunk is a String or Buffer
692 oldWrite.apply(stream, arguments)
693 }
694
695 return {
696 unhook: () => {
697 stream.write = oldWrite
698 },
699 captured: () => buf
700 }
701 }
702 beforeEach(() => {
703 hook = captureStream(process.stdout)
704 })
705 afterEach(() => {
706 hook.unhook()
707 if (fs.existsSync(postInstallScriptPath)) {
708 fs.unlinkSync(postInstallScriptPath)
709 }
710 })
711
712 it('should not throw any errors if no script', () => {
713 return lambda._postInstallScript(program, codeDirectory).then((dummy) => {
714 assert.isUndefined(dummy)
715 })
716 })
717
718 it('should throw any errors if script fails', () => {
719 fs.writeFileSync(postInstallScriptPath, '___fails___')
720 return lambda._postInstallScript(program, codeDirectory).catch((err) => {
721 assert.instanceOf(err, Error)
722 assert.match(err.message, /^Error: Command failed:/)
723 })
724 })
725
726 it('running script gives expected output', () => {
727 fs.writeFileSync(
728 postInstallScriptPath,
729 fs.readFileSync(path.join('test', 'post_install.sh'))
730 )
731 fs.chmodSync(path.join(codeDirectory, 'post_install.sh'), '755')
732 return lambda._postInstallScript(program, codeDirectory).then((dummy) => {
733 assert.isUndefined(dummy)
734 }).catch((err) => {
735 assert.isNull(err)
736 assert.equal(
737 `=> Running post install script post_install.sh\n\t\tYour environment is ${program.environment}\n`,
738 hook.captured()
739 )
740 })
741 })
742 })
743
744 describe('_zip', () => {
745 beforeEach(function () {
746 _timeout({ this: this, sec: 30 }) // give it time to build the node modules
747 return Promise.resolve().then(() => {
748 return lambda._cleanDirectory(codeDirectory)
749 }).then(() => {
750 return lambda._fileCopy(program, '.', codeDirectory, true)
751 }).then(() => {
752 return lambda._npmInstall(program, codeDirectory)
753 }).then(() => {
754 if (process.platform !== 'win32') {
755 fs.symlinkSync(
756 path.join(__dirname, '..', 'bin', 'node-lambda'),
757 path.join(codeDirectory, 'node-lambda-link')
758 )
759 }
760 })
761 })
762
763 it('Compress the file. `index.js` and `bin/node-lambda` are included and the permission is also preserved.', function () {
764 _timeout({ this: this, sec: 30 }) // give it time to zip
765
766 return lambda._zip(program, codeDirectory).then((data) => {
767 const archive = new Zip(data)
768 assert.include(archive.files['index.js'].name, 'index.js')
769 assert.include(archive.files['bin/node-lambda'].name, 'bin/node-lambda')
770
771 if (process.platform !== 'win32') {
772 const indexJsStat = fs.lstatSync('index.js')
773 const binNodeLambdaStat = fs.lstatSync(path.join('bin', 'node-lambda'))
774 assert.equal(
775 archive.files['index.js'].unixPermissions,
776 indexJsStat.mode
777 )
778 assert.equal(
779 archive.files['bin/node-lambda'].unixPermissions,
780 binNodeLambdaStat.mode
781 )
782
783 // isSymbolicLink
784 assert.include(archive.files['node-lambda-link'].name, 'node-lambda-link')
785 assert.equal(
786 archive.files['node-lambda-link'].unixPermissions & fs.constants.S_IFMT,
787 fs.constants.S_IFLNK
788 )
789 }
790 })
791 })
792 })
793
794 describe('_archive', () => {
795 // archive.files's name is a slash delimiter regardless of platform.
796 it('installs and zips with an index.js file and node_modules/aws-sdk (It is also a test of `_buildAndArchive`)', function () {
797 _timeout({ this: this, sec: 30 }) // give it time to zip
798
799 return lambda._archive(program).then((data) => {
800 const archive = new Zip(data)
801 const contents = Object.keys(archive.files).map((k) => {
802 return archive.files[k].name.toString()
803 })
804 assert.include(contents, 'index.js')
805 assert.include(contents, 'node_modules/aws-sdk/lib/aws.js')
806 })
807 })
808
809 it('packages a prebuilt module without installing (It is also a test of `_archivePrebuilt`)', function () {
810 _timeout({ this: this, sec: 30 }) // give it time to zip
811 const buildDir = '.build_' + Date.now()
812 after(() => fs.removeSync(buildDir))
813
814 fs.mkdirSync(buildDir)
815 fs.mkdirSync(path.join(buildDir, 'd'))
816 fs.mkdirSync(path.join(buildDir, 'node_modules'))
817 fs.writeFileSync(path.join(buildDir, 'node_modules', 'a'), '...')
818 fs.writeFileSync(path.join(buildDir, 'testa'), '...')
819 fs.writeFileSync(path.join(buildDir, 'd', 'testb'), '...')
820
821 program.prebuiltDirectory = buildDir
822 return lambda._archive(program).then((data) => {
823 const archive = new Zip(data)
824 const contents = Object.keys(archive.files).map((k) => {
825 return archive.files[k].name.toString()
826 });
827 [
828 'testa',
829 'd/testb',
830 'node_modules/a'
831 ].forEach((needle) => {
832 assert.include(contents, needle, `Target: "${needle}"`)
833 })
834 })
835 })
836 })
837
838 describe('_readArchive', () => {
839 const testZipFile = path.join(os.tmpdir(), 'node-lambda-test.zip')
840 let bufferExpected = null
841 before(function () {
842 _timeout({ this: this, sec: 30 }) // give it time to zip
843
844 return lambda._zip(program, codeDirectory).then((data) => {
845 bufferExpected = data
846 fs.writeFileSync(testZipFile, data)
847 })
848 })
849
850 after(() => fs.unlinkSync(testZipFile))
851
852 it('_readArchive fails (undefined)', () => {
853 return lambda._readArchive(program).then((data) => {
854 assert.isUndefined(data)
855 }).catch((err) => {
856 assert.instanceOf(err, Error)
857 assert.equal(err.message, 'No such Zipfile [undefined]')
858 })
859 })
860
861 it('_readArchive fails (does not exists file)', () => {
862 const filePath = path.join(path.resolve('/aaaa'), 'bbbb')
863 const _program = Object.assign({ deployZipfile: filePath }, program)
864 return lambda._readArchive(_program).then((data) => {
865 assert.isUndefined(data)
866 }).catch((err) => {
867 assert.instanceOf(err, Error)
868 assert.equal(err.message, `No such Zipfile [${filePath}]`)
869 })
870 })
871
872 it('_readArchive reads the contents of the zipfile', () => {
873 const _program = Object.assign({ deployZipfile: testZipFile }, program)
874 return lambda._readArchive(_program).then((data) => {
875 assert.deepEqual(data, bufferExpected)
876 })
877 })
878
879 describe('If value is set in `deployZipfile`, _readArchive is executed in _archive', () => {
880 it('`deployZipfile` is a invalid value. Process from creation of zip file', function () {
881 const filePath = path.join(path.resolve('/aaaa'), 'bbbb')
882 const _program = Object.assign({ deployZipfile: filePath }, program)
883 _timeout({ this: this, sec: 30 }) // give it time to zip
884 return lambda._archive(_program).then((data) => {
885 // same test as "installs and zips with an index.js file and node_modules/aws-sdk"
886 const archive = new Zip(data)
887 const contents = Object.keys(archive.files).map((k) => {
888 return archive.files[k].name.toString()
889 })
890 assert.include(contents, 'index.js')
891 assert.include(contents, 'node_modules/aws-sdk/lib/aws.js')
892 })
893 })
894
895 it('`deployZipfile` is a valid value._archive reads the contents of the zipfile', () => {
896 const _program = Object.assign({ deployZipfile: testZipFile }, program)
897 return lambda._archive(_program).then((data) => {
898 assert.deepEqual(data, bufferExpected)
899 })
900 })
901 })
902 })
903
904 describe('environment variable injection at runtime', () => {
905 beforeEach(() => {
906 // Prep...
907 fs.writeFileSync('tmp.env', 'FOO=bar\nBAZ=bing\n')
908 })
909
910 afterEach(() => fs.unlinkSync('tmp.env'))
911
912 it('should inject environment variables at runtime', () => {
913 // Run it...
914 lambda._setRunTimeEnvironmentVars({
915 configFile: 'tmp.env'
916 }, process.cwd())
917
918 assert.equal(process.env.FOO, 'bar')
919 assert.equal(process.env.BAZ, 'bing')
920 })
921 })
922
923 describe('create sample files', () => {
924 const targetFiles = [
925 '.env',
926 'context.json',
927 'event.json',
928 'deploy.env',
929 'event_sources.json'
930 ]
931
932 after(() => {
933 targetFiles.forEach((file) => fs.unlinkSync(file))
934 program.eventSourceFile = ''
935 })
936
937 it('should create sample files', () => {
938 lambda.setup(program)
939
940 const libPath = path.join(__dirname, '..', 'lib')
941 targetFiles.forEach((targetFile) => {
942 const boilerplateFile = path.join(libPath, `${targetFile}.example`)
943
944 assert.equal(
945 fs.readFileSync(targetFile).toString(),
946 fs.readFileSync(boilerplateFile).toString(),
947 targetFile
948 )
949 })
950 })
951
952 describe('_eventSourceList', () => {
953 it('program.eventSourceFile is empty value', () => {
954 program.eventSourceFile = ''
955 assert.deepEqual(
956 lambda._eventSourceList(program),
957 {
958 EventSourceMappings: null,
959 ScheduleEvents: null,
960 S3Events: null
961 }
962 )
963 })
964
965 it('program.eventSourceFile is invalid value', () => {
966 const dirPath = path.join(path.resolve('/hoge'), 'fuga')
967 program.eventSourceFile = dirPath
968 assert.throws(
969 () => { lambda._eventSourceList(program) },
970 Error,
971 `ENOENT: no such file or directory, open '${dirPath}'`
972 )
973 })
974
975 describe('program.eventSourceFile is valid value', () => {
976 before(() => {
977 fs.writeFileSync('only_EventSourceMappings.json', JSON.stringify({
978 EventSourceMappings: [{ test: 1 }]
979 }))
980 fs.writeFileSync('only_ScheduleEvents.json', JSON.stringify({
981 ScheduleEvents: [{ test: 2 }]
982 }))
983 fs.writeFileSync('only_S3Events.json', JSON.stringify({
984 S3Events: [{ test: 3 }]
985 }))
986 })
987
988 after(() => {
989 fs.unlinkSync('only_EventSourceMappings.json')
990 fs.unlinkSync('only_ScheduleEvents.json')
991 fs.unlinkSync('only_S3Events.json')
992 })
993
994 it('only EventSourceMappings', () => {
995 program.eventSourceFile = 'only_EventSourceMappings.json'
996 const expected = {
997 EventSourceMappings: [{ test: 1 }],
998 ScheduleEvents: [],
999 S3Events: []
1000 }
1001 assert.deepEqual(lambda._eventSourceList(program), expected)
1002 })
1003
1004 it('only ScheduleEvents', () => {
1005 program.eventSourceFile = 'only_ScheduleEvents.json'
1006 const expected = {
1007 EventSourceMappings: [],
1008 ScheduleEvents: [{ test: 2 }],
1009 S3Events: []
1010 }
1011 assert.deepEqual(lambda._eventSourceList(program), expected)
1012 })
1013
1014 it('only S3Events', () => {
1015 program.eventSourceFile = 'only_S3Events.json'
1016 const expected = {
1017 EventSourceMappings: [],
1018 ScheduleEvents: [],
1019 S3Events: [{ test: 3 }]
1020 }
1021 assert.deepEqual(lambda._eventSourceList(program), expected)
1022 })
1023
1024 it('EventSourceMappings & ScheduleEvents', () => {
1025 program.eventSourceFile = 'event_sources.json'
1026 const expected = {
1027 EventSourceMappings: [{
1028 BatchSize: 100,
1029 Enabled: true,
1030 EventSourceArn: 'your event source arn',
1031 StartingPosition: 'LATEST'
1032 }],
1033 ScheduleEvents: [{
1034 ScheduleName: 'node-lambda-test-schedule',
1035 ScheduleState: 'ENABLED',
1036 ScheduleExpression: 'rate(1 hour)',
1037 Input: {
1038 key1: 'value',
1039 key2: 'value'
1040 }
1041 }],
1042 S3Events: [{
1043 Bucket: 'BUCKET_NAME',
1044 Events: [
1045 's3:ObjectCreated:*'
1046 ],
1047 Filter: {
1048 Key: {
1049 FilterRules: [{
1050 Name: 'prefix',
1051 Value: 'STRING_VALUE'
1052 }]
1053 }
1054 }
1055 }]
1056 }
1057 assert.deepEqual(lambda._eventSourceList(program), expected)
1058 })
1059 })
1060
1061 describe('old style event_sources.json', () => {
1062 const oldStyleValue = [{
1063 BatchSize: 100,
1064 Enabled: true,
1065 EventSourceArn: 'your event source arn',
1066 StartingPosition: 'LATEST'
1067 }]
1068 const fileName = 'event_sources_old_style.json'
1069
1070 before(() => fs.writeFileSync(fileName, JSON.stringify(oldStyleValue)))
1071 after(() => fs.unlinkSync(fileName))
1072
1073 it('program.eventSourceFile is valid value', () => {
1074 program.eventSourceFile = fileName
1075 const expected = {
1076 EventSourceMappings: oldStyleValue,
1077 ScheduleEvents: [],
1078 S3Events: []
1079 }
1080 assert.deepEqual(lambda._eventSourceList(program), expected)
1081 })
1082 })
1083 })
1084 })
1085
1086 describe('_listEventSourceMappings', () => {
1087 it('simple test with mock', () => {
1088 return lambda._listEventSourceMappings(
1089 awsLambda,
1090 { FunctionName: 'test-func' }
1091 ).then((results) => {
1092 assert.deepEqual(
1093 results,
1094 lambdaMockSettings.listEventSourceMappings.EventSourceMappings
1095 )
1096 })
1097 })
1098 })
1099
1100 describe('_getStartingPosition', () => {
1101 it('null in SQS', () => {
1102 assert.isNull(lambda._getStartingPosition({
1103 EventSourceArn: 'arn:aws:sqs:us-east-1:sqs-queuename1'
1104 }))
1105 })
1106
1107 it('When there is no setting', () => {
1108 assert.equal(
1109 lambda._getStartingPosition({
1110 EventSourceArn: 'arn:aws:kinesis:test'
1111 }),
1112 'LATEST'
1113 )
1114 })
1115
1116 it('With StartingPosition', () => {
1117 assert.equal(
1118 lambda._getStartingPosition({
1119 EventSourceArn: 'arn:aws:kinesis:test',
1120 StartingPosition: 'test position'
1121 }),
1122 'test position'
1123 )
1124 })
1125 })
1126
1127 describe('_updateEventSources', () => {
1128 const eventSourcesJsonValue = {
1129 EventSourceMappings: [{
1130 EventSourceArn: lambdaMockSettings
1131 .listEventSourceMappings
1132 .EventSourceMappings[0]
1133 .EventSourceArn,
1134 StartingPosition: 'LATEST',
1135 BatchSize: 100,
1136 Enabled: true
1137 }]
1138 }
1139
1140 before(() => {
1141 fs.writeFileSync(
1142 'event_sources.json',
1143 JSON.stringify(eventSourcesJsonValue)
1144 )
1145 })
1146
1147 after(() => fs.unlinkSync('event_sources.json'))
1148
1149 it('program.eventSourceFile is empty value', () => {
1150 program.eventSourceFile = ''
1151 const eventSourceList = lambda._eventSourceList(program)
1152 return lambda._updateEventSources(
1153 awsLambda,
1154 '',
1155 [],
1156 eventSourceList.EventSourceMappings
1157 ).then((results) => {
1158 assert.deepEqual(results, [])
1159 })
1160 })
1161
1162 it('simple test with mock (In case of new addition)', () => {
1163 program.eventSourceFile = 'event_sources.json'
1164 const eventSourceList = lambda._eventSourceList(program)
1165 return lambda._updateEventSources(
1166 awsLambda,
1167 'functionName',
1168 [],
1169 eventSourceList.EventSourceMappings
1170 ).then((results) => {
1171 assert.deepEqual(results, [lambdaMockSettings.createEventSourceMapping])
1172 })
1173 })
1174
1175 it('simple test with mock (In case of deletion)', () => {
1176 return lambda._updateEventSources(
1177 awsLambda,
1178 'functionName',
1179 lambdaMockSettings.listEventSourceMappings.EventSourceMappings,
1180 {}
1181 ).then((results) => {
1182 assert.deepEqual(results, [lambdaMockSettings.deleteEventSourceMapping])
1183 })
1184 })
1185
1186 it('simple test with mock (In case of update)', () => {
1187 program.eventSourceFile = 'event_sources.json'
1188 const eventSourceList = lambda._eventSourceList(program)
1189 return lambda._updateEventSources(
1190 awsLambda,
1191 'functionName',
1192 lambdaMockSettings.listEventSourceMappings.EventSourceMappings,
1193 eventSourceList.EventSourceMappings
1194 ).then((results) => {
1195 assert.deepEqual(results, [lambdaMockSettings.updateEventSourceMapping])
1196 })
1197 })
1198 })
1199
1200 describe('_updateScheduleEvents', () => {
1201 const ScheduleEvents = require(path.join('..', 'lib', 'schedule_events'))
1202 const eventSourcesJsonValue = {
1203 ScheduleEvents: [{
1204 ScheduleName: 'node-lambda-test-schedule',
1205 ScheduleState: 'ENABLED',
1206 ScheduleExpression: 'rate(1 hour)',
1207 ScheduleDescription: 'Run node-lambda-test-function once per hour'
1208 }]
1209 }
1210
1211 let schedule = null
1212
1213 before(() => {
1214 fs.writeFileSync(
1215 'event_sources.json',
1216 JSON.stringify(eventSourcesJsonValue)
1217 )
1218 schedule = new ScheduleEvents(aws)
1219 })
1220
1221 after(() => fs.unlinkSync('event_sources.json'))
1222
1223 it('program.eventSourceFile is empty value', () => {
1224 program.eventSourceFile = ''
1225 const eventSourceList = lambda._eventSourceList(program)
1226 return lambda._updateScheduleEvents(
1227 schedule,
1228 '',
1229 eventSourceList.ScheduleEvents
1230 ).then((results) => {
1231 assert.deepEqual(results, [])
1232 })
1233 })
1234
1235 it('simple test with mock', () => {
1236 program.eventSourceFile = 'event_sources.json'
1237 const eventSourceList = lambda._eventSourceList(program)
1238 const functionArn = 'arn:aws:lambda:us-west-2:XXX:function:node-lambda-test-function'
1239 return lambda._updateScheduleEvents(
1240 schedule,
1241 functionArn,
1242 eventSourceList.ScheduleEvents
1243 ).then((results) => {
1244 const expected = [Object.assign(
1245 eventSourcesJsonValue.ScheduleEvents[0],
1246 { FunctionArn: functionArn }
1247 )]
1248 assert.deepEqual(results, expected)
1249 })
1250 })
1251 })
1252
1253 describe('_updateS3Events', () => {
1254 const S3Events = require(path.join('..', 'lib', 's3_events'))
1255 const eventSourcesJsonValue = {
1256 S3Events: [{
1257 Bucket: 'node-lambda-test-bucket',
1258 Events: ['s3:ObjectCreated:*'],
1259 Filter: null
1260 }]
1261 }
1262
1263 let s3Events = null
1264
1265 before(() => {
1266 fs.writeFileSync(
1267 'event_sources.json',
1268 JSON.stringify(eventSourcesJsonValue)
1269 )
1270 s3Events = new S3Events(aws)
1271 })
1272
1273 after(() => fs.unlinkSync('event_sources.json'))
1274
1275 it('program.eventSourceFile is empty value', () => {
1276 program.eventSourceFile = ''
1277 const eventSourceList = lambda._eventSourceList(program)
1278 return lambda._updateS3Events(
1279 s3Events,
1280 '',
1281 eventSourceList.S3Events
1282 ).then(results => {
1283 assert.deepEqual(results, [])
1284 })
1285 })
1286
1287 it('simple test with mock', () => {
1288 program.eventSourceFile = 'event_sources.json'
1289 const eventSourceList = lambda._eventSourceList(program)
1290 const functionArn = 'arn:aws:lambda:us-west-2:XXX:function:node-lambda-test-function'
1291 return lambda._updateS3Events(
1292 s3Events,
1293 functionArn,
1294 eventSourceList.S3Events
1295 ).then(results => {
1296 const expected = [Object.assign(
1297 eventSourcesJsonValue.S3Events[0],
1298 { FunctionArn: functionArn }
1299 )]
1300 assert.deepEqual(results, expected)
1301 })
1302 })
1303 })
1304
1305 describe('_uploadNew', () => {
1306 it('simple test with mock', () => {
1307 const params = lambda._params(program, null)
1308 return lambda._uploadNew(awsLambda, params, (results) => {
1309 assert.deepEqual(results, lambdaMockSettings.createFunction)
1310 })
1311 })
1312 })
1313
1314 describe('_uploadExisting', () => {
1315 it('simple test with mock', () => {
1316 const params = lambda._params(program, null)
1317 return lambda._uploadExisting(awsLambda, params).then((results) => {
1318 assert.deepEqual(results, lambdaMockSettings.updateFunctionConfiguration)
1319 })
1320 })
1321 })
1322
1323 describe('_setLogsRetentionPolicy', () => {
1324 const CloudWatchLogs = require(path.join('..', 'lib', 'cloudwatch_logs'))
1325 it('simple test with mock', () => {
1326 const params = lambda._params(program, null)
1327 return lambda._setLogsRetentionPolicy(
1328 new CloudWatchLogs(aws),
1329 program,
1330 params.FunctionName
1331 ).then((results) => {
1332 assert.deepEqual(results, { retentionInDays: program.retentionInDays })
1333 })
1334 })
1335 })
1336
1337 describe('check env vars before create sample files', () => {
1338 const filesCreatedBySetup = [
1339 '.env',
1340 'deploy.env',
1341 'event_sources.json'
1342 ]
1343
1344 beforeEach(() => {
1345 fs.writeFileSync('newContext.json', '{"FOO"="bar"\n"BAZ"="bing"\n}')
1346 fs.writeFileSync('newEvent.json', '{"FOO"="bar"}')
1347 })
1348
1349 afterEach(() => {
1350 fs.unlinkSync('newContext.json')
1351 fs.unlinkSync('newEvent.json')
1352 filesCreatedBySetup.forEach((file) => fs.unlinkSync(file))
1353 })
1354
1355 it('should use existing sample files', () => {
1356 program.eventFile = 'newEvent.json'
1357 program.contextFile = 'newContext.json'
1358
1359 lambda.setup(program)
1360
1361 assert.equal(fs.readFileSync('newContext.json').toString(), '{"FOO"="bar"\n"BAZ"="bing"\n}')
1362 assert.equal(fs.readFileSync('newEvent.json').toString(), '{"FOO"="bar"}')
1363
1364 const libPath = path.join(__dirname, '..', 'lib')
1365 filesCreatedBySetup.forEach((targetFile) => {
1366 const boilerplateFile = path.join(libPath, `${targetFile}.example`)
1367
1368 assert.equal(
1369 fs.readFileSync(targetFile).toString(),
1370 fs.readFileSync(boilerplateFile).toString(),
1371 targetFile
1372 )
1373 })
1374 })
1375 })
1376
1377 describe('Lambda.prototype._deployToRegion()', () => {
1378 it('simple test with mock', () => {
1379 const params = lambda._params(program, null)
1380 return lambda._deployToRegion(program, params, 'us-east-1').then((result) => {
1381 assert.deepEqual(
1382 result,
1383 [
1384 [[], [], []],
1385 [],
1386 { retentionInDays: 30 }
1387 ]
1388 )
1389 })
1390 })
1391 })
1392
1393 describe('Lambda.prototype.deploy()', () => {
1394 it('simple test with mock', function () {
1395 _timeout({ this: this, sec: 30 }) // give it time to zip
1396 return lambda.deploy(program).then((result) => {
1397 assert.isUndefined(result)
1398 })
1399 })
1400 })
1401
1402 describe('Lambda.prototype._updateTags()', () => {
1403 it('simple test with mock', () => {
1404 return lambda._updateTags(
1405 awsLambda,
1406 'arn:aws:lambda:eu-central-1:1234567:function:test',
1407 { tagKey: 'tagValue' }).then((result) => {
1408 assert.deepEqual(
1409 result, {}
1410 )
1411 })
1412 })
1413 })
1414})