Skip to content

Commit

Permalink
Merge pull request #33 from quii/regex
Browse files Browse the repository at this point in the history
Regex
  • Loading branch information
quii committed Jan 21, 2016
2 parents 289f6d1 + 7be6ad3 commit 51eee1e
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 61 deletions.
11 changes: 11 additions & 0 deletions examples/regex.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
- name: Regex example
request:
uri: /hello/chris
regexuri: "\\/hello\\/[a-z]+"
method: GET
response:
code: 200
body: '{"message": "hello, there"}'
headers:
content-type: text/json
16 changes: 11 additions & 5 deletions mockingjay/fakeendpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ func (f *FakeEndpoint) String() string {
return fmt.Sprintf(fakeEndpointStringerFormat, f.Name, f.Request)
}

func (f FakeEndpoint) isValid() bool {
return f.Request.isValid() && f.Response.isValid()
func (f FakeEndpoint) isValid() error {
if reqError := f.Request.isValid(); reqError != nil {
return reqError
}
if !f.Response.isValid() {
return errResponseInvalid
}
return nil
}

var (
errInvalidConfigError = errors.New("Config YAML structure is invalid")
errResponseInvalid = errors.New("Response is not configured correctly")
)

func errDuplicateRequestsError(duplicates []string) error {
Expand All @@ -44,8 +50,8 @@ func NewFakeEndpoints(data []byte) (endpoints []FakeEndpoint, err error) {
}

for _, endPoint := range endpoints {
if !endPoint.isValid() {
return nil, errInvalidConfigError
if endpointErr := endPoint.isValid(); endpointErr != nil {
return nil, endpointErr
}
}

Expand Down
40 changes: 35 additions & 5 deletions mockingjay/fakeendpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ const testYAML = `
---
- name: Test endpoint
request:
uri: /hello
uri: /hello/chris
method: GET
regexuri: "\\/hello\\/[a-z]+"
headers:
content-type: application/json
body: foobar
Expand Down Expand Up @@ -55,7 +56,7 @@ func TestItCreatesAServerConfigFromYAML(t *testing.T) {
t.Error("There should be a name set for the endpoint")
}

if firstEndpoint.Request.URI != "/hello" {
if firstEndpoint.Request.URI != "/hello/chris" {
t.Error("Request URI was not properly set")
}

Expand Down Expand Up @@ -83,6 +84,10 @@ func TestItCreatesAServerConfigFromYAML(t *testing.T) {
t.Error("First endpoint doesnt define cdc preference so it should be enabled by default")
}

if firstEndpoint.Request.RegexURI == nil {
t.Error("First endpoint should have a regex defined")
}

endpoint2 := endpoints[1]

if endpoint2.Request.Method != "DELETE" {
Expand Down Expand Up @@ -143,7 +148,7 @@ func TestItReturnsAnErrorWhenStructureOfYAMLIsWrong(t *testing.T) {
t.Error("Expected an error to be returned because the YAML is bad")
}

if err != errInvalidConfigError {
if err != errEmptyURI {
t.Errorf("Expected YAML was invalid error actual: %v", err.Error())
}
}
Expand All @@ -164,7 +169,7 @@ func TestItReturnsAnErrorWhenYAMLIsIncomplete(t *testing.T) {
t.Error("Expected an error to be returned because the YAML has missing fields")
}

if err != errInvalidConfigError {
if err != errResponseInvalid {
t.Errorf("Expected YAML was incomplete error actual: %v", err.Error())
}

Expand Down Expand Up @@ -192,10 +197,35 @@ func TestItReturnsErrorWhenRequestsAreDuplicated(t *testing.T) {
_, err := NewFakeEndpoints([]byte(duplicatedRequest))

if err == nil {
t.Error("Expected an error to be returned for duplicated requests")
t.Fatal("Expected an error to be returned for duplicated requests")
}

if !strings.Contains(err.Error(), "duplicated") {
t.Error("Unexpeted error message", err)
}
}

const badRegex = `
---
-
name: "This doesn't make sense"
request:
method: GET
regexuri: "\\/hello\\/[a-z]+"
uri: /goodbye/chris
response:
body: WOOT
code: 200
`

func TestItReturnsErrorWhenRegexDoesntMatchURI(t *testing.T) {
_, err := NewFakeEndpoints([]byte(badRegex))

if err == nil {
t.Fatal("Expected an error to be returned")
}

if err != errBadRegex {
t.Error("Didnt get the correct type of error returned, expected", errBadRegex, "but got", err)
}
}
32 changes: 32 additions & 0 deletions mockingjay/regexYAML.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package mockingjay

import (
"fmt"
"regexp"
)

// RegexYAML allows you to work with regex fields in YAML
type RegexYAML struct {
regexp.Regexp
}

// UnmarshalYAML will unhmarshal a YAML field into regexp
func (r *RegexYAML) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringFromYAML string
err := unmarshal(&stringFromYAML)
if err != nil {
return err
}
reg, err := regexp.Compile(stringFromYAML)
if err != nil {
return err
}
r.Regexp = *reg
return nil
}

// MarshalJSON returns a string for the regex
func (r *RegexYAML) MarshalJSON() ([]byte, error) {
asString := fmt.Sprintf(`"%s"`, r.Regexp.String())
return []byte(asString), nil
}
26 changes: 26 additions & 0 deletions mockingjay/regexYAML_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package mockingjay

import (
"testing"

"gopkg.in/yaml.v2"
)

type testRegexDataType struct {
Regex *RegexYAML
}

func TestItCanUnmarshalRegex(t *testing.T) {
rawYAML := `regex: "\\/hello\\/[a-z]+"`

var d testRegexDataType
err := yaml.Unmarshal([]byte(rawYAML), &d)

if err != nil {
t.Error("Couldnt unmarshal regex from YAML", err)
}

if d.Regex == nil {
t.Error("Regex was not extracted from YAML")
}
}
52 changes: 46 additions & 6 deletions mockingjay/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,44 @@ package mockingjay

import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"reflect"
"strings"
)

// Request is a simplified version of a http.Request
type Request struct {
URI string
Method string
Headers map[string]string
Body string
URI string
RegexURI *RegexYAML
Method string
Headers map[string]string
Body string
}

func (r Request) isValid() bool {
return r.URI != "" && r.Method != ""
var (
errBadRegex = errors.New("A regex defined in the request does not pass against it's defined URI")
errEmptyURI = errors.New("Cannot have an empty URI")
errEmptyMethod = errors.New("Cannot have an empty HTTP method")
)

func (r Request) isValid() error {
regexPassed := r.RegexURI == nil || r.RegexURI.MatchString(r.URI)
if !regexPassed {
return errBadRegex
}

if r.URI == "" {
return errEmptyURI
}

if r.Method == "" {
return errEmptyMethod
}
return nil
}

// AsHTTPRequest tries to create a http.Request from a given baseURL
Expand Down Expand Up @@ -68,3 +89,22 @@ func (r Request) String() string {
func (r Request) hash() string {
return fmt.Sprintf("URI: %v | METHOD: %v | HEADERS: %v | BODY: %v", r.URI, r.Method, r.Headers, r.Body)
}

func requestMatches(a, b Request) bool {

headersOk := !(a.Headers != nil && !reflect.DeepEqual(a.Headers, b.Headers))
bodyOk := a.Body == "*" || a.Body == b.Body
urlOk := matchURI(a.URI, a.RegexURI, b.URI)
methodOk := a.Method == b.Method

return bodyOk && urlOk && methodOk && headersOk
}

func matchURI(serverURI string, serverRegex *RegexYAML, incomingURI string) bool {
if serverURI == incomingURI {
return true
} else if serverRegex != nil {
return serverRegex.MatchString(incomingURI)
}
return false
}
25 changes: 18 additions & 7 deletions mockingjay/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ func TestItCreatesHTTPRequests(t *testing.T) {
body := "Body body body"
baseURL := "http://localhost:1234"

mockingJayRequest := Request{uri, method, headers, body}
mockingJayRequest := Request{
URI: uri,
Method: method,
Headers: headers,
Body: body}

httpRequest, _ := mockingJayRequest.AsHTTPRequest(baseURL)

Expand All @@ -37,21 +41,28 @@ func TestItCreatesHTTPRequests(t *testing.T) {
}

func TestItValidatesRequests(t *testing.T) {
noURIRequest := Request{"", "POST", nil, ""}
noURIRequest := Request{
URI: "",
Method: "POST"}

if noURIRequest.isValid() {
if noURIRequest.isValid() != errEmptyURI {
t.Error("A request without a URI is seen as valid")
}

noMethodRequest := Request{"/", "", nil, ""}
noMethodRequest := Request{
URI: "/",
Method: ""}

if noMethodRequest.isValid() {
if noMethodRequest.isValid() != errEmptyMethod {
t.Error("A request without a method is seen as valid")
}

validRequest := Request{"/", "POST", nil, ""}
validRequest := Request{
URI: "/",
Method: "POST",
}

if !validRequest.isValid() {
if validRequest.isValid() != nil {
t.Error("A valid request is seen as not valid")
}
}
62 changes: 62 additions & 0 deletions mockingjay/requestmatch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package mockingjay

import (
"regexp"
"testing"
)

var (
incomingRequest = Request{
URI: "/hello/chris",
Method: "GET",
}
)

func TestMatchingWithRegex(t *testing.T) {

uriPathRegex, err := regexp.Compile(`\/hello\/[a-z]+`)
regexURI := &RegexYAML{Regexp: *uriPathRegex}

if err != nil {
t.Fatal(err)
}

serverConfig := Request{
URI: "/hello/world",
RegexURI: regexURI,
Method: "GET",
}

if !requestMatches(serverConfig, incomingRequest) {
t.Error("Requests didnt match when we expected them to", incomingRequest, serverConfig)
}
}

func TestItMatchesOnURL(t *testing.T) {
serverConfig := Request{
URI: "/hello/bob",
Method: "GET",
}

if requestMatches(serverConfig, incomingRequest) {
t.Error("Should not match", serverConfig, incomingRequest)
}
}

func TestItMatchesWildcardBodies(t *testing.T) {
incomingRequest := Request{
URI: "/x",
Method: "POST",
Body: "Doesn't matter",
}

serverConfig := Request{
URI: "/x",
Method: "POST",
Body: "*",
}

if !requestMatches(serverConfig, incomingRequest) {
t.Error("Expected wildcards to match", incomingRequest, serverConfig)
}
}
Loading

0 comments on commit 51eee1e

Please sign in to comment.