Skip to content

Commit

Permalink
feat: allow messages to return provider states callback values
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Apr 11, 2021
1 parent 8b1859a commit a5670a8
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 48 deletions.
2 changes: 1 addition & 1 deletion examples/v3/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func TestMessagePact(t *testing.T) {
Given(v3.ProviderStateV3{
Name: "User with id 127 exists",
Parameters: map[string]interface{}{
"id": 27,
"id": 127,
},
}).
ExpectsToReceive("a user event").
Expand Down
2 changes: 1 addition & 1 deletion examples/v3/pacts/V3MessageConsumer-V3MessageProvider.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"consumer":{"name":"V3MessageConsumer"},"provider":{"name":"V3MessageProvider"},"messages":[{"contents":{"datetime":"2020-01-01","id":12,"lastName":"LastName","name":"FirstName"},"providerStates":[{"name":"User with id 127 exists","params":{"id":27}}],"metadata":{"Content-Type":"application/json; charset=utf-8"},"description":"a user event","matchingRules":{"body":{"$.datetime":{"combine":"AND","matchers":[{"match":"regex","regex":"[0-9\\-]+"}]},"$.id":{"combine":"AND","matchers":[{"match":"integer"}]},"$.lastName":{"combine":"AND","matchers":[{"match":"type"}]},"$.name":{"combine":"AND","matchers":[{"match":"type"}]}},"path":{}}}],"metadata":{"pactGo":{"version":"v1.4.3"},"pactSpecification":{"version":"3.0.0"}}}
{"consumer":{"name":"V3MessageConsumer"},"provider":{"name":"V3MessageProvider"},"messages":[{"contents":{"datetime":"2020-01-01","id":12,"lastName":"LastName","name":"FirstName"},"providerStates":[{"name":"User with id 127 exists","params":{"id":127}}],"metadata":{"Content-Type":"application/json; charset=utf-8"},"description":"a user event","matchingRules":{"body":{"$.datetime":{"combine":"AND","matchers":[{"match":"regex","regex":"[0-9\\-]+"}]},"$.id":{"combine":"AND","matchers":[{"match":"integer"}]},"$.lastName":{"combine":"AND","matchers":[{"match":"type"}]},"$.name":{"combine":"AND","matchers":[{"match":"type"}]}},"path":{}}}],"metadata":{"pactGo":{"version":"v1.4.3"},"pactSpecification":{"version":"3.0.0"}}}
18 changes: 8 additions & 10 deletions examples/v3/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,16 @@ func TestV3MessageProvider(t *testing.T) {

stateMappings := v3.StateHandlers{
"User with id 127 exists": func(setup bool, s v3.ProviderStateV3) (v3.ProviderStateV3Response, error) {
// TODO: it seems maybe the "action" flag isn't passed in for messages like the HTTP one?
// if setup {
user = &User{
ID: 44,
Name: "Baz",
Date: "2020-01-01",
LastName: "sampson",
if setup {
user = &User{
ID: 127,
Name: "Baz",
Date: "2020-01-01",
LastName: "sampson",
}
}
// }

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

Expand Down
6 changes: 2 additions & 4 deletions v3/http_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,6 @@ func stateHandlerMiddleware(stateHandlers StateHandlers) proxy.Middleware {
}
}

const providerStatesSetupPath = "/__setup/"

// Use this to wait for a port to be running prior
// to running tests.
var waitForPort = func(port int, network string, address string, timeoutDuration time.Duration, message string) error {
Expand All @@ -284,8 +282,8 @@ var waitForPort = func(port int, network string, address string, timeoutDuration
for {
select {
case <-timeout:
log.Printf("[ERROR] Expected server to start < %s. %s", timeoutDuration, message)
return fmt.Errorf("Expected server to start < %s. %s", timeoutDuration, message)
log.Printf("[ERROR] expected server to start < %s. %s", timeoutDuration, message)
return fmt.Errorf("expected server to start < %s. %s", timeoutDuration, message)
case <-time.After(50 * time.Millisecond):
_, err := net.Dial(network, fmt.Sprintf("%s:%d", address, port))
if err == nil {
Expand Down
2 changes: 1 addition & 1 deletion v3/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ var packages = map[string]packageInfo{
},
MockServerPackage: {
libName: "libpact_mock_server_ffi",
version: "0.0.15",
version: "0.0.16",
semverRange: ">= 0.0.15, < 1.0.0",
},
}
Expand Down
1 change: 0 additions & 1 deletion v3/interaction_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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"`
}

Expand Down
98 changes: 68 additions & 30 deletions v3/message_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func (v *MessageVerifier) verifyMessageProviderRaw(request VerifyMessageRequest,
if err != nil {
return err
}

// Starts the message wrapper API with hooks back to the message handlers
// This maps the 'description' field of a message pact, to a function handler
// that will implement the message producer. This function must return an object and optionally
Expand Down Expand Up @@ -78,11 +79,13 @@ func (v *MessageVerifier) verifyMessageProviderRaw(request VerifyMessageRequest,
ProviderTags: request.ProviderTags,
// CustomProviderHeaders: request.CustomProviderHeaders,
// ConsumerVersionSelectors: request.ConsumerVersionSelectors,
// EnablePending: request.EnablePending,
FailIfNoPactsFound: request.FailIfNoPactsFound,
// IncludeWIPPactsSince: request.IncludeWIPPactsSince,
EnablePending: request.EnablePending,
FailIfNoPactsFound: request.FailIfNoPactsFound,
IncludeWIPPactsSince: request.IncludeWIPPactsSince,
ProviderStatesSetupURL: fmt.Sprintf("http://localhost:%d%s", port, providerStatesSetupPath),
}

mux.HandleFunc(providerStatesSetupPath, messageStateHandler(request.MessageHandlers, request.StateHandlers))
mux.HandleFunc("/", messageVerificationHandler(request.MessageHandlers, request.StateHandlers))

ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
Expand Down Expand Up @@ -112,18 +115,23 @@ type messageVerificationHandlerRequest struct {
States []ProviderStateV3 `json:"providerStates"`
}

var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHandlers StateHandlers) http.HandlerFunc {
type messageStateHandlerRequest struct {
State string `json:"state"`
Params interface{} `json:"params"`
Action string `json:"action"`
}

var messageStateHandler = func(messageHandlers MessageHandlers, stateHandlers StateHandlers) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO: should this be set by the provider itself? How does the metadata go back?
w.Header().Set("Content-Type", "application/json; charset=utf-8")

log.Printf("[TRACE] message verification handler")
log.Printf("[TRACE] message state handler")

// Extract message
var message messageVerificationHandlerRequest
var message messageStateHandlerRequest
body, err := ioutil.ReadAll(r.Body)
r.Body.Close()
log.Printf("[TRACE] message verification handler received request: %+s", body)
log.Printf("[TRACE] message state handler received request: %+s, %s", body, r.URL.Path)

if err != nil {
w.WriteHeader(http.StatusBadRequest)
Expand All @@ -137,39 +145,67 @@ var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHand
return
}

// Setup any provider state
for _, state := range message.States {
sf, stateFound := stateHandlers[state.Name]
log.Println("[TRACE] message verification - setting up state for message", message)
sf, stateFound := stateHandlers[message.State]

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

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

if !stateFound {
log.Printf("[WARN] state handler not found for state: %v", state.Name)
} else {
// Execute state handler
_, err := sf(state.Action == "setup", state)
// res, err := sf(state.Action == "setup", state)
// Return provider state values for generator
if res != nil {
resBody, err := json.Marshal(res)

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

return
}

// Return provider state values for generator
// TODO: can't return state data here, because it's all the one request!
// if res != nil {
// resBody, err := json.Marshal(res)
log.Printf("[INFO] state handler for '%v' finished", message.State)
w.Write(resBody)
}

}

w.WriteHeader(http.StatusOK)
}
}
var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHandlers StateHandlers) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO: should this be set by the provider itself? How does the metadata go back?
w.Header().Set("Content-Type", "application/json; charset=utf-8")

log.Printf("[TRACE] message verification handler")

// if err != nil {
// log.Printf("[ERROR] state handler for '%v' errored: %v", state.Name, err)
// w.WriteHeader(http.StatusInternalServerError)
// Extract message
var message messageVerificationHandlerRequest
body, err := ioutil.ReadAll(r.Body)
r.Body.Close()
log.Printf("[TRACE] message verification handler received request: %+s, %s", body, r.URL.Path)

// return
// }
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

// w.Write(resBody)
// }
err = json.Unmarshal(body, &message)

}
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

// Lookup key in function mapping
Expand Down Expand Up @@ -201,3 +237,5 @@ var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHand
w.Write(resBody)
}
}

const providerStatesSetupPath = "/__setup/"

0 comments on commit a5670a8

Please sign in to comment.