Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] Add a default http handler to Skill #34

Open
wants to merge 1 commit into
base: package-refactor-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 1 addition & 16 deletions customskill/examples/http.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package main

import (
"bytes"
"io"
"log"
"net/http"

"github.com/mikeflynn/go-alexa/customskill"
Expand All @@ -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)
}

Expand Down
20 changes: 20 additions & 0 deletions customskill/skill.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
108 changes: 107 additions & 1 deletion customskill/skill_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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: <nil>", 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: <nil>", 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 {
Expand Down