@@ -13,6 +13,11 @@ import (
1313 "io/ioutil"
1414 "log"
1515 "net/http"
16+ "os"
17+ "os/exec"
18+ "path"
19+ "strconv"
20+ "strings"
1621 "testing"
1722 "time"
1823
@@ -35,12 +40,13 @@ var base64EncodedBodyRequest []byte
3540
3641func TestWrap (t * testing.T ) {
3742 for name , params := range map [string ]struct {
38- input []byte
39- handler http.HandlerFunc
40- expectStatus int
41- expectBody string
42- expectHeaders map [string ]string
43- expectCookies []string
43+ input []byte
44+ handler http.HandlerFunc
45+ detectContentType bool
46+ expectStatus int
47+ expectBody string
48+ expectHeaders map [string ]string
49+ expectCookies []string
4450 }{
4551 "hello" : {
4652 input : helloRequest ,
@@ -58,10 +64,8 @@ func TestWrap(t *testing.T) {
5864 encoder := json .NewEncoder (w )
5965 _ = encoder .Encode (struct { RequestQueryParams , Method any }{r .URL .Query (), r .Method })
6066 },
61- expectStatus : http .StatusTeapot ,
62- expectHeaders : map [string ]string {
63- "Hello" : "world1,world2" ,
64- },
67+ expectStatus : http .StatusTeapot ,
68+ expectHeaders : map [string ]string {"Hello" : "world1,world2" },
6569 expectCookies : []string {
6670 "yummy=cookie" ,
6771 "yummy=cake" ,
@@ -110,6 +114,13 @@ func TestWrap(t *testing.T) {
110114 handler : func (w http.ResponseWriter , r * http.Request ) {},
111115 expectStatus : http .StatusOK ,
112116 },
117+ "write status code only" : {
118+ input : helloRequest ,
119+ handler : func (w http.ResponseWriter , r * http.Request ) {
120+ w .WriteHeader (http .StatusAccepted )
121+ },
122+ expectStatus : http .StatusAccepted ,
123+ },
113124 "base64request" : {
114125 input : base64EncodedBodyRequest ,
115126 handler : func (w http.ResponseWriter , r * http.Request ) {
@@ -118,12 +129,58 @@ func TestWrap(t *testing.T) {
118129 expectStatus : http .StatusOK ,
119130 expectBody : "<idk/>" ,
120131 },
132+ "detect content type: write status code only" : {
133+ input : helloRequest ,
134+ handler : func (w http.ResponseWriter , r * http.Request ) {
135+ w .WriteHeader (http .StatusAccepted )
136+ },
137+ detectContentType : true ,
138+ expectStatus : http .StatusAccepted ,
139+ expectHeaders : map [string ]string {
140+ "Content-Type" : "application/octet-stream" ,
141+ },
142+ },
143+ "detect content type: empty handler" : {
144+ input : helloRequest ,
145+ handler : func (w http.ResponseWriter , r * http.Request ) {
146+ },
147+ detectContentType : true ,
148+ expectStatus : http .StatusOK ,
149+ expectHeaders : map [string ]string {
150+ "Content-Type" : "application/octet-stream" ,
151+ },
152+ },
153+ "detect content type: writes html" : {
154+ input : helloRequest ,
155+ handler : func (w http.ResponseWriter , r * http.Request ) {
156+ _ , _ = w .Write ([]byte ("<!DOCTYPE HTML><html></html>" ))
157+ },
158+ detectContentType : true ,
159+ expectBody : "<!DOCTYPE HTML><html></html>" ,
160+ expectStatus : http .StatusOK ,
161+ expectHeaders : map [string ]string {
162+ "Content-Type" : "text/html; charset=utf-8" ,
163+ },
164+ },
165+ "detect content type: writes zeros" : {
166+ input : helloRequest ,
167+ handler : func (w http.ResponseWriter , r * http.Request ) {
168+ _ , _ = w .Write ([]byte {0 , 0 , 0 , 0 , 0 })
169+ },
170+ detectContentType : true ,
171+ expectBody : "\x00 \x00 \x00 \x00 \x00 " ,
172+ expectStatus : http .StatusOK ,
173+ expectHeaders : map [string ]string {
174+ "Content-Type" : "application/octet-stream" ,
175+ },
176+ },
121177 } {
122178 t .Run (name , func (t * testing.T ) {
123179 handler := Wrap (params .handler )
124180 var req events.LambdaFunctionURLRequest
125181 require .NoError (t , json .Unmarshal (params .input , & req ))
126- res , err := handler (context .Background (), & req )
182+ ctx := context .WithValue (context .Background (), detectContentTypeContextKey {}, params .detectContentType )
183+ res , err := handler (ctx , & req )
127184 require .NoError (t , err )
128185 resultBodyBytes , err := ioutil .ReadAll (res )
129186 require .NoError (t , err )
@@ -155,3 +212,56 @@ func TestRequestContext(t *testing.T) {
155212 _ , err := handler (context .Background (), req )
156213 require .NoError (t , err )
157214}
215+
216+ func TestStartViaEmulator (t * testing.T ) {
217+ addr1 := "localhost:" + strconv .Itoa (6001 )
218+ addr2 := "localhost:" + strconv .Itoa (7001 )
219+ rieInvokeAPI := "http://" + addr1 + "/2015-03-31/functions/function/invocations"
220+ if _ , err := exec .LookPath ("aws-lambda-rie" ); err != nil {
221+ t .Skipf ("%v - install from https://github.com/aws/aws-lambda-runtime-interface-emulator/" , err )
222+ }
223+
224+ // compile our handler, it'll always run to timeout ensuring the SIGTERM is triggered by aws-lambda-rie
225+ testDir := t .TempDir ()
226+ handlerBuild := exec .Command ("go" , "build" , "-o" , path .Join (testDir , "lambdaurl.handler" ), "./testdata/lambdaurl.go" )
227+ handlerBuild .Stderr = os .Stderr
228+ handlerBuild .Stdout = os .Stderr
229+ require .NoError (t , handlerBuild .Run ())
230+
231+ // run the runtime interface emulator, capture the logs for assertion
232+ cmd := exec .Command ("aws-lambda-rie" , "--runtime-interface-emulator-address" , addr1 , "--runtime-api-address" , addr2 , "lambdaurl.handler" )
233+ cmd .Env = []string {
234+ "PATH=" + testDir ,
235+ "AWS_LAMBDA_FUNCTION_TIMEOUT=2" ,
236+ }
237+ cmd .Stderr = os .Stderr
238+ stdout , err := cmd .StdoutPipe ()
239+ require .NoError (t , err )
240+ var logs string
241+ done := make (chan interface {}) // closed on completion of log flush
242+ go func () {
243+ logBytes , err := ioutil .ReadAll (stdout )
244+ require .NoError (t , err )
245+ logs = string (logBytes )
246+ close (done )
247+ }()
248+ require .NoError (t , cmd .Start ())
249+ t .Cleanup (func () { _ = cmd .Process .Kill () })
250+
251+ // give a moment for the port to bind
252+ time .Sleep (500 * time .Millisecond )
253+
254+ client := & http.Client {Timeout : 5 * time .Second } // http client timeout to prevent case from hanging on aws-lambda-rie
255+ resp , err := client .Post (rieInvokeAPI , "application/json" , strings .NewReader ("{}" ))
256+ require .NoError (t , err )
257+ defer resp .Body .Close ()
258+ body , err := ioutil .ReadAll (resp .Body )
259+ assert .NoError (t , err )
260+
261+ expected := "{\" statusCode\" :200,\" headers\" :{\" Content-Type\" :\" text/html; charset=utf-8\" }}\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 <!DOCTYPE HTML>\n <html>\n <body>\n Hello World!\n </body>\n </html>\n "
262+ assert .Equal (t , expected , string (body ))
263+
264+ require .NoError (t , cmd .Process .Kill ()) // now ensure the logs are drained
265+ <- done
266+ t .Logf ("stdout:\n %s" , logs )
267+ }
0 commit comments