-
-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): add vanilla golang ServeMux, update docs
- Loading branch information
Showing
8 changed files
with
308 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Example - HTTP Mux | ||
|
||
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 Consumer uses to authenticate a User. | ||
|
||
We test 3 scenarios, highlighting the use of [Provider States](/pact-foundation/pact-go#provider#provider-states): | ||
|
||
1. When the user "Billy" exists, and we perform a login, we expect an HTTP `200` | ||
1. When the user "Billy" does not exists, and we perform a login, we expect an HTTP `404` | ||
1. When the user "Billy" is unauthorized, and we perform a login, we expect an HTTP `403` | ||
|
||
# Getting started | ||
|
||
Before any of these tests can be run, ensure Pact Go is installed and run the | ||
daemon in the background: | ||
|
||
``` | ||
go get ./... | ||
<path to>/pact-go daemon | ||
``` | ||
|
||
|
||
## Provider | ||
|
||
The "Provider" is a real HTTP API containing the `/users/login` API call: | ||
|
||
``` | ||
cd provider | ||
go test -v . | ||
``` | ||
|
||
This will spin up the Provider API with extra routes added for the handling of | ||
provider states, run the verification process and report back success/failure. | ||
|
||
### Running the Provider | ||
|
||
The provider can be run as a standalone service: | ||
|
||
``` | ||
go run cmd/usersvc/main.go | ||
# 200 | ||
curl -v -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{ | ||
"username":"billy", | ||
"password":"issilly" | ||
}' "http://localhost:8080/users/login" | ||
# 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" | ||
# 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" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net" | ||
"net/http" | ||
|
||
"github.com/pact-foundation/pact-go/examples/mux/provider" | ||
"github.com/pact-foundation/pact-go/utils" | ||
) | ||
|
||
func main() { | ||
mux := http.NewServeMux() | ||
mux.HandleFunc("/users/login", provider.UserLogin) | ||
|
||
port, _ := utils.GetFreePort() | ||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer ln.Close() | ||
|
||
log.Printf("API starting: port %d (%s)", port, ln.Addr()) | ||
log.Printf("API terminating: %v", http.Serve(ln, mux)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package provider | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/pact-foundation/pact-go/examples/types" | ||
) | ||
|
||
var userRepository = &types.UserRepository{ | ||
Users: map[string]*types.User{ | ||
"billy": &types.User{ | ||
Name: "billy", | ||
Username: "billy", | ||
Password: "issilly", | ||
}, | ||
}, | ||
} | ||
|
||
// UserLogin is the login route. | ||
var UserLogin = func(w http.ResponseWriter, r *http.Request) { | ||
var login types.LoginRequest | ||
w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||
|
||
body, err := ioutil.ReadAll(r.Body) | ||
defer r.Body.Close() | ||
|
||
if err != nil { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
return | ||
} | ||
|
||
err = json.Unmarshal(body, &login) | ||
if err != nil { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
return | ||
} | ||
|
||
user, err := userRepository.ByUsername(login.Username) | ||
if err != nil { | ||
w.WriteHeader(http.StatusNotFound) | ||
} else if user.Username != login.Username || user.Password != login.Password { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
} else { | ||
w.WriteHeader(http.StatusOK) | ||
res := types.LoginResponse{User: user} | ||
resBody, _ := json.Marshal(res) | ||
w.Write(resBody) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package provider | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net" | ||
"net/http" | ||
"os" | ||
"testing" | ||
|
||
"github.com/pact-foundation/pact-go/dsl" | ||
examples "github.com/pact-foundation/pact-go/examples/types" | ||
"github.com/pact-foundation/pact-go/types" | ||
"github.com/pact-foundation/pact-go/utils" | ||
) | ||
|
||
// The actual Provider test itself | ||
func TestPact_Provider(t *testing.T) { | ||
go startInstrumentedProvider() | ||
|
||
pact := createPact() | ||
|
||
// Verify the Provider with local Pact Files | ||
err := pact.VerifyProvider(types.VerifyRequest{ | ||
ProviderBaseURL: fmt.Sprintf("http://localhost:%d", port), | ||
PactURLs: []string{fmt.Sprintf("%s/billy-bobby.json", pactDir)}, | ||
ProviderStatesURL: fmt.Sprintf("http://localhost:%d/states", port), | ||
ProviderStatesSetupURL: fmt.Sprintf("http://localhost:%d/setup", port), | ||
}) | ||
|
||
if err != nil { | ||
t.Fatal("Error:", err) | ||
} | ||
} | ||
|
||
// Starts the provider API with hooks for provider states. | ||
// This essentially mirrors the main.go file, with extra routes added. | ||
func startInstrumentedProvider() { | ||
mux := http.NewServeMux() | ||
mux.HandleFunc("/users/login", UserLogin) | ||
mux.HandleFunc("/setup", providerStateSetupFunc) | ||
mux.HandleFunc("/states", providerStatesFunc) | ||
|
||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer ln.Close() | ||
|
||
log.Printf("API starting: port %d (%s)", port, ln.Addr()) | ||
log.Printf("API terminating: %v", http.Serve(ln, mux)) | ||
|
||
} | ||
|
||
// Get all states route. | ||
var providerStatesFunc = func(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||
body, _ := json.Marshal(providerStates) | ||
w.Write(body) | ||
} | ||
|
||
// Set current provider state route. | ||
var providerStateSetupFunc = func(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||
var state types.ProviderState | ||
|
||
body, err := ioutil.ReadAll(r.Body) | ||
defer r.Body.Close() | ||
|
||
if err != nil { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
return | ||
} | ||
|
||
err = json.Unmarshal(body, &state) | ||
if err != nil { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
return | ||
} | ||
|
||
// Setup database for different states | ||
if state.State == "User billy exists" { | ||
userRepository = billyExists | ||
} else if state.State == "User billy is unauthorized" { | ||
userRepository = billyUnauthorized | ||
} else { | ||
userRepository = billyDoesNotExist | ||
} | ||
} | ||
|
||
// This path returns all states available for the consumer 'billy' | ||
// Note that the key for the array is the consumer name (in this case, 'billy') | ||
// The values should match a Given("...") block in the Interaction. Essentially, | ||
// this broadcasts the allowed states of the provider for verification, it is not | ||
// necessary for all consumers to use all states. | ||
var providerStates = map[string][]string{ | ||
"billy": []string{ | ||
"User billy exists", | ||
"User billy does not exist", | ||
"User billy is unauthorized"}, | ||
} | ||
|
||
// Configuration / Test Data | ||
var dir, _ = os.Getwd() | ||
var pactDir = fmt.Sprintf("%s/../../pacts", dir) | ||
var logDir = fmt.Sprintf("%s/log", dir) | ||
var port, _ = utils.GetFreePort() | ||
|
||
// Provider States data sets | ||
var billyExists = &examples.UserRepository{ | ||
Users: map[string]*examples.User{ | ||
"billy": &examples.User{ | ||
Name: "billy", | ||
Username: "billy", | ||
Password: "issilly", | ||
}, | ||
}, | ||
} | ||
|
||
var billyDoesNotExist = &examples.UserRepository{} | ||
|
||
var billyUnauthorized = &examples.UserRepository{ | ||
Users: map[string]*examples.User{ | ||
"billy": &examples.User{ | ||
Name: "billy", | ||
Username: "billy", | ||
Password: "issilly1", | ||
}, | ||
}, | ||
} | ||
|
||
// Setup the Pact client. | ||
func createPact() dsl.Pact { | ||
// Create Pact connecting to local Daemon | ||
return dsl.Pact{ | ||
Port: 6666, | ||
Consumer: "billy", | ||
Provider: "bobby", | ||
LogDir: logDir, | ||
PactDir: pactDir, | ||
} | ||
} |