From f4f1d4c312c5e55abe9578154bd75b43d06d6a66 Mon Sep 17 00:00:00 2001 From: Rick <1450685+LinuxSuRen@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:29:52 +0800 Subject: [PATCH] feat: support combine mock and core server together (#390) Co-authored-by: rick --- cmd/server.go | 20 ++++++++++++- cmd/server_test.go | 12 ++++++++ go.mod | 4 --- pkg/mock/in_memory.go | 15 ++++++++-- pkg/mock/in_memory_test.go | 2 +- pkg/mock/server.go | 3 ++ pkg/server/http.go | 57 ++++++++++++++++++++++++++++++++++++++ pkg/server/http_test.go | 41 +++++++++++++++++++++++++++ 8 files changed, 145 insertions(+), 9 deletions(-) diff --git a/cmd/server.go b/cmd/server.go index 9e02e51..31df764 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -37,6 +37,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/linuxsuren/api-testing/pkg/logging" + "github.com/linuxsuren/api-testing/pkg/mock" "github.com/linuxsuren/api-testing/pkg/oauth" template "github.com/linuxsuren/api-testing/pkg/render" "github.com/linuxsuren/api-testing/pkg/server" @@ -88,6 +89,7 @@ func createServerCmd(execer fakeruntime.Execer, httpServer server.HTTPServer) (c flags.StringVarP(&opt.clientID, "client-id", "", os.Getenv("OAUTH_CLIENT_ID"), "ClientID is the application's ID") flags.StringVarP(&opt.clientSecret, "client-secret", "", os.Getenv("OAUTH_CLIENT_SECRET"), "ClientSecret is the application's secret") flags.BoolVarP(&opt.dryRun, "dry-run", "", false, "Do not really start a gRPC server") + flags.StringArrayVarP(&opt.mockConfig, "mock-config", "", nil, "The mock config files") // gc related flags flags.IntVarP(&opt.gcPercent, "gc-percent", "", 100, "The GC percent of Go") @@ -121,6 +123,8 @@ type serverOption struct { oauthSkipTls bool oauthGroup []string + mockConfig []string + gcPercent int dryRun bool @@ -283,10 +287,24 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) { promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}).ServeHTTP(w, r) }) + combineHandlers := server.NewDefaultCombineHandler() + combineHandlers.PutHandler("", mux) + + if len(o.mockConfig) > 0 { + cmd.Println("currently only one mock config is supported, will take the first one") + var mockServerHandler http.Handler + if mockServerHandler, err = mock.NewInMemoryServer(0). + SetupHandler(mock.NewLocalFileReader(o.mockConfig[0])); err != nil { + return + } + combineHandlers.PutHandler("/mock", mockServerHandler) + } + debugHandler(mux, remoteServer) - o.httpServer.WithHandler(mux) + o.httpServer.WithHandler(combineHandlers.GetHandler()) serverLogger.Info("HTTP server listening at", "addr", httplis.Addr()) serverLogger.Info("Server is running.") + err = o.httpServer.Serve(httplis) err = util.IgnoreErrServerClosed(err) } diff --git a/cmd/server_test.go b/cmd/server_test.go index 19ab09e..d604ed8 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -57,6 +57,18 @@ func TestPrintProto(t *testing.T) { verify: func(t *testing.T, buf *bytes.Buffer, err error) { assert.Nil(t, err) }, + }, { + name: "mock server, not found config", + args: []string{"server", "--mock-config=fake", "-p=0", "--http-port=0"}, + verify: func(t *testing.T, buffer *bytes.Buffer, err error) { + assert.Error(t, err) + }, + }, { + name: "mock server, normal", + args: []string{"server", "--mock-config=testdata/invalid-api.yaml", "-p=0", "--http-port=0"}, + verify: func(t *testing.T, buffer *bytes.Buffer, err error) { + assert.NoError(t, err) + }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/go.mod b/go.mod index 36b43de..9b7664d 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,6 @@ require ( github.com/go-logr/zapr v1.3.0 github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 - github.com/gorilla/mux v1.8.1 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 github.com/h2non/gock v1.2.0 github.com/invopop/jsonschema v0.7.0 github.com/jhump/protoreflect v1.15.3 @@ -48,8 +46,6 @@ require ( github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.2 // indirect diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index f3524ec..617d1e0 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -49,7 +49,7 @@ func NewInMemoryServer(port int) DynamicServer { } } -func (s *inMemoryServer) Start(reader Reader) (err error) { +func (s *inMemoryServer) SetupHandler(reader Reader) (handler http.Handler, err error) { var server *Server if server, err = reader.Parse(); err != nil { return @@ -57,7 +57,8 @@ func (s *inMemoryServer) Start(reader Reader) (err error) { // init the data s.data = make(map[string][]map[string]interface{}) - s.mux = mux.NewRouter() + s.mux = mux.NewRouter().PathPrefix("/mock").Subrouter() + handler = s.mux memLogger.Info("start to run all the APIs from objects") for _, obj := range server.Objects { @@ -69,10 +70,18 @@ func (s *inMemoryServer) Start(reader Reader) (err error) { for _, item := range server.Items { s.startItem(item) } + return +} + +func (s *inMemoryServer) Start(reader Reader) (err error) { + var handler http.Handler + if handler, err = s.SetupHandler(reader); err != nil { + return + } s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", s.port)) go func() { - err = http.Serve(s.listener, s.mux) + err = http.Serve(s.listener, handler) }() return } diff --git a/pkg/mock/in_memory_test.go b/pkg/mock/in_memory_test.go index 2838c02..926f1fd 100644 --- a/pkg/mock/in_memory_test.go +++ b/pkg/mock/in_memory_test.go @@ -33,7 +33,7 @@ func TestInMemoryServer(t *testing.T) { server.Stop() }() - api := "http://localhost:" + server.GetPort() + api := "http://localhost:" + server.GetPort() + "/mock" _, err = http.Post(api+"/team", "", bytes.NewBufferString(`{ "name": "test", diff --git a/pkg/mock/server.go b/pkg/mock/server.go index 7e9f342..7787af0 100644 --- a/pkg/mock/server.go +++ b/pkg/mock/server.go @@ -15,8 +15,11 @@ limitations under the License. */ package mock +import "net/http" + type DynamicServer interface { Start(reader Reader) error + SetupHandler(reader Reader) (http.Handler, error) Stop() error GetPort() string } diff --git a/pkg/server/http.go b/pkg/server/http.go index 12063e5..cb06fd2 100644 --- a/pkg/server/http.go +++ b/pkg/server/http.go @@ -1,9 +1,25 @@ +/* +Copyright 2024 API Testing Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package server import ( context "context" "net" "net/http" + "strings" ) // HTTPServer is an interface for serving HTTP requests @@ -13,6 +29,11 @@ type HTTPServer interface { Shutdown(ctx context.Context) error } +type CombineHandler interface { + PutHandler(string, http.Handler) + GetHandler() http.Handler +} + type defaultHTTPServer struct { server *http.Server handler http.Handler @@ -37,6 +58,42 @@ func (s *defaultHTTPServer) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } +type defaultCombineHandler struct { + handlerMapping map[string]http.Handler + defaultHandler http.Handler +} + +func NewDefaultCombineHandler() CombineHandler { + return &defaultCombineHandler{ + handlerMapping: make(map[string]http.Handler), + } +} + +func (s *defaultCombineHandler) PutHandler(name string, handler http.Handler) { + if name == "" { + s.defaultHandler = handler + } else { + s.handlerMapping[name] = handler + } +} + +func (s *defaultCombineHandler) GetHandler() http.Handler { + if len(s.handlerMapping) == 0 { + return s.defaultHandler + } + return s +} + +func (s *defaultCombineHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + for prefix, handler := range s.handlerMapping { + if strings.HasPrefix(r.URL.Path, prefix) { + handler.ServeHTTP(w, r) + return + } + } + s.defaultHandler.ServeHTTP(w, r) +} + type fakeHandler struct{} // NewFakeHTTPServer creates a fake HTTP server diff --git a/pkg/server/http_test.go b/pkg/server/http_test.go index 716a50e..115e3cc 100644 --- a/pkg/server/http_test.go +++ b/pkg/server/http_test.go @@ -1,7 +1,23 @@ +/* +Copyright 2024 API Testing Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package server_test import ( "net" + "net/http" "testing" "github.com/linuxsuren/api-testing/pkg/server" @@ -19,3 +35,28 @@ func TestHTTPServer(t *testing.T) { defaultHTTPServer := server.NewDefaultHTTPServer() defaultHTTPServer.WithHandler(nil) } + +func TestCombineHandler(t *testing.T) { + defaultHandler := http.NewServeMux() + fakeHandler := http.NewServeMux() + + t.Run("correct default handler", func(t *testing.T) { + combineHandler := server.NewDefaultCombineHandler() + + combineHandler.PutHandler("", defaultHandler) + combineHandler.PutHandler("/fake", fakeHandler) + + assert.NotEqual(t, defaultHandler, combineHandler.GetHandler()) + + fakeServer := server.NewFakeHTTPServer() + assert.Nil(t, fakeServer.Shutdown(nil)) + }) + + t.Run("only one default handler", func(t *testing.T) { + combineHandler := server.NewDefaultCombineHandler() + + combineHandler.PutHandler("", defaultHandler) + + assert.Equal(t, defaultHandler, combineHandler.GetHandler()) + }) +}