diff --git a/examples/v3/consumer_test.go b/examples/v3/consumer_test.go index ba7a59874..e5a315d6f 100644 --- a/examples/v3/consumer_test.go +++ b/examples/v3/consumer_test.go @@ -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"). diff --git a/examples/v3/pacts/V3MessageConsumer-V3MessageProvider.json b/examples/v3/pacts/V3MessageConsumer-V3MessageProvider.json index a36899801..24ddf3013 100644 --- a/examples/v3/pacts/V3MessageConsumer-V3MessageProvider.json +++ b/examples/v3/pacts/V3MessageConsumer-V3MessageProvider.json @@ -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"}}} \ No newline at end of file +{"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"}}} \ No newline at end of file diff --git a/examples/v3/provider_test.go b/examples/v3/provider_test.go index 8e4c5e83d..1ef16fd07 100644 --- a/examples/v3/provider_test.go +++ b/examples/v3/provider_test.go @@ -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 }, } diff --git a/v3/http_verifier.go b/v3/http_verifier.go index 5da3ba6e4..505284800 100644 --- a/v3/http_verifier.go +++ b/v3/http_verifier.go @@ -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 { @@ -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 { diff --git a/v3/installer/installer.go b/v3/installer/installer.go index b3b8f9c83..493c1e2c2 100644 --- a/v3/installer/installer.go +++ b/v3/installer/installer.go @@ -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", }, } diff --git a/v3/interaction_v3.go b/v3/interaction_v3.go index af51562e2..ba0b89972 100644 --- a/v3/interaction_v3.go +++ b/v3/interaction_v3.go @@ -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"` } diff --git a/v3/message_verifier.go b/v3/message_verifier.go index 7f6d823c5..7187b111e 100644 --- a/v3/message_verifier.go +++ b/v3/message_verifier.go @@ -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 @@ -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)) @@ -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) @@ -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 @@ -201,3 +237,5 @@ var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHand w.Write(resBody) } } + +const providerStatesSetupPath = "/__setup/"