serverless-http-invoker 2.0.0-beta.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -5,6 +5,9 @@ const expect = require("chai").expect
|
|
|
5
5
|
const ServerlessInvoker = require("../../index")
|
|
6
6
|
|
|
7
7
|
describe("basic", function () {
|
|
8
|
+
// in serverless v3 these started taking longer than the default 2000ms
|
|
9
|
+
this.timeout(2500)
|
|
10
|
+
|
|
8
11
|
let sls = null
|
|
9
12
|
beforeEach(function () {
|
|
10
13
|
sls = new ServerlessInvoker(path.join(__dirname))
|
|
@@ -4,167 +4,173 @@ const path = require("path")
|
|
|
4
4
|
const expect = require("chai").expect
|
|
5
5
|
const ServerlessInvoker = require("../../index")
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return expect(response).to.eventually.have.property("statusCode", 200)
|
|
17
|
-
})
|
|
7
|
+
describe("comprehensive", function () {
|
|
8
|
+
let sls = null
|
|
9
|
+
// in serverless v3 these started taking longer than the default 2000ms
|
|
10
|
+
this.timeout(2500)
|
|
11
|
+
|
|
12
|
+
beforeEach(function () {
|
|
13
|
+
process.chdir(path.join(__dirname))
|
|
14
|
+
sls = new ServerlessInvoker(path.join(__dirname))
|
|
15
|
+
})
|
|
18
16
|
|
|
19
|
-
it("should
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
})
|
|
17
|
+
it("should work with callbacks too", function () {
|
|
18
|
+
const response = sls.invoke("GET api/callback")
|
|
19
|
+
return expect(response).to.eventually.have.property("statusCode", 200)
|
|
20
|
+
})
|
|
23
21
|
|
|
24
|
-
it("should invoke path with
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
22
|
+
it("should invoke path with params", function () {
|
|
23
|
+
const response = sls.invoke("GET api/hello/world")
|
|
24
|
+
return expect(response).to.eventually.have.property("statusCode", 200)
|
|
25
|
+
})
|
|
28
26
|
|
|
29
|
-
it("should invoke path with
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
27
|
+
it("should invoke path with shorthand", function () {
|
|
28
|
+
const response = sls.invoke("GET /api/shorthand")
|
|
29
|
+
return expect(response).to.eventually.have.property("statusCode", 200)
|
|
30
|
+
})
|
|
33
31
|
|
|
34
|
-
it("should
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"body.message",
|
|
39
|
-
"Go Serverless v1.0! Your function executed successfully!"
|
|
40
|
-
)
|
|
41
|
-
})
|
|
32
|
+
it("should invoke path with multiple params", function () {
|
|
33
|
+
const response = sls.invoke("GET /api/res1/1111/res2/2222")
|
|
34
|
+
return expect(response).to.eventually.have.property("statusCode", 200)
|
|
35
|
+
})
|
|
42
36
|
|
|
43
|
-
it("should
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})
|
|
37
|
+
it("should parse json response body", function () {
|
|
38
|
+
const response = sls.invoke("GET api/hello")
|
|
39
|
+
expect(response).to.eventually.have.property("statusCode", 200)
|
|
40
|
+
return expect(response).to.eventually.have.deep.nested.property(
|
|
41
|
+
"body.message",
|
|
42
|
+
"Go Serverless v1.0! Your function executed successfully!"
|
|
43
|
+
)
|
|
44
|
+
})
|
|
51
45
|
|
|
52
|
-
it("should
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
it("should load environment", function () {
|
|
47
|
+
const response = sls.invoke("GET api/env")
|
|
48
|
+
expect(response).to.eventually.have.property("statusCode", 200)
|
|
49
|
+
return expect(response).to.eventually.have.deep.nested.property(
|
|
50
|
+
"body.message",
|
|
51
|
+
"process.env.MY_SIMPLE==simple value"
|
|
52
|
+
)
|
|
56
53
|
})
|
|
57
|
-
})
|
|
58
54
|
|
|
59
|
-
it("should pass
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
res1ID: "xxx",
|
|
64
|
-
res2ID: "yyy",
|
|
55
|
+
it("should pass data to POST", function () {
|
|
56
|
+
const response = sls.invoke("POST api/postit", { body: "boo" })
|
|
57
|
+
return expect(response).to.eventually.have.deep.property("body", {
|
|
58
|
+
message: "postit:boo",
|
|
65
59
|
})
|
|
66
60
|
})
|
|
67
|
-
})
|
|
68
61
|
|
|
69
|
-
it("should pass pathParameters
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
res1ID: "xxx",
|
|
77
|
-
res2ID: "yyy",
|
|
62
|
+
it("should pass pathParameters with values when present", function () {
|
|
63
|
+
const response = sls.invoke("GET /api/res1/xxx/res2/yyy")
|
|
64
|
+
return response.then((resp) => {
|
|
65
|
+
return expect(resp.body.input).to.have.deep.property("pathParameters", {
|
|
66
|
+
res1ID: "xxx",
|
|
67
|
+
res2ID: "yyy",
|
|
68
|
+
})
|
|
78
69
|
})
|
|
79
|
-
expect(resp.body.input).to.have.property("requestPayload", "boo")
|
|
80
70
|
})
|
|
81
|
-
})
|
|
82
71
|
|
|
83
|
-
it("should pass pathParameters
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
it("should pass pathParameters along with existing event too", function () {
|
|
73
|
+
const response = sls.invoke("GET /api/res1/xxx/res2/yyy", {
|
|
74
|
+
requestPayload: "boo",
|
|
75
|
+
})
|
|
76
|
+
return response.then((resp) => {
|
|
77
|
+
expect(resp.body).to.have.property("input")
|
|
78
|
+
expect(resp.body.input).to.have.deep.property("pathParameters", {
|
|
79
|
+
res1ID: "xxx",
|
|
80
|
+
res2ID: "yyy",
|
|
81
|
+
})
|
|
82
|
+
expect(resp.body.input).to.have.property("requestPayload", "boo")
|
|
83
|
+
})
|
|
87
84
|
})
|
|
88
|
-
})
|
|
89
85
|
|
|
90
|
-
it("should pass
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
money: "blah/blah/blah",
|
|
86
|
+
it("should pass pathParameters empty when not present", function () {
|
|
87
|
+
const response = sls.invoke("GET api/hello")
|
|
88
|
+
return response.then((resp) => {
|
|
89
|
+
return expect(resp.body.input).to.have.deep.property("pathParameters", {})
|
|
95
90
|
})
|
|
96
91
|
})
|
|
97
|
-
})
|
|
98
92
|
|
|
99
|
-
it("should pass
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
p2: "val2",
|
|
107
|
-
}
|
|
108
|
-
)
|
|
93
|
+
it("should pass greedy path params", function () {
|
|
94
|
+
const response = sls.invoke("GET api/greedy/blah/blah/blah")
|
|
95
|
+
return response.then((resp) => {
|
|
96
|
+
return expect(resp.body.input).to.have.deep.property("pathParameters", {
|
|
97
|
+
money: "blah/blah/blah",
|
|
98
|
+
})
|
|
99
|
+
})
|
|
109
100
|
})
|
|
110
|
-
})
|
|
111
101
|
|
|
112
|
-
it("should pass queryStringParameters
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return expect(resp.body.input).to.have.deep.property(
|
|
116
|
-
"queryStringParameters",
|
|
117
|
-
{}
|
|
102
|
+
it("should pass queryStringParameters with values when present", function () {
|
|
103
|
+
const response = sls.invoke(
|
|
104
|
+
"GET api/with_querystring_params?p1=val1&p2=val2"
|
|
118
105
|
)
|
|
106
|
+
return response.then((resp) => {
|
|
107
|
+
return expect(resp.body.input).to.have.deep.property(
|
|
108
|
+
"queryStringParameters",
|
|
109
|
+
{
|
|
110
|
+
p1: "val1",
|
|
111
|
+
p2: "val2",
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
})
|
|
119
115
|
})
|
|
120
|
-
})
|
|
121
116
|
|
|
122
|
-
it("should
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
117
|
+
it("should pass queryStringParameters even when not present", function () {
|
|
118
|
+
const response = sls.invoke("GET api/with_querystring_params")
|
|
119
|
+
return response.then((resp) => {
|
|
120
|
+
return expect(resp.body.input).to.have.deep.property(
|
|
121
|
+
"queryStringParameters",
|
|
122
|
+
{}
|
|
123
|
+
)
|
|
129
124
|
})
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("should match urls with query strings and path params in url", function () {
|
|
128
|
+
const response = sls.invoke(
|
|
129
|
+
"GET api/with_querystring_params_and_pathparams/ppvalue?qs1=qsval1"
|
|
130
|
+
)
|
|
131
|
+
return response.then((resp) => {
|
|
132
|
+
expect(resp.body.input).to.have.deep.property("queryStringParameters", {
|
|
133
|
+
qs1: "qsval1",
|
|
134
|
+
})
|
|
135
|
+
return expect(resp.body.input).to.have.deep.property("pathParameters", {
|
|
136
|
+
pathparam1: "ppvalue",
|
|
137
|
+
})
|
|
132
138
|
})
|
|
133
139
|
})
|
|
134
|
-
})
|
|
135
140
|
|
|
136
|
-
it("should marshal raw lambda exceptions back as http responses", function () {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
})
|
|
141
|
+
it("should marshal raw lambda exceptions back as http responses", function () {
|
|
142
|
+
const response = sls.invoke("GET api/throwWorld")
|
|
143
|
+
return expect(response).to.eventually.have.property("statusCode", 502)
|
|
144
|
+
})
|
|
140
145
|
|
|
141
|
-
it("should marshal raw handled lambda errors back as http responses", function () {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
})
|
|
146
|
+
it("should marshal raw handled lambda errors back as http responses", function () {
|
|
147
|
+
const response = sls.invoke("GET api/errorWorld")
|
|
148
|
+
return expect(response).to.eventually.have.property("statusCode", 502)
|
|
149
|
+
})
|
|
145
150
|
|
|
146
|
-
it("should error if path isn't found", function () {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
})
|
|
151
|
+
it("should error if path isn't found", function () {
|
|
152
|
+
const response = sls.invoke("GET api/DOES_NOT_EXIST")
|
|
153
|
+
return expect(response).to.eventually.be.rejectedWith(
|
|
154
|
+
/^Serverless http event not found for HTTP request/
|
|
155
|
+
)
|
|
156
|
+
})
|
|
152
157
|
|
|
153
|
-
it("should try to find service path in same dir", function () {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
158
|
+
it("should try to find service path in same dir", function () {
|
|
159
|
+
process.chdir(path.join(__dirname))
|
|
160
|
+
const localSls = new ServerlessInvoker()
|
|
161
|
+
expect(localSls.servicePath).to.match(/examples\/comprehensive$/)
|
|
162
|
+
})
|
|
158
163
|
|
|
159
|
-
it("should try to find service path in parent dir", function () {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
})
|
|
164
|
+
it("should try to find service path in parent dir", function () {
|
|
165
|
+
process.chdir(path.join(__dirname, "./subdir"))
|
|
166
|
+
const localSls = new ServerlessInvoker()
|
|
167
|
+
expect(localSls.servicePath).to.match(/examples\/comprehensive$/)
|
|
168
|
+
})
|
|
164
169
|
|
|
165
|
-
it("should fail if serverless.yml not found", function () {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
it("should fail if serverless.yml not found", function () {
|
|
171
|
+
process.chdir(path.join(__dirname, "../../test-data/no-serverless-found"))
|
|
172
|
+
expect(() => new ServerlessInvoker()).to.throw(
|
|
173
|
+
/^Cannot find serverless.yml. Started search in working directory/
|
|
174
|
+
)
|
|
175
|
+
})
|
|
170
176
|
})
|
|
@@ -34,7 +34,7 @@ functions:
|
|
|
34
34
|
shorthand_path_syntax:
|
|
35
35
|
handler: handler.hello
|
|
36
36
|
events:
|
|
37
|
-
- http: GET api/shorthand
|
|
37
|
+
- http: GET /api/shorthand
|
|
38
38
|
|
|
39
39
|
malformed_http_event:
|
|
40
40
|
handler: handler.hello
|
|
@@ -46,7 +46,7 @@ functions:
|
|
|
46
46
|
path_with_multiple_params:
|
|
47
47
|
handler: handler.hello
|
|
48
48
|
events:
|
|
49
|
-
- http: GET api/res1/{res1ID}/res2/{res2ID}
|
|
49
|
+
- http: GET /api/res1/{res1ID}/res2/{res2ID}
|
|
50
50
|
|
|
51
51
|
env:
|
|
52
52
|
handler: handler.env
|
package/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
const path = require("path")
|
|
3
|
-
const
|
|
3
|
+
const { existsSync } = require("fs")
|
|
4
|
+
const fsPromises = require("fs/promises")
|
|
4
5
|
const Serverless = require("serverless")
|
|
5
|
-
const Promise = require("bluebird")
|
|
6
6
|
const { wrap } = require("lambda-wrapper")
|
|
7
7
|
const assert = require("assert")
|
|
8
8
|
const url = require("url")
|
|
9
9
|
const querystring = require("querystring")
|
|
10
|
+
const YAML = require("yaml")
|
|
10
11
|
|
|
11
12
|
class ServerlessInvoker {
|
|
12
13
|
/**
|
|
@@ -14,14 +15,14 @@ class ServerlessInvoker {
|
|
|
14
15
|
* @param {*string} servicePath Path to the directory containing the serverless file.
|
|
15
16
|
*/
|
|
16
17
|
constructor(servicePath) {
|
|
17
|
-
this.servicePath = servicePath ||
|
|
18
|
+
this.servicePath = servicePath || ServerlessInvoker.findServicePath()
|
|
18
19
|
this.serverless = null
|
|
19
20
|
this.serverlessEvents = null
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
findServicePath() {
|
|
23
|
+
static findServicePath() {
|
|
23
24
|
let dir = process.cwd()
|
|
24
|
-
while (dir !== "/" && !
|
|
25
|
+
while (dir !== "/" && !existsSync(path.join(dir, "serverless.yml"))) {
|
|
25
26
|
dir = path.dirname(dir)
|
|
26
27
|
}
|
|
27
28
|
if (dir === "/") {
|
|
@@ -32,14 +33,30 @@ class ServerlessInvoker {
|
|
|
32
33
|
return dir
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
static async loadServerlessYaml(path) {
|
|
37
|
+
const file = await fsPromises.readFile(path, "utf8")
|
|
38
|
+
return YAML.parse(file)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async initializeServerless() {
|
|
42
|
+
process.env.SLS_DEBUG = "*"
|
|
43
|
+
// Serverless v3 requires us to load the config manually: https://www.serverless.com/framework/docs/deprecations#serverless-constructor-service-configuration-dependency
|
|
44
|
+
// see also https://www.serverless.com/framework/docs/guides/upgrading-v3#low-level-changes
|
|
45
|
+
// https://www.serverless.com/framework/docs/deprecations#serverless-constructor-configcommands-and-configoptions-requirement
|
|
46
|
+
const slsService = await ServerlessInvoker.loadServerlessYaml(
|
|
47
|
+
path.join(this.servicePath, "serverless.yml")
|
|
48
|
+
)
|
|
36
49
|
const config = {
|
|
37
|
-
|
|
50
|
+
serviceDir: path.dirname(this.servicePath),
|
|
51
|
+
configurationFilename: "serverless.yml",
|
|
52
|
+
configuration: slsService,
|
|
53
|
+
commands: [],
|
|
54
|
+
options: {},
|
|
38
55
|
}
|
|
39
56
|
const sls = new Serverless(config)
|
|
40
57
|
// NOTE: I've seen sls.init() run very slowly; nearly 500ms!
|
|
41
58
|
return sls.init().then(() => {
|
|
42
|
-
|
|
59
|
+
sls.service.load().then(() => {
|
|
43
60
|
sls.service.setFunctionNames({})
|
|
44
61
|
sls.service.mergeArrays()
|
|
45
62
|
sls.service.validate()
|
|
@@ -54,7 +71,7 @@ class ServerlessInvoker {
|
|
|
54
71
|
* @param {*object} event The event that should be submitted to the http endpoint.
|
|
55
72
|
* @param {*object} context The context passed to the lambda function
|
|
56
73
|
*/
|
|
57
|
-
invoke(httpRequest, event, context) {
|
|
74
|
+
async invoke(httpRequest, event, context) {
|
|
58
75
|
// Read the serverless.yml file
|
|
59
76
|
return this.initializeServerless()
|
|
60
77
|
.then(() => this.loadServerlessEvents())
|
|
@@ -160,27 +177,22 @@ class ServerlessInvoker {
|
|
|
160
177
|
return myURL.pathname
|
|
161
178
|
}
|
|
162
179
|
|
|
163
|
-
loadServerlessEnvironment() {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return env
|
|
168
|
-
})
|
|
180
|
+
async loadServerlessEnvironment() {
|
|
181
|
+
let env = this.serverless.service.provider.environment
|
|
182
|
+
Object.assign(process.env, env)
|
|
183
|
+
return env
|
|
169
184
|
}
|
|
170
185
|
|
|
171
|
-
invokeWithLambdaWrapper(httpEvent, event, context) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
lambda = Promise.promisifyAll(lambda)
|
|
179
|
-
return lambda.runHandler(event, context || {})
|
|
180
|
-
})
|
|
186
|
+
async invokeWithLambdaWrapper(httpEvent, event, context) {
|
|
187
|
+
const handlerModule = require(path.join(
|
|
188
|
+
this.servicePath,
|
|
189
|
+
httpEvent.handlerPath
|
|
190
|
+
))
|
|
191
|
+
const lambda = wrap(handlerModule, { handler: httpEvent.handlerName })
|
|
192
|
+
return lambda.runHandler(event, context || {})
|
|
181
193
|
}
|
|
182
194
|
|
|
183
|
-
loadServerlessEvents() {
|
|
195
|
+
async loadServerlessEvents() {
|
|
184
196
|
let funcs = this.serverless.service
|
|
185
197
|
.getAllFunctions()
|
|
186
198
|
.map((fname) => {
|
|
@@ -281,7 +293,7 @@ class ServerlessInvoker {
|
|
|
281
293
|
events.push(e)
|
|
282
294
|
}
|
|
283
295
|
}
|
|
284
|
-
return
|
|
296
|
+
return events
|
|
285
297
|
}
|
|
286
298
|
}
|
|
287
299
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-http-invoker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Locally invoke Serverless functions via their HTTP event as specified in Serverless.yml for testing.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Scott Willeke <scott@willeke.com> (https:/scott.willeke.com/)",
|
|
@@ -40,10 +40,11 @@
|
|
|
40
40
|
"lint-fix": "prettier --write \"{,!(node_modules)/**/}*.{ts,tsx,js,jsx,md,yml,json,html}\""
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"lambda-wrapper": "^0.3.0"
|
|
43
|
+
"lambda-wrapper": "^0.3.0",
|
|
44
|
+
"yaml": "^2.1.1"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"serverless": "^
|
|
47
|
+
"serverless": "^3.22.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"chai": "^4.3.6",
|
|
@@ -52,6 +53,6 @@
|
|
|
52
53
|
"mocha": "^10.0.0",
|
|
53
54
|
"nyc": "^15.1.0",
|
|
54
55
|
"prettier": "^2.7.1",
|
|
55
|
-
"serverless": "^
|
|
56
|
+
"serverless": "^3.22.0"
|
|
56
57
|
}
|
|
57
58
|
}
|