diff --git a/dsl/client.go b/dsl/client.go index 00253c23e..e43b8232c 100644 --- a/dsl/client.go +++ b/dsl/client.go @@ -17,6 +17,32 @@ import ( "github.com/pact-foundation/pact-go/types" ) +// Client is the interface +type Client interface { + // StartServer starts a remote Pact Mock Server. + StartServer(args []string, port int) *types.MockServer + + // ListServers lists all known Mock Servers + ListServers() []*types.MockServer + + // StopServer stops a remote Pact Mock Server. + StopServer(server *types.MockServer) (*types.MockServer, error) + + // RemoveAllServers stops all remote Pact Mock Servers. + RemoveAllServers(server *types.MockServer) *[]types.MockServer + + // VerifyProvider runs the verification process against a running Provider. + VerifyProvider(request types.VerifyRequest) (types.ProviderVerifierResponse, error) + + // UpdateMessagePact adds a pact message to a contract file + UpdateMessagePact(request types.PactMessageRequest) error + + // ReifyMessage takes a structured object, potentially containing nested Matchers + // and returns an object with just the example (generated) content + // The object may be a simple JSON primitive e.g. string or number or a complex object + ReifyMessage(request *types.PactReificationRequest) (res *types.ReificationResponse, err error) +} + // PactClient is the main interface into starting/stopping // the underlying Pact CLI subsystem type PactClient struct { diff --git a/dsl/client_test.go b/dsl/client_test.go index 59f5a9bc3..8ceb0f436 100644 --- a/dsl/client_test.go +++ b/dsl/client_test.go @@ -16,7 +16,7 @@ import ( ) func TestClient_List(t *testing.T) { - client, _ := createClient(true) + client, _ := createMockClient(true) servers := client.ListServers() if len(servers) != 3 { @@ -25,7 +25,7 @@ func TestClient_List(t *testing.T) { } func TestClient_StartServer(t *testing.T) { - client, svc := createClient(true) + client, svc := createMockClient(true) defer stubPorts()() port, _ := utils.GetFreePort() @@ -36,7 +36,7 @@ func TestClient_StartServer(t *testing.T) { } func TestClient_StartServerFail(t *testing.T) { - client, _ := createClient(false) + client, _ := createMockClient(false) server := client.StartServer([]string{}, 0) if server.Port != 0 { t.Fatalf("Expected server to be empty %v", server) @@ -44,7 +44,7 @@ func TestClient_StartServerFail(t *testing.T) { } func TestClient_StopServer(t *testing.T) { - client, svc := createClient(true) + client, svc := createMockClient(true) client.StopServer(&types.MockServer{}) if svc.ServiceStopCount != 1 { @@ -53,7 +53,7 @@ func TestClient_StopServer(t *testing.T) { } func TestClient_StopServerFail(t *testing.T) { - client, _ := createClient(true) + client, _ := createMockClient(true) res, err := client.StopServer(&types.MockServer{}) should := &types.MockServer{} if !reflect.DeepEqual(res, should) { @@ -65,7 +65,7 @@ func TestClient_StopServerFail(t *testing.T) { } func TestClient_VerifyProvider(t *testing.T) { - client, _ := createClient(true) + client, _ := createMockClient(true) ms := setupMockServer(true, t) defer ms.Close() @@ -85,7 +85,7 @@ func TestClient_VerifyProvider(t *testing.T) { } func TestClient_VerifyProviderFailValidation(t *testing.T) { - client, _ := createClient(true) + client, _ := createMockClient(true) req := types.VerifyRequest{} _, err := client.VerifyProvider(req) @@ -100,7 +100,7 @@ func TestClient_VerifyProviderFailValidation(t *testing.T) { } func TestClient_VerifyProviderFailExecution(t *testing.T) { - client, _ := createClient(false) + client, _ := createMockClient(false) ms := setupMockServer(true, t) defer ms.Close() @@ -187,9 +187,13 @@ func waitForPortInTest(port int, t *testing.T) { // but executes actual client code. This means we don't spin up the real // mock service but execute our code in isolation. // +// Use this when you want too exercise the client code, but not shell out to Ruby. +// Where possible, you should consider creating a mockClient{} object and +// stubbing out the required behaviour. +// // Stubbing the exec.Cmd interface is hard, see fakeExec* functions for // the magic. -func createClient(success bool) (*PactClient, *ServiceMock) { +func createMockClient(success bool) (*PactClient, *ServiceMock) { execFunc := fakeExecSuccessCommand if !success { execFunc = fakeExecFailCommand diff --git a/dsl/matcher.go b/dsl/matcher.go index 06d50ccee..2790b61bb 100644 --- a/dsl/matcher.go +++ b/dsl/matcher.go @@ -240,6 +240,23 @@ func (m StructMatcher) GetValue() interface{} { // to also contain complex matchers type MapMatcher map[string]Matcher +// UnmarshalJSON is a custom JSON parser for MapMatcher +// It treats the matchers as strings +func (m *MapMatcher) UnmarshalJSON(bytes []byte) (err error) { + sk := make(map[string]string) + err = json.Unmarshal(bytes, &sk) + if err != nil { + return + } + + *m = make(map[string]Matcher) + for k, v := range sk { + (*m)[k] = String(v) + } + + return +} + // Takes an object and converts it to a JSON representation func objectToString(obj interface{}) string { switch content := obj.(type) { diff --git a/dsl/mock_client.go b/dsl/mock_client.go new file mode 100644 index 000000000..adb420a6d --- /dev/null +++ b/dsl/mock_client.go @@ -0,0 +1,57 @@ +package dsl + +import ( + "errors" + + "github.com/pact-foundation/pact-go/types" +) + +// Mock Client for testing the DSL package +type mockClient struct { +} + +// StartServer starts a remote Pact Mock Server. +func (p *mockClient) StartServer(args []string, port int) *types.MockServer { + return &types.MockServer{ + Pid: 0, + Port: 0, + } +} + +// ListServers lists all known Mock Servers +func (p *mockClient) ListServers() []*types.MockServer { + var servers []*types.MockServer + + return servers +} + +// StopServer stops a remote Pact Mock Server. +func (p *mockClient) StopServer(server *types.MockServer) (*types.MockServer, error) { + return nil, errors.New("failed stopping server") +} + +// RemoveAllServers stops all remote Pact Mock Servers. +func (p *mockClient) RemoveAllServers(server *types.MockServer) *[]types.MockServer { + return nil +} + +// VerifyProvider runs the verification process against a running Provider. +func (p *mockClient) VerifyProvider(request types.VerifyRequest) (types.ProviderVerifierResponse, error) { + return types.ProviderVerifierResponse{}, nil +} + +// UpdateMessagePact adds a pact message to a contract file +func (p *mockClient) UpdateMessagePact(request types.PactMessageRequest) error { + return nil +} + +// ReifyMessage takes a structured object, potentially containing nested Matchers +// and returns an object with just the example (generated) content +// The object may be a simple JSON primitive e.g. string or number or a complex object +func (p *mockClient) ReifyMessage(request *types.PactReificationRequest) (res *types.ReificationResponse, err error) { + return &types.ReificationResponse{ + Response: map[string]string{ + "foo": "bar", + }, + }, nil +} diff --git a/dsl/pact.go b/dsl/pact.go index e799bd3ec..8ee11a6a1 100644 --- a/dsl/pact.go +++ b/dsl/pact.go @@ -32,7 +32,7 @@ type Pact struct { Server *types.MockServer // Pact RPC Client. - pactClient *PactClient + pactClient Client // Consumer is the name of the Consumer/Client. Consumer string @@ -157,12 +157,9 @@ func (p *Pact) Setup(startMockServer bool) *Pact { } if p.pactClient == nil { - p.pactClient = NewClient() - p.pactClient.TimeoutDuration = p.ClientTimeout - } - - if p.PactFileWriteMode == "" { - p.PactFileWriteMode = "overwrite" + c := NewClient() + c.TimeoutDuration = p.ClientTimeout + p.pactClient = c } if p.PactFileWriteMode == "" { @@ -409,7 +406,7 @@ var checkCliCompatibility = func() { func stateHandlerMiddleware(stateHandlers types.StateHandlers) proxy.Middleware { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/__setup" { + if r.URL.Path == "/__setup" { var s *types.ProviderState decoder := json.NewDecoder(r.Body) decoder.Decode(&s) @@ -435,6 +432,8 @@ func stateHandlerMiddleware(stateHandlers types.StateHandlers) proxy.Middleware } log.Println("[DEBUG] skipping state handler for request", r.RequestURI) + + // Pass through to application next.ServeHTTP(w, r) }) } @@ -454,7 +453,12 @@ var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHand return } - json.Unmarshal(body, &message) + err = json.Unmarshal(body, &message) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } // Setup any provider state for _, state := range message.States { diff --git a/dsl/pact_test.go b/dsl/pact_test.go index 93904871d..c950bb68b 100644 --- a/dsl/pact_test.go +++ b/dsl/pact_test.go @@ -1,8 +1,12 @@ package dsl import ( + "errors" + "fmt" "io/ioutil" "log" + "net/http" + "net/http/httptest" "os" "strings" "testing" @@ -147,11 +151,11 @@ func TestPact_VerifyFail(t *testing.T) { err := pact.Verify(testFunc) if err == nil { - t.Fatalf("Expected error but got none") + t.Fatalf("want error but got none") } if !strings.Contains(err.Error(), "something went wrong") { - t.Fatalf("Expected response body to contain an error message 'something went wrong' but got '%s'", err.Error()) + t.Fatalf("expected response body to contain an error message 'something went wrong' but got '%s'", err.Error()) } } @@ -160,74 +164,75 @@ func TestPact_Setup(t *testing.T) { defer stubPorts()() pact.Setup(true) if pact.Server == nil { - t.Fatalf("Expected server to be created") + t.Fatalf("expected server to be created") } + pact2 := &Pact{LogLevel: "DEBUG"} pact2.Setup(false) if pact2.Server != nil { - t.Fatalf("Expected server to be nil") + t.Fatalf("expected server to be nil") } if pact2.pactClient == nil { - t.Fatalf("Needed to still have a client") + t.Fatalf("expected non-nil client") } } func TestPact_SetupWithMockServerPort(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) pact := &Pact{LogLevel: "DEBUG", AllowedMockServerPorts: "32768", pactClient: c} defer stubPorts()() pact.Setup(true) if pact.Server == nil { - t.Fatalf("Expected server to be created") + t.Fatalf("expected server to be created") } if pact.Server.Port != 32768 { - t.Fatalf("Expected mock daemon to be started on specific port") + t.Fatalf("expected mock daemon to be started on specific port") } } func TestPact_SetupWithMockServerPortCSV(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", AllowedMockServerPorts: "32768,32769", pactClient: c} pact.Setup(true) if pact.Server == nil { - t.Fatalf("Expected server to be created") + t.Fatalf("expected server to be created") } if pact.Server.Port != 32768 { - t.Fatalf("Expected mock daemon to be started on specific port") + t.Fatalf("expected mock daemon to be started on specific port") } } func TestPact_SetupWithMockServerPortRange(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", AllowedMockServerPorts: "32768-32770", pactClient: c} pact.Setup(true) if pact.Server == nil { - t.Fatalf("Expected server to be created") + t.Fatalf("expected server to be created") } if pact.Server.Port != 32768 { - t.Fatal("Expected mock daemon to be started on specific port, got", 32768) + t.Fatal("expected mock daemon to be started on specific port, got", 32768) } } func TestPact_Invalidrange(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", AllowedMockServerPorts: "abc-32770", pactClient: c} pact.Setup(true) if pact.Server == nil { - t.Fatalf("Expected server to be created") + t.Fatalf("expected server to be created") } if pact.Server.Port != 0 { - t.Fatalf("Expected mock daemon to be started on specific port") + t.Fatalf("expected mock daemon to be started on specific port") } } func TestPact_Teardown(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", pactClient: c} pact.Setup(true) @@ -237,14 +242,27 @@ func TestPact_Teardown(t *testing.T) { } } +func TestPact_TeardownFail(t *testing.T) { + c := &mockClient{} + pact := &Pact{LogLevel: "DEBUG", pactClient: c, Server: &types.MockServer{}} + pact.Teardown() +} + func TestPact_VerifyProviderRaw(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() + dummyMiddleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + }) + } + pact := &Pact{LogLevel: "DEBUG", pactClient: c} _, err := pact.VerifyProviderRaw(types.VerifyRequest{ ProviderBaseURL: "http://www.foo.com", PactURLs: []string{"foo.json", "bar.json"}, + RequestFilter: dummyMiddleware, }) if err != nil { @@ -253,7 +271,7 @@ func TestPact_VerifyProviderRaw(t *testing.T) { } func TestPact_VerifyProvider(t *testing.T) { - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() exampleTest := &testing.T{} pact := &Pact{LogLevel: "DEBUG", pactClient: c} @@ -267,8 +285,9 @@ func TestPact_VerifyProvider(t *testing.T) { t.Fatal("Error:", err) } } + func TestPact_VerifyProviderFail(t *testing.T) { - c, _ := createClient(false) + c, _ := createMockClient(false) defer stubPorts()() exampleTest := &testing.T{} pact := &Pact{LogLevel: "DEBUG", pactClient: c} @@ -283,10 +302,26 @@ func TestPact_VerifyProviderFail(t *testing.T) { } } +func TestPact_VerifyProviderFailBadURL(t *testing.T) { + c, _ := createMockClient(false) + defer stubPorts()() + exampleTest := &testing.T{} + pact := &Pact{LogLevel: "DEBUG", pactClient: c} + + _, err := pact.VerifyProvider(exampleTest, types.VerifyRequest{ + ProviderBaseURL: "http.", + PactURLs: []string{"foo.json", "bar.json"}, + }) + + if err == nil { + t.Fatal("want error, got nil") + } +} + func TestPact_VerifyProviderBroker(t *testing.T) { s := setupMockBroker(false) defer s.Close() - c, _ := createClient(true) + c, _ := createMockClient(true) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", pactClient: c, Provider: "bobby"} @@ -305,7 +340,7 @@ func TestPact_VerifyProviderBroker(t *testing.T) { func TestPact_VerifyProviderBrokerNoConsumers(t *testing.T) { s := setupMockBroker(false) defer s.Close() - c, _ := createClient(true) + c, _ := createMockClient(true) pact := &Pact{LogLevel: "DEBUG", pactClient: c, Provider: "providernotexist"} _, err := pact.VerifyProviderRaw(types.VerifyRequest{ @@ -314,12 +349,12 @@ func TestPact_VerifyProviderBrokerNoConsumers(t *testing.T) { }) if err == nil { - t.Fatalf("Expected error but got none") + t.Fatalf("expected error but got none") } } func TestPact_VerifyProviderRawFail(t *testing.T) { - c, _ := createClient(false) + c, _ := createMockClient(false) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", pactClient: c} _, err := pact.VerifyProviderRaw(types.VerifyRequest{ @@ -328,7 +363,7 @@ func TestPact_VerifyProviderRawFail(t *testing.T) { }) if err == nil { - t.Fatalf("Expected error but got none") + t.Fatalf("expected error but got none") } } @@ -351,7 +386,431 @@ func TestPact_AddInteraction(t *testing.T) { WillRespondWith(Response{}) if len(pact.Interactions) != 2 { - t.Fatalf("Expected 2 interactions to be added to Pact but got %d", len(pact.Interactions)) + t.Fatalf("expected 2 interactions to be added to Pact but got %d", len(pact.Interactions)) + } +} + +func TestPact_StateHandlerMiddlewareStateHandlerExists(t *testing.T) { + var called bool + + handlers := map[string]types.StateHandler{ + "state x": func() error { + called = true + + return nil + }, + } + + // TODO: parameterise the __setup + req, err := http.NewRequest("POST", "/__setup", strings.NewReader(`{ + "states": ["state x"], + "consumer": "test", + "provider": "provider" + }`)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + mw := stateHandlerMiddleware(handlers) + mw(dummyHandler("X-Dummy-Handler")).ServeHTTP(rr, req) + + // Expect state handler + if !called { + t.Error("expected state handler to have been called") + } + + // expect http handler to NOT have been called + if h := rr.HeaderMap.Get("X-Dummy-Handler"); h == "true" { + t.Error("expected http handler to not be invoked") + } +} + +func TestPact_StateHandlerMiddlewareStateHandlerNotExists(t *testing.T) { + var called bool + + handlers := map[string]types.StateHandler{} + + // TODO: parameterise the __setup + req, err := http.NewRequest("POST", "/__setup", strings.NewReader(`{ + "states": ["state x"], + "consumer": "test", + "provider": "provider" + }`)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + mw := stateHandlerMiddleware(handlers) + mw(dummyHandler("X-Dummy-Handler")).ServeHTTP(rr, req) + + // Expect state handler + if called { + t.Error("expected state handler to not have been called") + } + + // expect http handler to NOT have been called + if h := rr.HeaderMap.Get("X-Dummy-Handler"); h == "true" { + t.Error("expected http handler to not be invoked") + } +} + +func TestPact_StateHandlerMiddlewareStateHandlerError(t *testing.T) { + handlers := map[string]types.StateHandler{ + "state x": func() error { + return errors.New("handler error") + }, + } + + // TODO: parameterise the __setup + req, err := http.NewRequest("POST", "/__setup", strings.NewReader(`{ + "states": ["state x"], + "consumer": "test", + "provider": "provider" + }`)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + mw := stateHandlerMiddleware(handlers) + mw(dummyHandler("X-Dummy-Handler")).ServeHTTP(rr, req) + + // expect 500 + if status := rr.Code; status != http.StatusInternalServerError { + t.Errorf("want statusCode to be 500, goto %v", status) + } + + // expect http handler to NOT have been called + if h := rr.HeaderMap.Get("X-Dummy-Handler"); h == "true" { + t.Error("expected http handler to not be invoked") + } +} + +func TestPact_StateHandlerMiddlewarePassThroughInvalidPath(t *testing.T) { + handlers := map[string]types.StateHandler{} + + // TODO: parameterise the __setup + req, err := http.NewRequest("POST", "/someotherpath", strings.NewReader(`{ }`)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + mw := stateHandlerMiddleware(handlers) + mw(dummyHandler("X-Dummy-Handler")).ServeHTTP(rr, req) + + // expect http handler to have been called + if h := rr.HeaderMap.Get("X-Dummy-Handler"); h != "true" { + t.Errorf("expected target http handler to be invoked") + } +} + +func dummyHandler(header string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(header, "true") + }) +} + +func TestPact_AddMessage(t *testing.T) { + // AddMessage is a fairly useless method, currently, + // but is reserved for future API usage. + pact := &Pact{} + pact.AddMessage() + + if len(pact.MessageInteractions) != 1 { + t.Errorf("expected pact to have 1 Message Interaction but got %v", len(pact.MessageInteractions)) + } + +} + +var message = `{ + "providerStates": [ + { + "name": "state x" + } + ], + "metadata": { + "content-type": "application-json" + }, + "content": {"foo": "bar"}, + "description": "a message" + }` + +func createMessageHandlers(invoked *int, err error) MessageHandlers { + return map[string]MessageHandler{ + "a message": func(m Message) (interface{}, error) { + *invoked++ + fmt.Println("message handler") + + return nil, err + }, + } +} + +func createStateHandlers(invoked *int, err error) StateHandlers { + return map[string]StateHandler{ + "state x": func(s State) error { + fmt.Println("state handler") + *invoked++ + + return err + }, + } +} + +func TestPact_MessageVerificationHandler(t *testing.T) { + var called = 0 + + req, err := http.NewRequest("POST", "/", strings.NewReader(message)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + h := messageVerificationHandler(createMessageHandlers(&called, nil), createStateHandlers(&called, nil)) + h.ServeHTTP(rr, req) + + // Expect state handler + if called != 2 { + t.Error("expected state handler and message handler to have been called", called) + } +} + +func TestPact_MessageVerificationHandlerInvalidMessage(t *testing.T) { + var called = 0 + + req, err := http.NewRequest("POST", "/", strings.NewReader("{broken")) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + h := messageVerificationHandler(createMessageHandlers(&called, nil), createStateHandlers(&called, nil)) + h.ServeHTTP(rr, req) + + // Expect state handler + if rr.Code != http.StatusBadRequest { + t.Errorf("expected 500 but got %v", rr.Code) + } +} + +func TestPact_MessageVerificationHandlerMessageNotFound(t *testing.T) { + var called = 0 + var badMessage = `{ + "content": {"foo": "bar"}, + "description": "a message not found" + }` + + req, err := http.NewRequest("POST", "/", strings.NewReader(badMessage)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + h := messageVerificationHandler(createMessageHandlers(&called, nil), createStateHandlers(&called, nil)) + h.ServeHTTP(rr, req) + + // Expect state handler + if rr.Code != http.StatusNotFound { + t.Errorf("expected 404 but got %v", rr.Code) + } +} + +func TestPact_MessageVerificationHandlerStateHandlerFail(t *testing.T) { + var called = 0 + + req, err := http.NewRequest("POST", "/", strings.NewReader(message)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + h := messageVerificationHandler(createMessageHandlers(&called, nil), createStateHandlers(&called, errors.New("state handler failed"))) + h.ServeHTTP(rr, req) + + // Expect state handler + if rr.Code != http.StatusInternalServerError { + t.Errorf("expected 500 but got %v", rr.Code) + } +} + +func TestPact_MessageVerificationHandlerMessageHandlerFail(t *testing.T) { + var called = 0 + + req, err := http.NewRequest("POST", "/", strings.NewReader(message)) + + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + h := messageVerificationHandler(createMessageHandlers(&called, errors.New("message handler failed")), createStateHandlers(&called, nil)) + h.ServeHTTP(rr, req) + + // Expect state handler + if rr.Code != http.StatusServiceUnavailable { + t.Errorf("expected 503 but got %v", rr.Code) + } +} + +func TestPact_VerifyMessageConsumerRawFailToReify(t *testing.T) { + pact := &Pact{} + + message := pact.AddMessage() + message. + Given("user with id 1 exists"). + ExpectsToReceive("a user"). + WithContent(map[string]interface{}{ + "id": Like(1), + }) + + client, _ := createMockClient(false) + + pact.pactClient = client + + var invoked bool + h := func(m Message) error { + invoked = true + return nil + } + err := pact.VerifyMessageConsumerRaw(message, h) + + if err == nil { + t.Fatalf("expected error but got none") + } + + if invoked { + t.Fatalf("expected handler not to be invoked") + } +} + +func TestPact_VerifyMessageConsumerRawSuccess(t *testing.T) { + pact := &Pact{} + + message := pact.AddMessage() + message. + Given("user with id 1 exists"). + ExpectsToReceive("a user"). + WithContent(map[string]interface{}{ + "foo": "bar", + }) + // TODO: replace the createMockClient below and mock out properly + // AsType(idType{}) + + c, _ := createMockClient(true) + + pact.pactClient = c + + var invoked bool + h := func(m Message) error { + invoked = true + return nil + } + + err := pact.VerifyMessageConsumerRaw(message, h) + + if err != nil { + t.Fatalf("expected no error but got %v", err) + } + + if !invoked { + t.Fatalf("expected handler to be invoked") + } +} + +func TestPact_VerifyMessageConsumerFail(t *testing.T) { + pact := &Pact{} + + message := pact.AddMessage() + message. + Given("user with id 1 exists"). + ExpectsToReceive("a user"). + WithContent(map[string]interface{}{ + "foo": "bar", + }) + + c, _ := createMockClient(true) + + pact.pactClient = c + + h := func(m Message) error { + return errors.New("message handler failed") + } + exampleTest := &testing.T{} + err := pact.VerifyMessageConsumer(exampleTest, message, h) + + if err == nil { + t.Fatalf("expected error but got none") + } +} + +func TestPact_VerifyMessageConsumerSuccess(t *testing.T) { + pact := &Pact{} + + message := pact.AddMessage() + message. + Given("user with id 1 exists"). + ExpectsToReceive("a user"). + WithContent(map[string]interface{}{ + "foo": "bar", + }) + + c, _ := createMockClient(true) + + pact.pactClient = c + + var invoked bool + h := func(m Message) error { + invoked = true + return nil + } + exampleTest := &testing.T{} + err := pact.VerifyMessageConsumer(exampleTest, message, h) + + if err != nil { + t.Fatalf("expected no error but got %v", err) + } + + if !invoked { + t.Fatalf("expected handler to be invoked") + } +} + +func TestPact_VerifyMessageProviderSuccess(t *testing.T) { + c, _ := createMockClient(true) + var called = 0 + defer stubPorts()() + exampleTest := &testing.T{} + + pact := &Pact{LogLevel: "DEBUG", pactClient: c} + + _, err := pact.VerifyMessageProvider(exampleTest, VerifyMessageRequest{ + PactURLs: []string{"foo.json", "bar.json"}, + MessageHandlers: createMessageHandlers(&called, nil), + StateHandlers: createStateHandlers(&called, nil), + }) + + if err != nil { + t.Fatal("Error:", err) } } diff --git a/proxy/http_test.go b/proxy/http_test.go index 943b369ff..47700ca76 100644 --- a/proxy/http_test.go +++ b/proxy/http_test.go @@ -1 +1,91 @@ package proxy + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +func dummyHandler(header string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(header, "true") + }) +} + +func DummyMiddleware(header string) Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(header, "true") + next.ServeHTTP(w, r) + }) + } +} + +func TestLoggingMiddleware(t *testing.T) { + req, err := http.NewRequest("GET", "/x", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + loggingMiddleware(dummyHandler("X-Dummy-Handler")).ServeHTTP(rr, req) + + if h := rr.HeaderMap.Get("X-Dummy-Handler"); h != "true" { + t.Errorf("expected handler to set the header 'X-Dummy-Handler: true' but got '%v'", + h) + } +} + +func TestChainHandlers(t *testing.T) { + req, err := http.NewRequest("GET", "/health-check", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + + headers := []string{ + "1", + "2", + "3", + "X-Dummy-Handler", + } + mw := []Middleware{ + DummyMiddleware("1"), + DummyMiddleware("2"), + DummyMiddleware("3"), + DummyMiddleware("X-Dummy-Handler"), + } + + middlewareChain := chainHandlers(mw...) + middlewareChain(dummyHandler("X-Dummy-Handler")).ServeHTTP(rr, req) + + for _, h := range headers { + if v := rr.HeaderMap.Get(h); v != "true" { + t.Errorf("expected handler to set the header '%v: true' but got '%v'", + h, v) + } + } +} + +func TestHTTPReverseProxy(t *testing.T) { + + // Setup target to proxy + port, err := HTTPReverseProxy(Options{ + Middleware: []Middleware{ + DummyMiddleware("1"), + }, + TargetScheme: "http", + TargetAddress: fmt.Sprintf("127.0.0.1:1234"), + }) + + if err != nil { + t.Errorf("unexpected error %v", err) + } + + if port == 0 { + t.Errorf("want non-zero port, got %v", port) + } +}