From 00e7d7faf31ea7672a7b1c5357291831c296c7d3 Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Sat, 24 Mar 2018 11:36:06 +1100 Subject: [PATCH] feat(api): uplift API to support better matching BREAKING CHANGE: significant type modifications for the Request and Response bodies, to accommodate type safe matching. Request/Response types now accept union types to discriminate between true objects with Matchers, or primitive types. Introduction of dsl.String, dsl.StringMatcher, dsl.MapMatcher, and dsl.Matcher. Relateds to #73 --- README.md | 56 ++++---- doc.go | 49 +++---- dsl/interaction.go | 12 +- dsl/interaction_test.go | 4 +- dsl/matcher.go | 116 +++++++++++----- dsl/matcher_test.go | 131 ++++-------------- dsl/request.go | 57 +------- dsl/response.go | 2 +- examples/README.md | 4 +- .../consumer/goconsumer/user_service_test.go | 30 ++-- examples/gin/README.md | 10 +- examples/go-kit/README.md | 10 +- examples/mux/README.md | 10 +- 13 files changed, 195 insertions(+), 296 deletions(-) diff --git a/README.md b/README.md index 6693ca35a..2d62705fe 100644 --- a/README.md +++ b/README.md @@ -151,12 +151,12 @@ func TestConsumer(t *testing.T) { WithRequest(dsl.Request{ Method: "GET", Path: "/foobar", - Headers: map[string]string{"Content-Type": "application/json"}, + Headers: dsl.MapMatcher{"Content-Type": "application/json"}, Body: `{"s":"foo"}`, }). WillRespondWith(dsl.Response{ Status: 200, - Headers: map[string]string{"Content-Type": "application/json"}, + Headers: dsl.MapMatcher{"Content-Type": "application/json"}, Body: `{"s":"bar"}`, }) @@ -182,44 +182,36 @@ as the element _type_ (valid JSON number, string, object etc.) itself matches. consisting of elements like those passed in. `min` must be >= 1. `content` may be a valid JSON value: e.g. strings, numbers and objects. +Matchers can be used on the `Body`, `Headers`, `Path` and `Query` fields of the `dsl.Request` +type, and the `Body` and `Headers` fields of the `dsl.Response` type. + *Example:* -Here is a complex example that shows how all 3 terms can be used together: +Here is a more complex example that shows how all 3 terms can be used together: ```go -colour := Term("red", "red|green|blue") - -match := EachLike( - EachLike( - fmt.Sprintf(`{ - "size": 10, - "colour": %s, - "tag": [["jumper", "shirt]] - }`, colour) - 1), - 1)) + body := + Like(map[string]interface{}{ + "response": map[string]interface{}{ + "name": Like("Billy"), + "type": Term("admin", "admin|user|guest"), + "items": EachLike("cat", 2) + }, + }) ``` This example will result in a response body from the mock server that looks like: ```json -[ - [ - { - "size": 10, - "colour": "red", - "tag": [ - [ - "jumper", - "shirt" - ], - [ - "jumper", - "shirt" - ] - ] - } - ] -] +{ + "response": { + "name": "Billy", + "type": "admin", + "items": [ + "cat", + "cat" + ] + } +} ``` See the [matcher tests](https://github.com/pact-foundation/pact-go/blob/master/dsl/matcher_test.go) diff --git a/doc.go b/doc.go index 20f81e3cb..a263e66e2 100644 --- a/doc.go +++ b/doc.go @@ -71,38 +71,27 @@ cases. Here is a complex example that shows how all 3 terms can be used together: -colour := Term("red", "red|green|blue") - -match := EachLike( - EachLike( - fmt.Sprintf(`{ - "size": 10, - "colour": %s, - "tag": [["jumper", "shirt]] - }`, colour) - 1), - 1)) - + body := + Like(map[string]interface{}{ + "response": map[string]interface{}{ + "name": Like("Billy"), + "type": Term("admin", "admin|user|guest"), + "items": EachLike("cat", 2) + }, + }) This example will result in a response body from the mock server that looks like: - [ - [ - { - "size": 10, - "colour": "red", - "tag": [ - [ - "jumper", - "shirt" - ], - [ - "jumper", - "shirt" - ] - ] - } - ] - ] + + { + "response": { + "name": "Billy", + "type": "admin", + "items": [ + "cat", + "cat" + ] + } + } See the examples in the dsl package and the matcher tests (https://github.com/pact-foundation/pact-go/blob/master/dsl/matcher_test.go) diff --git a/dsl/interaction.go b/dsl/interaction.go index 9429cfb74..1bf745db5 100644 --- a/dsl/interaction.go +++ b/dsl/interaction.go @@ -42,12 +42,7 @@ func (p *Interaction) WithRequest(request Request) *Interaction { // Need to fix any weird JSON marshalling issues with the body Here // If body is a string, not an object, we need to put it back into an object // so that it's not double encoded - switch content := request.Body.(type) { - case string: - p.Request.Body = toObject([]byte(content)) - default: - // leave alone - } + p.Request.Body = toObject(request.Body) return p } @@ -69,13 +64,14 @@ func (p *Interaction) WillRespondWith(response Response) *Interaction { func toObject(stringOrObject interface{}) interface{} { switch content := stringOrObject.(type) { + case []byte: case string: var obj interface{} err := json.Unmarshal([]byte(content), &obj) if err != nil { - log.Println("[DEBUG] interaction: error unmarshaling object into string:", err.Error()) - return stringOrObject + log.Printf("[DEBUG] interaction: error unmarshaling string '%v' into an object. Probably not an object: %v\n", stringOrObject, err.Error()) + return content } return obj diff --git a/dsl/interaction_test.go b/dsl/interaction_test.go index de17a032a..03a5b9467 100644 --- a/dsl/interaction_test.go +++ b/dsl/interaction_test.go @@ -121,7 +121,7 @@ func TestInteraction_WillRespondWith(t *testing.T) { func TestInteraction_toObject(t *testing.T) { // unstructured string should not be changed - res := toObject([]byte("somestring")) + res := toObject("somestring") content, ok := res.(string) if !ok { @@ -133,7 +133,7 @@ func TestInteraction_toObject(t *testing.T) { } // errors should return a string repro of original interface{} - res = toObject([]byte("")) + res = toObject("") content, ok = res.(string) if !ok { diff --git a/dsl/matcher.go b/dsl/matcher.go index 32eb58eb9..160a25651 100644 --- a/dsl/matcher.go +++ b/dsl/matcher.go @@ -5,47 +5,29 @@ import ( "log" ) -// type Matcher interface{} - // EachLike specifies that a given element in a JSON body can be repeated // "minRequired" times. Number needs to be 1 or greater -func EachLike(content interface{}, minRequired int) string { - // TODO: should we just be marshalling these things as map[string]interface{} JSON objects anyway? - // this might remove the need for this ugly string/object combination - // TODO: if content is a string, it should probably be immediately converted to an object - // TODO: the above seems to have been fixed, but perhaps best to just _only_ allow objects - // instead of allowing string and other nonsense?? - return objectToString(map[string]interface{}{ +func EachLike(content interface{}, minRequired int) Matcher { + return Matcher{ "json_class": "Pact::ArrayLike", "contents": toObject(content), "min": minRequired, - }) - // return fmt.Sprintf(` - // { - // "json_class": "Pact::ArrayLike", - // "contents": %v, - // "min": %d - // }`, objectToString(content), minRequired) + } } // Like specifies that the given content type should be matched based // on type (int, string etc.) instead of a verbatim match. -func Like(content interface{}) string { - return objectToString(map[string]interface{}{ +func Like(content interface{}) Matcher { + return Matcher{ "json_class": "Pact::SomethingLike", "contents": toObject(content), - }) - // return fmt.Sprintf(` - // { - // "json_class": "Pact::SomethingLike", - // "contents": %v - // }`, objectToString(content)) + } } // Term specifies that the matching should generate a value // and also match using a regular expression. -func Term(generate string, matcher string) MatcherString { - return MatcherString(objectToString(map[string]interface{}{ +func Term(generate string, matcher string) Matcher { + return Matcher{ "json_class": "Pact::Term", "data": map[string]interface{}{ "generate": toObject(generate), @@ -55,21 +37,93 @@ func Term(generate string, matcher string) MatcherString { "s": toObject(matcher), }, }, - })) + } +} + +// Regex is a more appropriately named alias for the "Term" matcher +var Regex = Term + +// StringMatcher allows a string or Matcher to be provided in +// when matching with the DSL +// We use the strategy outlined at http://www.jerf.org/iri/post/2917 +// to create a "sum" or "union" type. +type StringMatcher interface { + // isMatcher is how we tell the compiler that strings + // and other types are the same / allowed + isMatcher() +} + +// S is the string primitive wrapper (alias) for the StringMatcher type, +// it allows plain strings to be matched +type S string + +func (s S) isMatcher() {} + +// String is the longer named form of the string primitive wrapper, +// it allows plain strings to be matched +type String string + +func (s String) isMatcher() {} + +// Matcher matches a complex object structure, which may itself +// contain nested Matchers +type Matcher map[string]interface{} + +func (m Matcher) isMatcher() {} + +// MarshalJSON is a custom encoder for Header type +func (m Matcher) MarshalJSON() ([]byte, error) { + obj := map[string]interface{}{} + + for header, value := range m { + obj[header] = toObject(value) + } + + return json.Marshal(obj) +} + +// UnmarshalJSON is a custom decoder for Header type +func (m *Matcher) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &m); err != nil { + return err + } + + return nil +} + +// MapMatcher allows a map[string]string-like object +// to also contain complex matchers +type MapMatcher map[string]StringMatcher + +// MarshalJSON is a custom encoder for Header type +func (h MapMatcher) MarshalJSON() ([]byte, error) { + obj := map[string]interface{}{} + + for header, value := range h { + obj[header] = toObject(value) + } + + return json.Marshal(obj) +} + +// UnmarshalJSON is a custom decoder for Header type +func (h *MapMatcher) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &h); err != nil { + return err + } + + return nil } // Takes an object and converts it to a JSON representation func objectToString(obj interface{}) string { switch content := obj.(type) { case string: - log.Println("STRING VALUE:", content) return content default: - log.Printf("OBJECT VALUE: %v", obj) jsonString, err := json.Marshal(obj) - log.Println("OBJECT -> JSON VALUE:", string(jsonString)) if err != nil { - log.Println("[DEBUG] interaction: error unmarshaling object into string:", err.Error()) + log.Println("[DEBUG] objectToString: error unmarshaling object into string:", err.Error()) return "" } return string(jsonString) diff --git a/dsl/matcher_test.go b/dsl/matcher_test.go index d45bd97e9..02dbe06af 100644 --- a/dsl/matcher_test.go +++ b/dsl/matcher_test.go @@ -4,11 +4,12 @@ import ( "bytes" "encoding/json" "fmt" + "log" "testing" ) func TestMatcher_TermString(t *testing.T) { - expected := formatJSON(MatcherString(` + expected := formatJSON(` { "data": { "generate": "myawesomeword", @@ -19,7 +20,7 @@ func TestMatcher_TermString(t *testing.T) { } }, "json_class": "Pact::Term" - }`)) + }`) match := formatJSON(Term("myawesomeword", `\w+`)) if expected != match { @@ -34,7 +35,7 @@ func TestMatcher_LikeBasicString(t *testing.T) { "json_class": "Pact::SomethingLike" }`) - match := formatJSON(Like(`"myspecialvalue"`)) + match := formatJSON(Like("myspecialvalue")) if expected != match { t.Fatalf("Expected Term to match. '%s' != '%s'", expected, match) } @@ -129,7 +130,7 @@ func TestMatcher_EachLikeString(t *testing.T) { "min": 7 }`) - match := formatJSON(EachLike(`"someword"`, 7)) + match := formatJSON(EachLike("someword", 7)) if expected != match { t.Fatalf("Expected Term to match. '%s' != '%s'", expected, match) } @@ -187,7 +188,7 @@ func TestMatcher_EachLikeArrayString(t *testing.T) { "min": 1 }`) - match := formatJSON(EachLike(`[1,2,3]`, 1)) + match := formatJSON(EachLike("[1,2,3]", 1)) if expected != match { t.Fatalf("Expected Term to match. '%s' != '%s'", expected, match) } @@ -206,37 +207,14 @@ func TestMatcher_NestLikeInEachLike(t *testing.T) { "min": 1 }`) - match := formatJSON(EachLike(map[string]interface{}{ + match := formatJSON(EachLike(Matcher{ "id": Like(10), - // "id": map[string]interface{}{ - // "contents": 10, - // "json_class": "Pact::SomethingLike", - // }, }, 1)) if expected != match { t.Fatalf("Expected Term to match. '%s' != '%s'", expected, match) } } -func TestMatcher_NestLikeInEachLikeString(t *testing.T) { - expected := formatJSON(` - { - "contents": { - "id": { - "contents": 10, - "json_class": "Pact::SomethingLike" - } - }, - "json_class": "Pact::ArrayLike", - "min": 1 - }`) - - match := formatJSON(EachLike(fmt.Sprintf(`{ "id": %s }`, Like(10)), 1)) - - if expected != match { - t.Fatalf("Expected Term to match. '%s' != '%s'", expected, match) - } -} func TestMatcher_NestTermInEachLike(t *testing.T) { expected := formatJSON(` @@ -260,8 +238,8 @@ func TestMatcher_NestTermInEachLike(t *testing.T) { match := formatJSON( EachLike( - fmt.Sprintf(`{ "colour": %s }`, - Term("red", `red|green`)), + Matcher{ + "colour": Term("red", "red|green")}, 1)) if expected != match { @@ -283,7 +261,7 @@ func TestMatcher_NestedEachLike(t *testing.T) { match := formatJSON( EachLike( - EachLike(`"blue"`, 1), + EachLike("blue", 1), 1)) if expected != match { @@ -332,21 +310,14 @@ func TestMatcher_NestAllTheThings(t *testing.T) { "min": 1 }`) - jumper := Like(`"jumper"`) - shirt := Like(`"shirt"`) - tag := EachLike(fmt.Sprintf(`[%s, %s]`, jumper, shirt), 2) - size := Like(10) - colour := Term("red", "red|green|blue") - match := formatJSON( EachLike( EachLike( - fmt.Sprintf( - `{ - "size": %s, - "colour": %s, - "tag": %s - }`, size, colour, tag), + Matcher{ + "colour": Term("red", "red|green|blue"), + "size": Like(10), + "tag": EachLike([]Matcher{Like("jumper"), Like("shirt")}, 2), + }, 1), 1)) if expected != match { @@ -360,15 +331,21 @@ func formatJSON(object interface{}) interface{} { switch content := object.(type) { case string: json.Indent(&out, []byte(content), "", "\t") - case MatcherString: - json.Indent(&out, []byte(content), "", "\t") + // case StringMatcher: + // json.Indent(&out, []byte(content), "", "\t") + default: + jsonString, err := json.Marshal(object) + if err != nil { + log.Println("[ERROR] unable to marshal json:", err) + } + json.Indent(&out, []byte(jsonString), "", "\t") } return string(out.Bytes()) } func ExampleLike_string() { - match := Like(`"myspecialvalue"`) + match := Like("myspecialvalue") fmt.Println(formatJSON(match)) // Output: //{ @@ -441,63 +418,3 @@ func ExampleEachLike() { // "min": 1 //} } - -func E_xampleEachLike_nested() { - jumper := Like(`"jumper"`) - shirt := Like(`"shirt"`) - tag := EachLike(fmt.Sprintf(`[%s, %s]`, jumper, shirt), 2) - size := Like(10) - colour := Term("red", "red|green|blue") - - match := EachLike( - EachLike( - fmt.Sprintf( - `{ - "size": %s, - "colour": %s, - "tag": %s - }`, size, colour, tag), - 1), - 1) - fmt.Println(formatJSON(match)) - // Output: - // { - // "contents": { - // "contents": { - // "colour": { - // "data": { - // "generate": "%s", - // "matcher": { - // "json_class": "Regexp", - // "o": 0, - // "s": "red" - // } - // }, - // "json_class": "Pact::Term" - // }, - // "size": { - // "contents": 10, - // "json_class": "Pact::SomethingLike" - // }, - // "tag": { - // "contents": [ - // { - // "contents": "jumper", - // "json_class": "Pact::SomethingLike" - // }, - // { - // "contents": "shirt", - // "json_class": "Pact::SomethingLike" - // } - // ], - // "json_class": "Pact::ArrayLike", - // "min": 2 - // } - // }, - // "json_class": "Pact::ArrayLike", - // "min": 1 - // }, - // "json_class": "Pact::ArrayLike", - // "min": 1 - // } -} diff --git a/dsl/request.go b/dsl/request.go index 2e03cf584..023ba0968 100644 --- a/dsl/request.go +++ b/dsl/request.go @@ -1,61 +1,10 @@ package dsl -import ( - "encoding/json" -) - // Request is the default implementation of the Request interface. type Request struct { Method string `json:"method"` - Path MatcherString `json:"path"` - Query MatcherMap `json:"query,omitempty"` - Headers MatcherMap `json:"headers,omitempty"` + Path StringMatcher `json:"path"` + Query MapMatcher `json:"query,omitempty"` + Headers MapMatcher `json:"headers,omitempty"` Body interface{} `json:"body,omitempty"` } - -// MatcherMap allows a map[string]string-like object -// to also contain complex matchers -type MatcherMap map[string]MatcherString - -// MarshalJSON is a custom encoder for Header type -func (h MatcherMap) MarshalJSON() ([]byte, error) { - obj := map[string]interface{}{} - - for header, value := range h { - obj[header] = toObject([]byte(value)) - } - - return json.Marshal(obj) -} - -// UnmarshalJSON is a custom decoder for Header type -func (h *MatcherMap) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &h); err != nil { - return err - } - - return nil -} - -// MatcherString allows the use of Matchers in string types -// It convert any matchers in interaction type to abstract JSON objects -// See https://github.com/pact-foundation/pact-go/issues/71 for background -type MatcherString string - -// MarshalJSON is a custom encoder for Header type -func (m MatcherString) MarshalJSON() ([]byte, error) { - var obj interface{} - - obj = toObject([]byte(m)) - - return json.Marshal(obj) -} - -// UnmarshalJSON is a custom decoder for Header type -func (m *MatcherString) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &m); err != nil { - return err - } - - return nil -} diff --git a/dsl/response.go b/dsl/response.go index 75e6c8e10..b3036300b 100644 --- a/dsl/response.go +++ b/dsl/response.go @@ -3,6 +3,6 @@ package dsl // Response is the default implementation of the Response interface. type Response struct { Status int `json:"status"` - Headers MatcherMap `json:"headers,omitempty"` + Headers MapMatcher `json:"headers,omitempty"` Body interface{} `json:"body,omitempty"` } diff --git a/examples/README.md b/examples/README.md index e00674671..882eb44a7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,7 +3,7 @@ This folder contains a number of examples in different frameworks to demonstrate how Pact could be used in each. -Each Provider API currently exposes a single `Login` endpoint at `POST /users/login`, +Each Provider API currently exposes a single `Login` endpoint at `POST /users/login/1`, which the [Consumer](consumer/goconsumer) uses to authenticate a User. We test 3 scenarios, highlighting the use of [Provider States](/pact-foundation/pact-go#provider#provider-states): @@ -54,5 +54,5 @@ Before you can run the consumer make sure the provider is go run cmd/web/main.go ``` -Hit http://localhost:8081/ in your browser. You can use the username/password +Hit http://localhost:8081/ in your browser. You can use the username/password combination of "billy" / "issilly" to authenticate. diff --git a/examples/consumer/goconsumer/user_service_test.go b/examples/consumer/goconsumer/user_service_test.go index 55f072802..1cfe0acb9 100644 --- a/examples/consumer/goconsumer/user_service_test.go +++ b/examples/consumer/goconsumer/user_service_test.go @@ -28,9 +28,11 @@ var name = "Jean-Marie de La Beaujardière😀😍" var like = dsl.Like var eachLike = dsl.EachLike var term = dsl.Term + +// var str = dsl.S var loginRequest = fmt.Sprintf(`{ "username":"%s", "password": "issilly" }`, name) -var commonHeaders = dsl.MatcherMap{ +var commonHeaders = dsl.MapMatcher{ "Content-Type": dsl.Term("application/json; charset=utf-8", `application\/json`), } @@ -113,14 +115,14 @@ func TestPactConsumerLoginHandler_UserExists(t *testing.T) { return nil } + body := - like(fmt.Sprintf( - `{ - "user": { - "name": "%s", - "type": %v - } - }`, name, term("admin", "admin|user|guest"))) + like(dsl.Matcher{ + "user": dsl.Matcher{ + "name": name, + "type": term("admin", "admin|user|guest"), + }, + }) // Pull from pact broker, used in e2e/integrated tests for pact-go release // Setup interactions on the Mock Service. Note that you can have multiple @@ -132,7 +134,7 @@ func TestPactConsumerLoginHandler_UserExists(t *testing.T) { WithRequest(dsl.Request{ Method: "POST", Path: dsl.Term("/users/login/1", "/users/login/[0-9]+"), - Query: dsl.MatcherMap{ + Query: dsl.MapMatcher{ "foo": dsl.Term("bar", "[a-zA-Z]+"), }, Body: loginRequest, @@ -157,7 +159,7 @@ func TestPactConsumerLoginHandler_UserDoesNotExist(t *testing.T) { client.loginHandler(rr, req) if client.user != nil { - return fmt.Errorf("Expected user to be nil but got: %v", client.user) + return fmt.Errorf("Expected user to be nil but in stead got: %v", client.user) } return nil @@ -169,11 +171,11 @@ func TestPactConsumerLoginHandler_UserDoesNotExist(t *testing.T) { UponReceiving("A request to login with user 'billy'"). WithRequest(dsl.Request{ Method: "POST", - Path: "/users/login/10", + Path: dsl.String("/users/login/10"), Body: loginRequest, Headers: commonHeaders, - Query: dsl.MatcherMap{ - "foo": "anything", + Query: dsl.MapMatcher{ + "foo": dsl.String("anything"), }, }). WillRespondWith(dsl.Response{ @@ -207,7 +209,7 @@ func TestPactConsumerLoginHandler_UserUnauthorised(t *testing.T) { UponReceiving("A request to login with user 'billy'"). WithRequest(dsl.Request{ Method: "POST", - Path: "/users/login/10", + Path: dsl.String("/users/login/10"), Body: loginRequest, Headers: commonHeaders, }). diff --git a/examples/gin/README.md b/examples/gin/README.md index 141637086..5f73a8077 100644 --- a/examples/gin/README.md +++ b/examples/gin/README.md @@ -6,7 +6,7 @@ RESTful APIs. The following example is a simple Login UI ([Consumer](#consumer)) that calls a User Service ([Provider](#provider)) using JSON over HTTP. -The API currently exposes a single `Login` endpoint at `POST /users/login`, which +The API currently exposes a single `Login` endpoint at `POST /users/login/:id`, which the Consumer uses to authenticate a User. We test 3 scenarios, highlighting the use of [Provider States](/pact-foundation/pact-go#provider#provider-states): @@ -29,7 +29,7 @@ go get ./... ## Provider The "Provider" is a real Go Kit Endpoint (following the Profile Service [example](https://github.com/go-kit/kit/tree/master/examples/profilesvc)), -exposing a single `/users/login` API call: +exposing a single `/users/login/:id` API call: ``` cd provider @@ -50,17 +50,17 @@ go run cmd/usersvc/main.go curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"billy", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" # 403 curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"billy", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" # 404 curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"someoneelse", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" ``` diff --git a/examples/go-kit/README.md b/examples/go-kit/README.md index 171b6a40c..632995dc4 100644 --- a/examples/go-kit/README.md +++ b/examples/go-kit/README.md @@ -6,7 +6,7 @@ microservices. The following example is a simple Login UI ([Consumer](#consumer)) that calls a User Service ([Provider](#provider)) using JSON over HTTP. -The API currently exposes a single `Login` endpoint at `POST /users/login`, which +The API currently exposes a single `Login` endpoint at `POST /users/login/:id`, which the Consumer uses to authenticate a User. We test 3 scenarios, highlighting the use of [Provider States](/pact-foundation/pact-go#provider#provider-states): @@ -29,7 +29,7 @@ go get ./... ## Provider The "Provider" is a real Go Kit Endpoint (following the Profile Service [example](https://github.com/go-kit/kit/tree/master/examples/profilesvc)), -exposing a single `/users/login` API call: +exposing a single `/users/login/:id` API call: ``` cd provider @@ -50,17 +50,17 @@ go run cmd/usersvc/main.go curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"billy", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" # 403 curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"billy", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" # 404 curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"someoneelse", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" ``` diff --git a/examples/mux/README.md b/examples/mux/README.md index 369cc46d1..b0d6a86d3 100644 --- a/examples/mux/README.md +++ b/examples/mux/README.md @@ -5,7 +5,7 @@ Example using the standard libraries Mux Router. The following example is a simple Login UI ([Consumer](#consumer)) that calls a User Service ([Provider](#provider)) using JSON over HTTP. -The API currently exposes a single `Login` endpoint at `POST /users/login`, which +The API currently exposes a single `Login` endpoint at `POST /users/login/:id`, which the Consumer uses to authenticate a User. We test 3 scenarios, highlighting the use of [Provider States](/pact-foundation/pact-go#provider#provider-states): @@ -27,7 +27,7 @@ go get ./... ## Provider -The "Provider" is a real HTTP API containing the `/users/login` API call: +The "Provider" is a real HTTP API containing the `/users/login/:id` API call: ``` cd provider @@ -48,17 +48,17 @@ go run cmd/usersvc/main.go curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"billy", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" # 403 curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"billy", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" # 404 curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ "username":"someoneelse", "password":"issilly" -}' "http://localhost:8080/users/login" +}' "http://localhost:8080/users/login/1" ```