diff options
author | john.mcdonnell <jmcdonnell@gitlab.com> | 2022-04-01 06:32:31 +0300 |
---|---|---|
committer | john.mcdonnell <jmcdonnell@gitlab.com> | 2022-04-01 06:32:31 +0300 |
commit | cf490eb06969efd7bfb16c341adb98f986fd8aad (patch) | |
tree | 94ad43feee3953e977c198fc2a41e11030f16d20 | |
parent | ffa6861c2b9f66ed22ebdfa6bc7a7319c47a1ea3 (diff) |
Adding POC pact test for gitalyjmd-pact-testing-poc
-rw-r--r-- | testing/pacts/README.MD | 33 | ||||
-rw-r--r-- | testing/pacts/consumer_test.go | 104 | ||||
-rw-r--r-- | testing/pacts/pacts/createrepository-createrepository.json | 38 | ||||
-rw-r--r-- | testing/pacts/provider_test.go | 56 | ||||
-rw-r--r-- | testing/pacts/server.go | 90 |
5 files changed, 321 insertions, 0 deletions
diff --git a/testing/pacts/README.MD b/testing/pacts/README.MD new file mode 100644 index 000000000..89879211e --- /dev/null +++ b/testing/pacts/README.MD @@ -0,0 +1,33 @@ +We can use the [grpc-gateway](https://grpc-ecosystem.github.io/grpc-gateway/) which is a reverse-proxy to translate HTTP API into gRPC. +That would give use a HTTP endpoint that would be more suited to use with the curent tooling available in Pact. + +To generate HTTP endpoints run the following command from the `proto` directory + +``` +protoc -I . --grpc-gateway_out go/gitalypb \ + --grpc-gateway_opt logtostderr=true \ + --grpc-gateway_opt paths=source_relative \ + --grpc-gateway_opt generate_unbound_methods=true \ + *.proto +``` + + +When running thest tests, you must have a praefect server running. +For initial simplicity, we can use the GDK on own localhost. This can be improved later. + +- Configure praefect to run on localhost using `listen_addr` as opposed to `socket_path` in {GITALY_DIR}/praefect.config.toml, and restart GDK +``` +# # TCP address to listen on +listen_addr = "192.168.178.33:2305" + +# # Praefect can listen on a socket when placed on the same machine as all clients +# socket_path = "/Users/john/dev/gdk/praefect.socket" +``` + +- Modify `testing/pacts/server.go grpcServerEndpoint` accordingly to ensure you point at your praefect endpoint. + +- Run the tests + ``` + cd {GITALY_DIR}/testing/pacts + go test -v + ```
\ No newline at end of file diff --git a/testing/pacts/consumer_test.go b/testing/pacts/consumer_test.go new file mode 100644 index 000000000..6f749afb5 --- /dev/null +++ b/testing/pacts/consumer_test.go @@ -0,0 +1,104 @@ +package pacts + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "log" + "math/rand" + "net/http" + "strings" + "testing" + "time" + + "github.com/google/uuid" + "github.com/pact-foundation/pact-go/dsl" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +type Repository struct { + RelativePath string `json:"relativePath" pact:"relativePath=@hashed/ab/cd/abcdefghij.git"` + StorageName string `json:"storageName" pact:"storageName=default"` +} + +type CreateRepository struct { + Repository Repository `json:"repository"` + DefaultBranch string `json:"defaultBranch" pact:"defaultBranch=bWFpbg=="` +} + +func (c *CreateRepository) String() string { + j, _ := json.Marshal(c) + return string(j) +} + +func TestConsumer(t *testing.T) { + uniqueProjectPath := uuid.New().String() + + // Create Pact connecting to local Daemon + pact := &dsl.Pact{ + Consumer: "CreateRepository", + Provider: "CreateRepository", + Host: "localhost", + } + defer pact.Teardown() + + // Pass in test case + test := func() error { + u := fmt.Sprintf("http://localhost:%d/gitaly.RepositoryService/CreateRepository", pact.Server.Port) + req, err := http.NewRequest(http.MethodPost, u, strings.NewReader( + (&CreateRepository{ + Repository: Repository{ + RelativePath: uniqueProjectPath, + StorageName: "default", + }, + DefaultBranch: base64.StdEncoding.EncodeToString([]byte("main")), + }).String(), + )) + + // NOTE: by default, request bodies are expected to be sent with a Content-Type + // of application/json. If you don't explicitly set the content-type, you + // will get a mismatch during Verification. + req.Header.Set("Content-Type", "application/json") + + if err != nil { + return err + } + if _, err = http.DefaultClient.Do(req); err != nil { + return err + } + + return err + } + + // Set up our expected interactions. + pact. + AddInteraction(). + Given("Repository does not exist"). + UponReceiving("A request to CreateRepository"). + WithRequest(dsl.Request{ + Method: http.MethodPost, + Path: dsl.String("/gitaly.RepositoryService/CreateRepository"), + Body: map[string]interface{}{ + "defaultBranch": base64.StdEncoding.EncodeToString([]byte("main")), + "repository": map[string]string{ + "relativePath": uniqueProjectPath, + "storageName": "default", + }, + }, + }). + WillRespondWith(dsl.Response{ + Status: 200, + Headers: dsl.MapMatcher{ + "Content-Type": dsl.String("application/json"), + }, + Body: map[string]interface{}{}, + }) + + // Verify + if err := pact.Verify(test); err != nil { + log.Fatalf("Error on Verify: %v", err) + } +} diff --git a/testing/pacts/pacts/createrepository-createrepository.json b/testing/pacts/pacts/createrepository-createrepository.json new file mode 100644 index 000000000..96d06c23c --- /dev/null +++ b/testing/pacts/pacts/createrepository-createrepository.json @@ -0,0 +1,38 @@ +{ + "consumer": { + "name": "CreateRepository" + }, + "provider": { + "name": "CreateRepository" + }, + "interactions": [ + { + "description": "A request to CreateRepository", + "providerState": "Repository does not exist", + "request": { + "method": "POST", + "path": "/gitaly.RepositoryService/CreateRepository", + "body": { + "defaultBranch": "bWFpbg==", + "repository": { + "relativePath": "ae300491-9af0-49a8-9d52-c8e685524fb4", + "storageName": "default" + } + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + } + } + } + ], + "metadata": { + "pactSpecification": { + "version": "2.0.0" + } + } +}
\ No newline at end of file diff --git a/testing/pacts/provider_test.go b/testing/pacts/provider_test.go new file mode 100644 index 000000000..4aefe772e --- /dev/null +++ b/testing/pacts/provider_test.go @@ -0,0 +1,56 @@ +package pacts + +import ( + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/pact-foundation/pact-go/dsl" + "github.com/pact-foundation/pact-go/types" +) + +var ( + dir, _ = os.Getwd() + pactDir = fmt.Sprintf("%s/pacts", dir) +) + +func TestProvider(t *testing.T) { + // Create Pact connecting to local Daemon + pact := &dsl.Pact{ + Consumer: "MyConsumer", + Provider: "MyProvider", + } + + // Start provider API in the background + go startServer() + + // Authorization middleware + // This is your chance to modify the request before it hits your provider + // NOTE: this should be used very carefully, as it has the potential to + // _change_ the contract + f := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + }) + } + + // Verify the Provider with local Pact Files + pact.VerifyProvider(t, types.VerifyRequest{ + ProviderBaseURL: "http://localhost:8081", + PactURLs: []string{filepath.ToSlash(fmt.Sprintf("%s/createrepository-createrepository.json", pactDir))}, + CustomProviderHeaders: []string{"X-API-Token: abcd"}, + RequestFilter: f, + StateHandlers: types.StateHandlers{ + "User foo exists": func() error { + return nil + }, + }, + }) +} + +func startServer() { + log.Fatal(PactServerRun()) +} diff --git a/testing/pacts/server.go b/testing/pacts/server.go new file mode 100644 index 000000000..3c1bea00d --- /dev/null +++ b/testing/pacts/server.go @@ -0,0 +1,90 @@ +package pacts + +import ( + "context" + "flag" + "log" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + // Update +) + +// command-line options: +// gRPC server endpoint +var grpcServerEndpoint = flag.String("grpc-server-endpoint", "192.168.178.33:2305", "gRPC server endpoint") + +func PactServerRun() error { + log.Println(("Running grpc-server")) + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Register gRPC server endpoint + // Note: Make sure the gRPC server is running properly and accessible + mux := runtime.NewServeMux() + opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + if err := gitalypb.RegisterConflictsServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterSSHServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterServerServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterRepositoryServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterHookServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterDiffServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterObjectPoolServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterInternalGitalyHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterBlobServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterOperationServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterNamespaceServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterCleanupServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterWikiServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterSmartHTTPServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterRefTransactionHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterCommitServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterRefServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterRemoteServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + if err := gitalypb.RegisterPraefectInfoServiceHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts); err != nil { + panic(err) + } + + // Start HTTP server (and proxy calls to gRPC server endpoint) + return http.ListenAndServe(":8081", mux) +} |