Skip to content

Commit

Permalink
feat: enable setup/teardown in states, fix issue where params aren't …
Browse files Browse the repository at this point in the history
…passed through
  • Loading branch information
mefellows committed Mar 13, 2021
1 parent 424f7cc commit 6ffeb4e
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 18 deletions.
32 changes: 21 additions & 11 deletions examples/v3/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,20 @@ func TestV3HTTPProvider(t *testing.T) {
PactFiles: []string{filepath.ToSlash(fmt.Sprintf("%s/V3Consumer-V3Provider.json", pactDir))},
RequestFilter: f,
StateHandlers: v3.StateHandlers{
"User foo exists": func(s v3.ProviderStateV3) error {
log.Println("[DEBUG] calling user foo exists state handler")
"User foo exists": func(setup bool, s v3.ProviderStateV3) (v3.ProviderStateV3Response, error) {

if setup {
log.Println("[DEBUG] calling user foo exists state handler", s)
} else {
log.Println("[DEBUG] teardown the 'User foo exists' state")
}

// ... do something

return nil
// Optionally (if there are generators in the pact) return provider state values to be used in the verification (only )
return v3.ProviderStateV3Response{"id": "bar"}, nil

// return nil, nil
},
},
})
Expand All @@ -71,23 +79,25 @@ func TestV3MessageProvider(t *testing.T) {
if user != nil {
return user, nil
} else {
return map[string]string{
return v3.ProviderStateV3Response{
"message": "not found",
}, nil
}
},
}

stateMappings := v3.StateHandlers{
"User with id 127 exists": func(v3.ProviderStateV3) error {
user = &User{
ID: 44,
Name: "Baz",
Date: "2020-01-01",
LastName: "sampson",
"User with id 127 exists": func(setup bool, s v3.ProviderStateV3) (v3.ProviderStateV3Response, error) {
if setup {
user = &User{
ID: 44,
Name: "Baz",
Date: "2020-01-01",
LastName: "sampson",
}
}

return nil
return v3.ProviderStateV3Response{"id": "bar"}, nil
},
}

Expand Down
40 changes: 35 additions & 5 deletions v3/http_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func AfterEachMiddleware(AfterEach Hook) proxy.Middleware {
type stateHandlerAction struct {
Action string `json:"action"`
State string `json:"state"`
// Params map[string]interface{}
Params map[string]interface{}
}

// stateHandlerMiddleware responds to the various states that are
Expand All @@ -200,29 +200,59 @@ func stateHandlerMiddleware(stateHandlers StateHandlers) proxy.Middleware {
var state stateHandlerAction
buf := new(strings.Builder)
io.Copy(buf, r.Body)
log.Println("[TRACE] state handler received input", buf.String())
log.Println("[TRACE] state handler received raw input", buf.String())

err := json.Unmarshal([]byte(buf.String()), &state)
log.Println("[TRACE] state handler received input", state)
log.Println("[TRACE] state handler parsed input (without params)", state)

if err != nil {
log.Println("[ERROR] unable to decode incoming state change payload", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

// Setup any provider state
// Extract the params from the payload. They are in the root, so we need to do some more work to achieve this
var params ProviderStateV3Response
err = json.Unmarshal([]byte(buf.String()), &params)
if err != nil {
log.Println("[ERROR] unable to decode incoming state change payload", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

delete(params, "action")
delete(params, "state")
state.Params = params
log.Println("[TRACE] state handler completed parsing input (with params)", state)

// Find the provider state handler
sf, stateFound := stateHandlers[state.State]

if !stateFound {
log.Printf("[WARN] no state handler found for state: %v", state.State)
} else {
// Execute state handler
if err := sf(ProviderStateV3{Name: state.State}); err != nil {
res, err := sf(state.Action == "setup", ProviderStateV3{Name: state.State, Parameters: state.Params})

if err != nil {
log.Printf("[ERROR] state handler for '%v' errored: %v", state.State, err)
w.WriteHeader(http.StatusInternalServerError)
return
}

// Return provider state values for generator
if res != nil {
resBody, err := json.Marshal(res)

if err != nil {
log.Printf("[ERROR] state handler for '%v' errored: %v", state.State, err)
w.WriteHeader(http.StatusInternalServerError)

return
}

w.Write(resBody)
}
}

w.WriteHeader(http.StatusOK)
Expand Down
5 changes: 5 additions & 0 deletions v3/interaction_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package v3
// ProviderStateV3 allows parameters and a description to be passed to the verification process
type ProviderStateV3 struct {
Name string `json:"name"`
Action string `json:"action"` // TODO: remove this, don't expose to the user
Parameters interface{} `json:"params,omitempty"`
}

// ProviderStateV3Response may return values in the state setup
// for the "value from provider state" feature
type ProviderStateV3Response map[string]interface{}

// InteractionV3 sets up an expected request/response on a mock server
// and is replayed on the provider side for verification
// TODO: HTTPInteraction?
Expand Down
6 changes: 5 additions & 1 deletion v3/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import (

// StateHandler is a provider function that sets up a given state before
// the provider interaction is validated
type StateHandler func(ProviderStateV3) error
// It can optionally return a map of key => value (JSON) that may be used
// as values in the verification process
// See https://github.com/pact-foundation/pact-reference/tree/master/rust/pact_verifier_cli#state-change-requests
// https://github.com/pact-foundation/pact-js/tree/feat/v3.0.0#provider-state-injected-values for more
type StateHandler func(setup bool, state ProviderStateV3) (ProviderStateV3Response, error)

// StateHandlers is a list of StateHandler's
type StateHandlers map[string]StateHandler
Expand Down
19 changes: 18 additions & 1 deletion v3/message_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,28 @@ var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHand
log.Printf("[WARN] state handler not found for state: %v", state.Name)
} else {
// Execute state handler
if err = sf(state); err != nil {
res, err := sf(state.Action == "setup", state)

if err != nil {
log.Printf("[WARN] state handler for '%v' return error: %v", state.Name, err)
w.WriteHeader(http.StatusInternalServerError)
return
}

// Return provider state values for generator
if res != nil {
resBody, err := json.Marshal(res)

if err != nil {
log.Printf("[ERROR] state handler for '%v' errored: %v", state.Name, err)
w.WriteHeader(http.StatusInternalServerError)

return
}

w.Write(resBody)
}

}
}

Expand Down

0 comments on commit 6ffeb4e

Please sign in to comment.