Skip to content

Commit 65c87e3

Browse files
authored
feat: support read mock response from local file (#788)
* feat: support read mock response from local file * docs: add more instructors about the mock conditional response * support response as a random image --------- Co-authored-by: rick <LinuxSuRen@users.noreply.github.com>
1 parent 53bcd6e commit 65c87e3

File tree

8 files changed

+131
-12
lines changed

8 files changed

+131
-12
lines changed

docs/api-testing-mock-schema.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,19 @@
6060
"type": "object",
6161
"properties": {
6262
"encoder": {
63-
"type": "string"
63+
"type": "string",
64+
"enum": [
65+
"base64",
66+
"url",
67+
"raw"
68+
]
6469
},
6570
"body": {
6671
"type": "string"
6772
},
73+
"bodyFromFile": {
74+
"type": "string"
75+
},
6876
"header": {
6977
"type": "object",
7078
"description": "HTTP response headers. Common headers include 'Content-Type', 'Cache-Control', 'Set-Cookie', etc.",

docs/site/content/zh/latest/tasks/mock.md

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ items:
102102
curl http://localhost:6060/mock/api/v1/repos/atest/prs -v
103103
```
104104

105-
另外,为了满足复杂的场景,还可以对 Response Body 做特定的解码,目前支持:`base64`、`url`:
105+
#### 编码器
106+
107+
另外,为了满足复杂的场景,还可以对 Response Body 做特定的解码,目前支持:`base64`、`url`、`raw`:
108+
109+
> encoder 为 `raw` 时,表示不进行处理
106110

107111
```yaml
108112
#!api-testing-mock
@@ -136,6 +140,63 @@ items:
136140
encoder: url
137141
```
138142

143+
如果你的响应内容比较大,或者保存在一个本地文件中,那么你可以这么写:
144+
145+
```yaml
146+
#!api-testing-mock
147+
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json
148+
items:
149+
- name: baidu
150+
request:
151+
path: /api/v1/baidu
152+
response:
153+
bodyFromFile: /tmp/baidu.html
154+
```
155+
156+
通过下面的方式也可以生成图片:
157+
158+
```yaml
159+
items:
160+
- name: image
161+
request:
162+
path: /v1/image
163+
response:
164+
header:
165+
Content-Type: image/png
166+
body: |
167+
{{ randImage 300 300 }}
168+
```
169+
170+
#### 条件判断
171+
172+
对于查询类的 API,通常会接收参数,并根据参数的不同,返回相应的数据。这时候,可以用到条件判断的表达式:
173+
174+
```yaml
175+
items:
176+
- name: cats
177+
request:
178+
path: /api/v1/cats/{size}
179+
response:
180+
header:
181+
Content-Type: application/json
182+
body: |
183+
{{if eq .Param.size "big"}}
184+
{
185+
"name": "big cat"
186+
}
187+
{{else if eq .Param.size "middle"}}
188+
{
189+
"name": "middle cat"
190+
}
191+
{{else if eq .Param.size "small"}}
192+
{
193+
"name": "small cat"
194+
}
195+
{{end}}
196+
```
197+
198+
## 代理
199+
139200
在实际情况中,往往是向已有系统或平台添加新的 API,此时要 Mock 所有已经存在的 API 就既没必要也需要很多工作量。因此,我们提供了一种简单的方式,即可以增加**代理**的方式把已有的 API 请求转发到实际的地址,只对新增的 API 进行 Mock 处理。如下所示:
140201

141202
```yaml
@@ -160,7 +221,7 @@ proxies:
160221
target: http://192.168.123.58:9200
161222
```
162223

163-
## TCP 协议代理
224+
### TCP 协议代理
164225

165226
```yaml
166227
proxies:
@@ -170,7 +231,7 @@ proxies:
170231
target: 192.168.123.58:33060
171232
```
172233

173-
## 代理多个服务
234+
### 代理多个服务
174235

175236
```shell
176237
atest mock-compose bin/compose.yaml

pkg/mock/in_memory.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"io"
2525
"net"
2626
"net/http"
27+
"os"
2728
"sort"
2829
"strings"
2930
"sync"
@@ -450,6 +451,15 @@ func (h *advanceHandler) handle(w http.ResponseWriter, req *http.Request) {
450451
w.Header().Set(k, hv)
451452
}
452453

454+
if h.item.Response.BodyFromFile != "" {
455+
// read from file
456+
if data, readErr := os.ReadFile(h.item.Response.BodyFromFile); readErr != nil {
457+
memLogger.Error(readErr, "failed to read file", "file", h.item.Response.BodyFromFile)
458+
} else {
459+
h.item.Response.Body = string(data)
460+
}
461+
}
462+
453463
var err error
454464
if h.item.Response.Encoder == "base64" {
455465
h.item.Response.BodyData, err = base64.StdEncoding.DecodeString(h.item.Response.Body)
@@ -458,9 +468,21 @@ func (h *advanceHandler) handle(w http.ResponseWriter, req *http.Request) {
458468
if resp, err = http.Get(h.item.Response.Body); err == nil {
459469
h.item.Response.BodyData, err = io.ReadAll(resp.Body)
460470
}
471+
} else if h.item.Response.Encoder == "raw" {
472+
h.item.Response.BodyData = []byte(h.item.Response.Body)
461473
} else {
462474
if h.item.Response.BodyData, err = render.RenderAsBytes("start-item", h.item.Response.Body, h.item); err != nil {
463-
fmt.Printf("failed to render body: %v", err)
475+
memLogger.Error(err, "failed to render body")
476+
}
477+
}
478+
479+
if strings.HasPrefix(h.item.Response.Header[util.ContentType], "image/") {
480+
if strings.HasPrefix(string(h.item.Response.BodyData), util.ImageBase64Prefix) {
481+
// decode base64 image data
482+
imgData := strings.TrimPrefix(string(h.item.Response.BodyData), util.ImageBase64Prefix)
483+
if h.item.Response.BodyData, err = base64.StdEncoding.DecodeString(imgData); err != nil {
484+
memLogger.Error(err, "failed to decode base64 image data")
485+
}
464486
}
465487
}
466488

pkg/mock/in_memory_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ import (
2424
"strings"
2525
"testing"
2626

27+
_ "embed"
2728
"github.com/linuxsuren/api-testing/pkg/util"
2829
"github.com/stretchr/testify/assert"
2930
)
3031

32+
//go:embed testdata/api.yaml
33+
var mockFile []byte
34+
3135
func TestInMemoryServer(t *testing.T) {
3236
server := NewInMemoryServer(context.Background(), 0)
3337
server.EnableMetrics()
@@ -167,6 +171,13 @@ func TestInMemoryServer(t *testing.T) {
167171
assert.Equal(t, "hello", string(data))
168172
})
169173

174+
t.Run("read response from file", func(t *testing.T) {
175+
resp, err = http.Get(api + "/v1/readResponseFromFile")
176+
assert.NoError(t, err)
177+
data, _ := io.ReadAll(resp.Body)
178+
assert.Equal(t, mockFile, data)
179+
})
180+
170181
t.Run("not found config file", func(t *testing.T) {
171182
server := NewInMemoryServer(context.Background(), 0)
172183
err := server.Start(NewLocalFileReader("fake"), "/")

pkg/mock/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 API Testing Authors.
2+
Copyright 2024-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.

pkg/mock/testdata/api.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ items:
3030
response:
3131
body: aGVsbG8=
3232
encoder: base64
33+
- name: readResponseFromFile
34+
request:
35+
path: /v1/readResponseFromFile
36+
response:
37+
encoder: raw
38+
bodyFromFile: testdata/api.yaml
3339
- name: prList
3440
request:
3541
path: /v1/repos/{repo}/prs
@@ -50,6 +56,14 @@ items:
5056
"status": "success"
5157
}]
5258
}
59+
- name: image
60+
request:
61+
path: /v1/image
62+
response:
63+
header:
64+
Content-Type: image/png
65+
body: |
66+
{{ randImage 300 300 }}
5367
proxies:
5468
- path: /v1/myProjects
5569
target: http://localhost:{{.GetPort}}

pkg/mock/types.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ type RequestWithAuth struct {
4444
}
4545

4646
type Response struct {
47-
Encoder string `yaml:"encoder" json:"encoder"`
48-
Body string `yaml:"body" json:"body"`
49-
Header map[string]string `yaml:"header" json:"header"`
50-
StatusCode int `yaml:"statusCode" json:"statusCode"`
51-
BodyData []byte
47+
Encoder string `yaml:"encoder" json:"encoder"`
48+
Body string `yaml:"body" json:"body"`
49+
BodyFromFile string `yaml:"bodyFromFile" json:"bodyFromFile"`
50+
Header map[string]string `yaml:"header" json:"header"`
51+
StatusCode int `yaml:"statusCode" json:"statusCode"`
52+
BodyData []byte
5253
}
5354

5455
type Webhook struct {

pkg/server/remote_server.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,9 @@ func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (repl
14051405
}
14061406

14071407
server := mock.NewInMemoryServer(ctx, int(in.GetPort())).WithTLS(dServer.GetTLS())
1408-
server.Start(s.mockWriter, in.Prefix)
1408+
if err = server.Start(s.mockWriter, in.Prefix); err != nil {
1409+
return
1410+
}
14091411
server.WithLogWriter(s)
14101412
s.loader = server
14111413
}

0 commit comments

Comments
 (0)