From 3729029045449f99c4ba45a5041222458a83073b Mon Sep 17 00:00:00 2001 From: "Harrison H. Jones" Date: Thu, 4 Jan 2018 08:34:14 -0800 Subject: [PATCH] Add a default http handler to Skill --- customskill/examples/http.go | 17 +----- customskill/skill.go | 20 +++++++ customskill/skill_test.go | 108 ++++++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 17 deletions(-) diff --git a/customskill/examples/http.go b/customskill/examples/http.go index 76c7a79..f4722e5 100644 --- a/customskill/examples/http.go +++ b/customskill/examples/http.go @@ -1,9 +1,6 @@ package main import ( - "bytes" - "io" - "log" "net/http" "github.com/mikeflynn/go-alexa/customskill" @@ -15,24 +12,12 @@ var ( s customskill.Skill ) -func handler(w http.ResponseWriter, r *http.Request) { - buf := bytes.NewBuffer(nil) - if _, err := io.Copy(buf, r.Body); err != nil { - log.Panicf("failed to copy request body to buffer: %v", err) - } - defer r.Body.Close() - if err := s.Handle(w, buf.Bytes()); err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - log.Printf("Got an error: %v", err) - } -} - func main() { s = customskill.Skill{ OnLaunch: onLaunch, } - http.HandleFunc("/", handler) + http.HandleFunc("/", s.DefaultHTTPHandler) http.ListenAndServe(":8080", nil) } diff --git a/customskill/skill.go b/customskill/skill.go index b0c3b6d..c162327 100644 --- a/customskill/skill.go +++ b/customskill/skill.go @@ -1,8 +1,10 @@ package customskill import ( + "bytes" "encoding/json" "io" + "net/http" "github.com/mikeflynn/go-alexa/customskill/request" "github.com/mikeflynn/go-alexa/customskill/response" @@ -12,6 +14,9 @@ import ( var ( requestBootstrapFromJSON = request.BootstrapFromJSON jsonMarshal = json.Marshal + ioCopy = io.Copy + httpError = http.Error + sHandle = func(s *Skill, w io.Writer, b []byte) error { return s.Handle(w, b) } ) // Handle parses a JSON payload, calls the appropriate request handler, serializes the response, and writes it to the provided writer. @@ -78,6 +83,21 @@ func (s *Skill) Handle(w io.Writer, b []byte) error { return nil } +func (s *Skill) DefaultHTTPHandler(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + buf := bytes.NewBuffer(nil) + if _, err := ioCopy(buf, r.Body); err != nil { + httpError(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + if err := sHandle(s, w, buf.Bytes()); err != nil { + httpError(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } +} + func (s *Skill) applicationIDIsValid(appID string) bool { for _, str := range s.ValidApplicationIDs { if str == appID { diff --git a/customskill/skill_test.go b/customskill/skill_test.go index 97be874..f7a4a5d 100644 --- a/customskill/skill_test.go +++ b/customskill/skill_test.go @@ -3,14 +3,16 @@ package customskill import ( "bytes" "encoding/json" + "errors" "io" "io/ioutil" + "net/http" + "net/http/httptest" "strings" "testing" "github.com/mikeflynn/go-alexa/customskill/request" "github.com/mikeflynn/go-alexa/customskill/response" - "github.com/pkg/errors" ) func TestSkill_Handle(t *testing.T) { @@ -416,6 +418,110 @@ func TestSkill_Handle(t *testing.T) { fmt.Printf("Written response: %s", buf.String())*/ } +func TestSkill_DefaultHTTPHandler(t *testing.T) { + checkCounter := 0 + var tests = []struct { + name string + checkNum int + w http.ResponseWriter + r *http.Request + ioCopy func(dst io.Writer, src io.Reader) (written int64, err error) + httpError func(w http.ResponseWriter, error string, code int) + sHandle func(s *Skill, w io.Writer, b []byte) error + }{ + { + name: "happy-path", + checkNum: 1, + sHandle: func(s *Skill, w io.Writer, b []byte) error { + checkCounter++ + got := string(b) + want := "happy-path" + if got != want { + t.Errorf("[happy-path] bytes written mismatch: got: %s, want: %s", got, want) + } + return nil + }, + r: httptest.NewRequest("", "http://domain.tld", bytes.NewReader([]byte("happy-path"))), + httpError: func(w http.ResponseWriter, error string, code int) { + t.Error("[happy-path] unexpected http.Error call") + }, + }, + { + name: "io-copy-error-sends-http-error", + checkNum: 1, + ioCopy: func(dst io.Writer, src io.Reader) (written int64, err error) { + return 0, errors.New("dummy error") + }, + r: httptest.NewRequest("", "http://domain.tld", bytes.NewReader(nil)), + httpError: func(w http.ResponseWriter, error string, code int) { + checkCounter++ + if w != nil { + t.Errorf("[io-copy-error-sends-http-error] http.Error w argument mismatch: got: %v, want: ", w) + } + if error != http.StatusText(http.StatusInternalServerError) { + t.Errorf("[io-copy-error-sends-http-error] http.Error error argument mismatch: got: %s, want: %s", error, http.StatusText(http.StatusInternalServerError)) + } + if code != http.StatusInternalServerError { + t.Errorf("[io-copy-error-sends-http-error] http.Error code argument mismatch: got: %d, want: %d", code, http.StatusInternalServerError) + } + }, + }, + { + name: "s-Handle-error-sends-http-error", + checkNum: 1, + ioCopy: func(dst io.Writer, src io.Reader) (written int64, err error) { + return 0, nil + }, + sHandle: func(s *Skill, w io.Writer, b []byte) error { + return errors.New("dummy error") + }, + r: httptest.NewRequest("", "http://domain.tld", bytes.NewReader(nil)), + httpError: func(w http.ResponseWriter, error string, code int) { + checkCounter++ + if w != nil { + t.Errorf("[s-Handle-error-sends-http-error] http.Error w argument mismatch: got: %v, want: ", w) + } + if error != http.StatusText(http.StatusInternalServerError) { + t.Errorf("[s-Handle-error-sends-http-error] http.Error error argument mismatch: got: %s, want: %s", error, http.StatusText(http.StatusInternalServerError)) + } + if code != http.StatusInternalServerError { + t.Errorf("[s-Handle-error-sends-http-error] http.Error code argument mismatch: got: %d, want: %d", code, http.StatusInternalServerError) + } + }, + }, + } + + for _, test := range tests { + // Override the helper functions for each test + if test.ioCopy != nil { + ioCopy = test.ioCopy + } + if test.httpError != nil { + httpError = test.httpError + } + if test.sHandle != nil { + sHandle = test.sHandle + } + + // Call the tested function + s := Skill{} + s.DefaultHTTPHandler(test.w, test.r) + + // Check to ensure the desired number of checks were performed + if checkCounter != test.checkNum { + t.Errorf("[%s] check counter mismatch: got: %d, want: %d", test.name, test.checkNum, checkCounter) + } + + // Reset the check counter + checkCounter = 0 + + // Restore the helper functions + ioCopy = io.Copy + httpError = http.Error + sHandle = func(s *Skill, w io.Writer, b []byte) error { return s.Handle(w, b) } + } +} + /* Test helper functions */ type badReadWriter struct {