From 79419ffdd8fa114e42ca2acb94f6ae7187ee48a4 Mon Sep 17 00:00:00 2001 From: rick Date: Wed, 30 Oct 2024 21:33:55 +0800 Subject: [PATCH 1/5] feat: imporove mock object with standard crud --- .../content/zh/latest/tasks/mock/simple.yaml | 17 +++++ pkg/mock/in_memory.go | 68 +++++++++++-------- pkg/mock/in_memory_test.go | 2 +- 3 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 docs/site/content/zh/latest/tasks/mock/simple.yaml diff --git a/docs/site/content/zh/latest/tasks/mock/simple.yaml b/docs/site/content/zh/latest/tasks/mock/simple.yaml new file mode 100644 index 00000000..02880b6c --- /dev/null +++ b/docs/site/content/zh/latest/tasks/mock/simple.yaml @@ -0,0 +1,17 @@ +#!api-testing-mock +# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json +objects: + - name: users + initCount: 1 + sample: | + { + "username": "sss", + "email": "{{randEmail}}", + "age": 12 + } + - name: roles + sample: | + { + "name": "admin", + "description": "this is a role" + } \ No newline at end of file diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index c51ca5e0..f68e6a8b 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -48,6 +48,7 @@ type inMemoryServer struct { mux *mux.Router listener net.Listener port int + prefix string wg sync.WaitGroup ctx context.Context cancelFunc context.CancelFunc @@ -69,6 +70,7 @@ func (s *inMemoryServer) SetupHandler(reader Reader, prefix string) (handler htt // init the data s.data = make(map[string][]map[string]interface{}) s.mux = mux.NewRouter().PathPrefix(prefix).Subrouter() + s.prefix = prefix handler = s.mux err = s.Load() return @@ -120,7 +122,45 @@ func (s *inMemoryServer) Start(reader Reader, prefix string) (err error) { func (s *inMemoryServer) startObject(obj Object) { // create a simple CRUD server + s.mux.HandleFunc(fmt.Sprintf("/%s/{%s}", obj.Name, obj.Name), func(w http.ResponseWriter, req *http.Request) { + fmt.Println("mock server received request", req.URL.Path) + method := req.Method + w.Header().Set(util.ContentType, util.JSON) + + objName := strings.TrimPrefix(req.URL.Path, s.prefix+"/"+obj.Name+"/") + + switch method { + case http.MethodGet: + // list all items + for _, item := range s.data[obj.Name] { + if item["name"] == objName { + data, err := json.Marshal(item) + writeResponse(w, data, err) + return + } + } + case http.MethodDelete: + // delete an item + for i, item := range s.data[obj.Name] { + if item["name"] == objName { + if len(s.data[obj.Name]) == i+1 { + s.data[obj.Name] = s.data[obj.Name][:i] + } else { + s.data[obj.Name] = append(s.data[obj.Name][:i], s.data[obj.Name][i+1]) + } + + writeResponse(w, []byte(`{"msg": "deleted"}`), nil) + return + } + } + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + writeResponse(w, []byte(`{"msg": "not found"}`), fmt.Errorf("not found")) + }) s.mux.HandleFunc("/"+obj.Name, func(w http.ResponseWriter, req *http.Request) { + fmt.Println("mock server received request", req.URL.Path) method := req.Method w.Header().Set(util.ContentType, util.JSON) @@ -168,32 +208,6 @@ func (s *inMemoryServer) startObject(obj Object) { s.data[obj.Name] = append(s.data[obj.Name], objData) - _, _ = w.Write(data) - } else { - memLogger.Info("failed to read from body", "error", err) - } - case http.MethodDelete: - // delete an item - if data, err := io.ReadAll(req.Body); err == nil { - objData := map[string]interface{}{} - - jsonErr := json.Unmarshal(data, &objData) - if jsonErr != nil { - memLogger.Info(jsonErr.Error()) - return - } - - for i, item := range s.data[obj.Name] { - if objData["name"] == item["name"] { - if len(s.data[obj.Name]) == i+1 { - s.data[obj.Name] = s.data[obj.Name][:i] - } else { - s.data[obj.Name] = append(s.data[obj.Name][:i], s.data[obj.Name][i+1]) - } - break - } - } - _, _ = w.Write(data) } else { memLogger.Info("failed to read from body", "error", err) @@ -220,7 +234,7 @@ func (s *inMemoryServer) startObject(obj Object) { memLogger.Info("failed to read from body", "error", err) } default: - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusMethodNotAllowed) } }) diff --git a/pkg/mock/in_memory_test.go b/pkg/mock/in_memory_test.go index 5edfe999..d5144c72 100644 --- a/pkg/mock/in_memory_test.go +++ b/pkg/mock/in_memory_test.go @@ -118,7 +118,7 @@ func TestInMemoryServer(t *testing.T) { assert.NoError(t, err) resp, err = http.DefaultClient.Do(delReq) assert.NoError(t, err) - assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) }) t.Run("only accept GET method in getting a single object", func(t *testing.T) { From b085a16bdb19582d50c3659886101ba59a2da3f1 Mon Sep 17 00:00:00 2001 From: rick Date: Thu, 31 Oct 2024 02:23:44 +0000 Subject: [PATCH 2/5] add more document of mock --- docs/site/content/zh/about/index.md | 3 +- .../content/zh/latest/tasks/mock-server.md | 17 --- docs/site/content/zh/latest/tasks/mock.md | 102 ++++++++++++++---- .../zh/latest/tasks}/mock/image-registry.yaml | 0 .../content/zh/latest/tasks/mock/object.yaml | 10 ++ .../content/zh/latest/tasks/mock/simple.yaml | 45 +++++--- pkg/mock/in_memory.go | 80 ++++++-------- pkg/mock/in_memory_test.go | 12 ++- 8 files changed, 162 insertions(+), 107 deletions(-) delete mode 100644 docs/site/content/zh/latest/tasks/mock-server.md rename docs/{ => site/content/zh/latest/tasks}/mock/image-registry.yaml (100%) create mode 100644 docs/site/content/zh/latest/tasks/mock/object.yaml diff --git a/docs/site/content/zh/about/index.md b/docs/site/content/zh/about/index.md index 0a5aaab4..32de6487 100644 --- a/docs/site/content/zh/about/index.md +++ b/docs/site/content/zh/about/index.md @@ -5,7 +5,7 @@ linkTitle: 关于 {{% blocks/cover title="About API Testing" height="auto" %}} -API Testing 是一个接口调试和测试工具的开源项目。 +API Testing (atest)是一个接口调试和测试工具的开源项目。 请继续阅读以了解更多信息,或访问我们的 [文档](/latest/) 开始使用! @@ -20,4 +20,3 @@ API Testing 是一个接口调试和测试工具的开源项目。 // TBD. {{% /blocks/section %}} - diff --git a/docs/site/content/zh/latest/tasks/mock-server.md b/docs/site/content/zh/latest/tasks/mock-server.md deleted file mode 100644 index 2e10ccbd..00000000 --- a/docs/site/content/zh/latest/tasks/mock-server.md +++ /dev/null @@ -1,17 +0,0 @@ -+++ -title = "Mock Server 功能使用" -+++ - -## Get started - -您可以通过执行下面的命令 mock 一个容器仓库服务[container registry](https://distribution.github.io/distribution/): - -```shell -atest mock --prefix / mock/image-registry.yaml -``` - -之后,您可以通过使用如下的命令使用 mock 功能。 - -```shell -docker pull localhost:6060/repo/name:tag -``` diff --git a/docs/site/content/zh/latest/tasks/mock.md b/docs/site/content/zh/latest/tasks/mock.md index d025c739..89b2c413 100644 --- a/docs/site/content/zh/latest/tasks/mock.md +++ b/docs/site/content/zh/latest/tasks/mock.md @@ -16,52 +16,76 @@ atest mock --prefix / --port 9090 mock.yaml 在 UI 上可以实现和命令行相同的功能,并可以通过页面编辑的方式修改、加载 Mock 服务配置。 +## Mock Docker Registry + +您可以通过执行下面的命令 mock 一个容器仓库服务[container registry](https://distribution.github.io/distribution/): + +```shell +atest mock --prefix / mock/image-registry.yaml +``` + +之后,您可以通过使用如下的命令使用 mock 功能。 + +```shell +docker pull localhost:6060/repo/name:tag +``` + ## 语法 从整体上来看,我们的写法和 HTTP 的名称基本保持一致,用户无需再了解额外的名词。此外,提供两种描述 Mock 服务的方式: -* 针对某个数据对象的 CRUD -* 任意 HTTP 服务 +* 面向对象的 CRUD +* 自定义 HTTP 服务 -下面是一个具体的例子: +### 面对对象 ```yaml #!api-testing-mock # yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json objects: - - name: repo - fields: - - name: name - kind: string - - name: url - kind: string - name: projects initCount: 3 sample: | { - "name": "api-testing", + "name": "atest", "color": "{{ randEnum "blue" "read" "pink" }}" } +``` + +上面 `projects` 的配置,会自动提供该对象的 CRUD(创建、查找、更新、删除)的 API,你可以通过 `atest` 或类似工具发出 HTTP 请求。例如: + +```shell +curl http://localhost:6060/mock/projects + +curl http://localhost:6060/mock/projects/atest + +curl http://localhost:6060/mock/projects -X POST -d '{"name": "new"}' + +curl http://localhost:6060/mock/projects -X PUT -d '{"name": "new", "remark": "this is a project"}' + +curl http://localhost:6060/mock/projects/atest -X DELETE +``` + +> `initCount` 是指按照 `sample` 给定的数据初始化多少个对象;如果没有指定的话,则默认值为 1. + +### 自定义 + +```yaml +#!api-testing-mock +# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json items: - - name: base64 - request: - path: /v1/base64 - response: - body: aGVsbG8= - encoder: base64 - name: prList request: path: /v1/repos/{repo}/prs - header: - name: rick response: header: server: mock + Content-Type: application/json body: | { "count": 1, "items": [{ - "title": "fix: there is a bug on page {{ randEnum "one" }}", + "title": "fix: there is a bug on page {{ randEnum "one", "two" }}", "number": 123, "message": "{{.Response.Header.server}}", "author": "someone", @@ -69,3 +93,43 @@ items: }] } ``` + +启动 Mock 服务后,我们就可以发起如下的请求: + +```shell +curl http://localhost:6060/mock/v1/repos/atest/prs -v +``` + +另外,为了满足复杂的场景,还可以对 Response Body 做特定的解码,目前支持:`base64`、`url`: + +```yaml +#!api-testing-mock +# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json +items: + - name: base64 + request: + path: /v1/base64 + response: + body: aGVsbG8= + encoder: base64 +``` + +上面 Body 的内容是经过 `base64` 编码的,这可以用于不希望直接明文显示,或者是图片的场景: + +```shell +curl http://localhost:6060/mock/v1/base64 +``` + +如果你的 Body 内容可以通过另外一个 HTTP 请求(GET)获得,那么你可以这么写: + +``` +#!api-testing-mock +# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json +items: + - name: baidu + request: + path: /v1/baidu + response: + body: https://baidu.com + encoder: url +``` diff --git a/docs/mock/image-registry.yaml b/docs/site/content/zh/latest/tasks/mock/image-registry.yaml similarity index 100% rename from docs/mock/image-registry.yaml rename to docs/site/content/zh/latest/tasks/mock/image-registry.yaml diff --git a/docs/site/content/zh/latest/tasks/mock/object.yaml b/docs/site/content/zh/latest/tasks/mock/object.yaml new file mode 100644 index 00000000..be3b9ab6 --- /dev/null +++ b/docs/site/content/zh/latest/tasks/mock/object.yaml @@ -0,0 +1,10 @@ +#!api-testing-mock +# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json +objects: + - name: projects + initCount: 3 + sample: | + { + "name": "atest", + "color": "{{ randEnum "blue" "read" "pink" }}" + } diff --git a/docs/site/content/zh/latest/tasks/mock/simple.yaml b/docs/site/content/zh/latest/tasks/mock/simple.yaml index 02880b6c..44825cfb 100644 --- a/docs/site/content/zh/latest/tasks/mock/simple.yaml +++ b/docs/site/content/zh/latest/tasks/mock/simple.yaml @@ -1,17 +1,32 @@ #!api-testing-mock # yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json -objects: - - name: users - initCount: 1 - sample: | - { - "username": "sss", - "email": "{{randEmail}}", - "age": 12 - } - - name: roles - sample: | - { - "name": "admin", - "description": "this is a role" - } \ No newline at end of file +items: + - name: prList + request: + path: /v1/repos/{repo}/prs + response: + header: + server: mock + body: | + { + "count": 1, + "items": [{ + "title": "fix: there is a bug on page {{ randEnum "one" }}", + "number": 123, + "message": "{{.Response.Header.server}}", + "author": "someone", + "status": "success" + }] + } + - name: base64 + request: + path: /v1/base64 + response: + body: aGVsbG8= + encoder: base64 + - name: baidu + request: + path: /v1/baidu + response: + body: https://baidu.com + encoder: url diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index f68e6a8b..f8b81313 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -122,43 +122,6 @@ func (s *inMemoryServer) Start(reader Reader, prefix string) (err error) { func (s *inMemoryServer) startObject(obj Object) { // create a simple CRUD server - s.mux.HandleFunc(fmt.Sprintf("/%s/{%s}", obj.Name, obj.Name), func(w http.ResponseWriter, req *http.Request) { - fmt.Println("mock server received request", req.URL.Path) - method := req.Method - w.Header().Set(util.ContentType, util.JSON) - - objName := strings.TrimPrefix(req.URL.Path, s.prefix+"/"+obj.Name+"/") - - switch method { - case http.MethodGet: - // list all items - for _, item := range s.data[obj.Name] { - if item["name"] == objName { - data, err := json.Marshal(item) - writeResponse(w, data, err) - return - } - } - case http.MethodDelete: - // delete an item - for i, item := range s.data[obj.Name] { - if item["name"] == objName { - if len(s.data[obj.Name]) == i+1 { - s.data[obj.Name] = s.data[obj.Name][:i] - } else { - s.data[obj.Name] = append(s.data[obj.Name][:i], s.data[obj.Name][i+1]) - } - - writeResponse(w, []byte(`{"msg": "deleted"}`), nil) - return - } - } - default: - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - writeResponse(w, []byte(`{"msg": "not found"}`), fmt.Errorf("not found")) - }) s.mux.HandleFunc("/"+obj.Name, func(w http.ResponseWriter, req *http.Request) { fmt.Println("mock server received request", req.URL.Path) method := req.Method @@ -174,7 +137,7 @@ func (s *inMemoryServer) startObject(obj Object) { exclude := false for k, v := range req.URL.Query() { - if v == nil || len(v) == 0 { + if len(v) == 0 { continue } @@ -238,29 +201,48 @@ func (s *inMemoryServer) startObject(obj Object) { } }) - // get a single object + // handle a single object s.mux.HandleFunc(fmt.Sprintf("/%s/{name:[a-z]+}", obj.Name), func(w http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - w.Header().Set(util.ContentType, util.JSON) objects := s.data[obj.Name] if objects != nil { name := mux.Vars(req)["name"] - + var data []byte for _, obj := range objects { if obj["name"] == name { - data, err := json.Marshal(obj) - writeResponse(w, data, err) - return + data, _ = json.Marshal(obj) + break + } + } + + if len(data) == 0 { + w.WriteHeader(http.StatusNotFound) + return + } + + method := req.Method + switch method { + case http.MethodGet: + writeResponse(w, data, nil) + case http.MethodDelete: + for i, item := range s.data[obj.Name] { + if item["name"] == name { + if len(s.data[obj.Name]) == i+1 { + s.data[obj.Name] = s.data[obj.Name][:i] + } else { + s.data[obj.Name] = append(s.data[obj.Name][:i], s.data[obj.Name][i+1]) + } + + writeResponse(w, []byte(`{"msg": "deleted"}`), nil) + } } + default: + w.WriteHeader(http.StatusMethodNotAllowed) } + } }) - return } func (s *inMemoryServer) startItem(item Item) { diff --git a/pkg/mock/in_memory_test.go b/pkg/mock/in_memory_test.go index d5144c72..b1594471 100644 --- a/pkg/mock/in_memory_test.go +++ b/pkg/mock/in_memory_test.go @@ -95,10 +95,7 @@ func TestInMemoryServer(t *testing.T) { }) // delete object - delReq, err := http.NewRequest(http.MethodDelete, api+"/team", bytes.NewBufferString(`{ - "name": "test", - "members": [] - }`)) + delReq, err := http.NewRequest(http.MethodDelete, api+"/team/test", nil) assert.NoError(t, err) resp, err = http.DefaultClient.Do(delReq) assert.NoError(t, err) @@ -111,6 +108,11 @@ func TestInMemoryServer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `[{"name":"someone"}]`, string(data)) } + + resp, err = http.Get(api + "/team/test") + if assert.NoError(t, err) { + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + } }) t.Run("invalid request method", func(t *testing.T) { @@ -122,7 +124,7 @@ func TestInMemoryServer(t *testing.T) { }) t.Run("only accept GET method in getting a single object", func(t *testing.T) { - wrongMethodReq, err := http.NewRequest(http.MethodPut, api+"/team/test", nil) + wrongMethodReq, err := http.NewRequest(http.MethodPut, api+"/team/someone", nil) assert.NoError(t, err) resp, err = http.DefaultClient.Do(wrongMethodReq) assert.NoError(t, err) From 451e4e06047ac822661e460c319da9e472390da5 Mon Sep 17 00:00:00 2001 From: rick Date: Thu, 31 Oct 2024 05:51:24 +0000 Subject: [PATCH 3/5] add a common proxy on mock server --- pkg/mock/in_memory.go | 7 +++++++ pkg/mock/types.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index f8b81313..ad92513a 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -102,6 +102,13 @@ func (s *inMemoryServer) Load() (err error) { } s.handleOpenAPI() + + for _, proxy := range server.Proxies { + memLogger.Info("start to proxy", "target", proxy.Target) + s.mux.HandleFunc(proxy.Path, func(w http.ResponseWriter, req *http.Request) { + fmt.Println("catch all", req.URL.Path) + }) + } return } diff --git a/pkg/mock/types.go b/pkg/mock/types.go index d88248d9..d424bdf5 100644 --- a/pkg/mock/types.go +++ b/pkg/mock/types.go @@ -55,8 +55,14 @@ type Webhook struct { Request Request `yaml:"request" json:"request"` } +type Proxy struct { + Path string `yaml:"path" json:"path"` + Target string `yaml:"target" json:"target"` +} + type Server struct { Objects []Object `yaml:"objects" json:"objects"` Items []Item `yaml:"items" json:"items"` + Proxies []Proxy `yaml:"proxies" json:"proxies"` Webhooks []Webhook `yaml:"webhooks" json:"webhooks"` } From 875de990ce99007e424de6e7033b439ec377ff94 Mon Sep 17 00:00:00 2001 From: rick Date: Thu, 31 Oct 2024 10:30:06 +0000 Subject: [PATCH 4/5] test pass with the mock proxy --- docs/site/content/zh/latest/tasks/mock.md | 10 +++---- .../content/zh/latest/tasks/mock/simple.yaml | 7 +++-- pkg/mock/in_memory.go | 26 ++++++++++++++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/docs/site/content/zh/latest/tasks/mock.md b/docs/site/content/zh/latest/tasks/mock.md index 89b2c413..9667c736 100644 --- a/docs/site/content/zh/latest/tasks/mock.md +++ b/docs/site/content/zh/latest/tasks/mock.md @@ -76,7 +76,7 @@ curl http://localhost:6060/mock/projects/atest -X DELETE items: - name: prList request: - path: /v1/repos/{repo}/prs + path: /api/v1/repos/{repo}/prs response: header: server: mock @@ -97,7 +97,7 @@ items: 启动 Mock 服务后,我们就可以发起如下的请求: ```shell -curl http://localhost:6060/mock/v1/repos/atest/prs -v +curl http://localhost:6060/mock/api/v1/repos/atest/prs -v ``` 另外,为了满足复杂的场景,还可以对 Response Body 做特定的解码,目前支持:`base64`、`url`: @@ -108,7 +108,7 @@ curl http://localhost:6060/mock/v1/repos/atest/prs -v items: - name: base64 request: - path: /v1/base64 + path: /api/v1/base64 response: body: aGVsbG8= encoder: base64 @@ -117,7 +117,7 @@ items: 上面 Body 的内容是经过 `base64` 编码的,这可以用于不希望直接明文显示,或者是图片的场景: ```shell -curl http://localhost:6060/mock/v1/base64 +curl http://localhost:6060/mock/api/v1/base64 ``` 如果你的 Body 内容可以通过另外一个 HTTP 请求(GET)获得,那么你可以这么写: @@ -128,7 +128,7 @@ curl http://localhost:6060/mock/v1/base64 items: - name: baidu request: - path: /v1/baidu + path: /api/v1/baidu response: body: https://baidu.com encoder: url diff --git a/docs/site/content/zh/latest/tasks/mock/simple.yaml b/docs/site/content/zh/latest/tasks/mock/simple.yaml index 44825cfb..a5ad6602 100644 --- a/docs/site/content/zh/latest/tasks/mock/simple.yaml +++ b/docs/site/content/zh/latest/tasks/mock/simple.yaml @@ -3,7 +3,7 @@ items: - name: prList request: - path: /v1/repos/{repo}/prs + path: /api/v1/repos/{repo}/prs response: header: server: mock @@ -20,7 +20,7 @@ items: } - name: base64 request: - path: /v1/base64 + path: /api/v1/base64 response: body: aGVsbG8= encoder: base64 @@ -30,3 +30,6 @@ items: response: body: https://baidu.com encoder: url +proxies: + - path: /api/v1/{point} + target: http://atest.localhost:8080 diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index ad92513a..50ca3cd6 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -106,7 +106,31 @@ func (s *inMemoryServer) Load() (err error) { for _, proxy := range server.Proxies { memLogger.Info("start to proxy", "target", proxy.Target) s.mux.HandleFunc(proxy.Path, func(w http.ResponseWriter, req *http.Request) { - fmt.Println("catch all", req.URL.Path) + api := fmt.Sprintf("%s/%s", proxy.Target, strings.TrimPrefix(req.URL.Path, s.prefix)) + memLogger.Info("redirect to", "target", api) + + targetReq, err := http.NewRequestWithContext(req.Context(), req.Method, api, req.Body) + if err != nil { + memLogger.Error(err, "failed to create proxy request") + return + } + + resp, err := http.DefaultClient.Do(targetReq) + if err != nil { + memLogger.Error(err, "failed to do proxy request") + return + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + memLogger.Error(err, "failed to read response body") + return + } + + for k, v := range resp.Header { + w.Header().Add(k, v[0]) + } + w.Write(data) }) } return From 55243e17025c3a5550702a263b5372ea1a7b0e5c Mon Sep 17 00:00:00 2001 From: rick Date: Fri, 1 Nov 2024 02:17:09 +0000 Subject: [PATCH 5/5] update mock json schema --- docs/api-testing-mock-schema.json | 24 +++++++++---------- docs/site/content/zh/latest/tasks/mock.md | 20 +++++++++++++++- .../content/zh/latest/tasks/mock/simple.yaml | 2 +- pkg/mock/in_memory.go | 15 +++++------- pkg/mock/types.go | 12 +++------- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/docs/api-testing-mock-schema.json b/docs/api-testing-mock-schema.json index d67f3083..296b413e 100644 --- a/docs/api-testing-mock-schema.json +++ b/docs/api-testing-mock-schema.json @@ -10,18 +10,7 @@ "properties": { "name": {"type": "string"}, "initCount": {"type": "integer"}, - "sample": {"type": "string"}, - "fields": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "kind": {"type": "string"} - }, - "required": ["name", "kind"] - } - } + "sample": {"type": "string"} }, "required": ["name"] } @@ -66,6 +55,17 @@ "required": ["name", "request", "response"] } }, + "proxies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": {"type": "string"}, + "target": {"type": "string"} + }, + "required": ["path", "target"] + } + }, "webhooks": { "type": "array", "items": { diff --git a/docs/site/content/zh/latest/tasks/mock.md b/docs/site/content/zh/latest/tasks/mock.md index 9667c736..5b988be7 100644 --- a/docs/site/content/zh/latest/tasks/mock.md +++ b/docs/site/content/zh/latest/tasks/mock.md @@ -122,7 +122,7 @@ curl http://localhost:6060/mock/api/v1/base64 如果你的 Body 内容可以通过另外一个 HTTP 请求(GET)获得,那么你可以这么写: -``` +```yaml #!api-testing-mock # yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json items: @@ -133,3 +133,21 @@ items: body: https://baidu.com encoder: url ``` + +在实际情况中,往往是向已有系统或平台添加新的 API,此时要 Mock 所有已经存在的 API 就既没必要也需要很多工作量。因此,我们提供了一种简单的方式,即可以增加**代理**的方式把已有的 API 请求转发到实际的地址,只对新增的 API 进行 Mock 处理。如下所示: + +```yaml +#!api-testing-mock +# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json +proxies: + - path: /api/v1/{part} + target: http://atest.localhost:8080 +``` + +当我们发起如下的请求时,实际请求的地址为 `http://atest.localhost:8080/api/v1/projects` + +```shell +curl http://localhost:6060/mock/api/v1/projects +``` + +> 更多 URL 中通配符的用法,请参考 https://github.com/gorilla/mux diff --git a/docs/site/content/zh/latest/tasks/mock/simple.yaml b/docs/site/content/zh/latest/tasks/mock/simple.yaml index a5ad6602..7fea3daa 100644 --- a/docs/site/content/zh/latest/tasks/mock/simple.yaml +++ b/docs/site/content/zh/latest/tasks/mock/simple.yaml @@ -31,5 +31,5 @@ items: body: https://baidu.com encoder: url proxies: - - path: /api/v1/{point} + - path: /api/v1/{part} target: http://atest.localhost:8080 diff --git a/pkg/mock/in_memory.go b/pkg/mock/in_memory.go index 50ca3cd6..f7750d77 100644 --- a/pkg/mock/in_memory.go +++ b/pkg/mock/in_memory.go @@ -138,16 +138,13 @@ func (s *inMemoryServer) Load() (err error) { func (s *inMemoryServer) Start(reader Reader, prefix string) (err error) { var handler http.Handler - if handler, err = s.SetupHandler(reader, prefix); err != nil { - return - } - - if s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", s.port)); err != nil { - return + if handler, err = s.SetupHandler(reader, prefix); err == nil { + if s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", s.port)); err == nil { + go func() { + err = http.Serve(s.listener, handler) + }() + } } - go func() { - err = http.Serve(s.listener, handler) - }() return } diff --git a/pkg/mock/types.go b/pkg/mock/types.go index d424bdf5..bed7c569 100644 --- a/pkg/mock/types.go +++ b/pkg/mock/types.go @@ -16,15 +16,9 @@ limitations under the License. package mock type Object struct { - Name string `yaml:"name" json:"name"` - InitCount *int `yaml:"initCount" json:"initCount"` - Sample string `yaml:"sample" json:"sample"` - Fields []Field `yaml:"fields" json:"fields"` -} - -type Field struct { - Name string `yaml:"name" json:"name"` - Kind string `yaml:"kind" json:"kind"` + Name string `yaml:"name" json:"name"` + InitCount *int `yaml:"initCount" json:"initCount"` + Sample string `yaml:"sample" json:"sample"` } type Item struct {