Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/api-testing-mock-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,19 @@
"type": "object",
"properties": {
"encoder": {
"type": "string"
"type": "string",
"enum": [
"base64",
"url",
"raw"
]
},
"body": {
"type": "string"
},
"bodyFromFile": {
"type": "string"
},
"header": {
"type": "object",
"description": "HTTP response headers. Common headers include 'Content-Type', 'Cache-Control', 'Set-Cookie', etc.",
Expand Down
67 changes: 64 additions & 3 deletions docs/site/content/zh/latest/tasks/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ items:
curl http://localhost:6060/mock/api/v1/repos/atest/prs -v
```

另外,为了满足复杂的场景,还可以对 Response Body 做特定的解码,目前支持:`base64`、`url`:
#### 编码器

另外,为了满足复杂的场景,还可以对 Response Body 做特定的解码,目前支持:`base64`、`url`、`raw`:

> encoder 为 `raw` 时,表示不进行处理

```yaml
#!api-testing-mock
Expand Down Expand Up @@ -136,6 +140,63 @@ items:
encoder: url
```

如果你的响应内容比较大,或者保存在一个本地文件中,那么你可以这么写:

```yaml
#!api-testing-mock
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-mock-schema.json
items:
- name: baidu
request:
path: /api/v1/baidu
response:
bodyFromFile: /tmp/baidu.html
```

通过下面的方式也可以生成图片:

```yaml
items:
- name: image
request:
path: /v1/image
response:
header:
Content-Type: image/png
body: |
{{ randImage 300 300 }}
```

#### 条件判断

对于查询类的 API,通常会接收参数,并根据参数的不同,返回相应的数据。这时候,可以用到条件判断的表达式:

```yaml
items:
- name: cats
request:
path: /api/v1/cats/{size}
response:
header:
Content-Type: application/json
body: |
{{if eq .Param.size "big"}}
{
"name": "big cat"
}
{{else if eq .Param.size "middle"}}
{
"name": "middle cat"
}
{{else if eq .Param.size "small"}}
{
"name": "small cat"
}
{{end}}
```

## 代理

在实际情况中,往往是向已有系统或平台添加新的 API,此时要 Mock 所有已经存在的 API 就既没必要也需要很多工作量。因此,我们提供了一种简单的方式,即可以增加**代理**的方式把已有的 API 请求转发到实际的地址,只对新增的 API 进行 Mock 处理。如下所示:

```yaml
Expand All @@ -160,7 +221,7 @@ proxies:
target: http://192.168.123.58:9200
```

## TCP 协议代理
### TCP 协议代理

```yaml
proxies:
Expand All @@ -170,7 +231,7 @@ proxies:
target: 192.168.123.58:33060
```

## 代理多个服务
### 代理多个服务

```shell
atest mock-compose bin/compose.yaml
Expand Down
24 changes: 23 additions & 1 deletion pkg/mock/in_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"io"
"net"
"net/http"
"os"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -450,6 +451,15 @@ func (h *advanceHandler) handle(w http.ResponseWriter, req *http.Request) {
w.Header().Set(k, hv)
}

if h.item.Response.BodyFromFile != "" {
// read from file
if data, readErr := os.ReadFile(h.item.Response.BodyFromFile); readErr != nil {
memLogger.Error(readErr, "failed to read file", "file", h.item.Response.BodyFromFile)
} else {
h.item.Response.Body = string(data)
}
}

var err error
if h.item.Response.Encoder == "base64" {
h.item.Response.BodyData, err = base64.StdEncoding.DecodeString(h.item.Response.Body)
Expand All @@ -458,9 +468,21 @@ func (h *advanceHandler) handle(w http.ResponseWriter, req *http.Request) {
if resp, err = http.Get(h.item.Response.Body); err == nil {
h.item.Response.BodyData, err = io.ReadAll(resp.Body)
}
} else if h.item.Response.Encoder == "raw" {
h.item.Response.BodyData = []byte(h.item.Response.Body)
} else {
if h.item.Response.BodyData, err = render.RenderAsBytes("start-item", h.item.Response.Body, h.item); err != nil {
fmt.Printf("failed to render body: %v", err)
memLogger.Error(err, "failed to render body")
}
}

if strings.HasPrefix(h.item.Response.Header[util.ContentType], "image/") {
if strings.HasPrefix(string(h.item.Response.BodyData), util.ImageBase64Prefix) {
// decode base64 image data
imgData := strings.TrimPrefix(string(h.item.Response.BodyData), util.ImageBase64Prefix)
if h.item.Response.BodyData, err = base64.StdEncoding.DecodeString(imgData); err != nil {
memLogger.Error(err, "failed to decode base64 image data")
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions pkg/mock/in_memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ import (
"strings"
"testing"

_ "embed"
"github.com/linuxsuren/api-testing/pkg/util"
"github.com/stretchr/testify/assert"
)

//go:embed testdata/api.yaml
var mockFile []byte

func TestInMemoryServer(t *testing.T) {
server := NewInMemoryServer(context.Background(), 0)
server.EnableMetrics()
Expand Down Expand Up @@ -167,6 +171,13 @@ func TestInMemoryServer(t *testing.T) {
assert.Equal(t, "hello", string(data))
})

t.Run("read response from file", func(t *testing.T) {
resp, err = http.Get(api + "/v1/readResponseFromFile")
assert.NoError(t, err)
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, mockFile, data)
})

t.Run("not found config file", func(t *testing.T) {
server := NewInMemoryServer(context.Background(), 0)
err := server.Start(NewLocalFileReader("fake"), "/")
Expand Down
2 changes: 1 addition & 1 deletion pkg/mock/server.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024 API Testing Authors.
Copyright 2024-2025 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.
Expand Down
14 changes: 14 additions & 0 deletions pkg/mock/testdata/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ items:
response:
body: aGVsbG8=
encoder: base64
- name: readResponseFromFile
request:
path: /v1/readResponseFromFile
response:
encoder: raw
bodyFromFile: testdata/api.yaml
- name: prList
request:
path: /v1/repos/{repo}/prs
Expand All @@ -50,6 +56,14 @@ items:
"status": "success"
}]
}
- name: image
request:
path: /v1/image
response:
header:
Content-Type: image/png
body: |
{{ randImage 300 300 }}
proxies:
- path: /v1/myProjects
target: http://localhost:{{.GetPort}}
Expand Down
11 changes: 6 additions & 5 deletions pkg/mock/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ type RequestWithAuth struct {
}

type Response struct {
Encoder string `yaml:"encoder" json:"encoder"`
Body string `yaml:"body" json:"body"`
Header map[string]string `yaml:"header" json:"header"`
StatusCode int `yaml:"statusCode" json:"statusCode"`
BodyData []byte
Encoder string `yaml:"encoder" json:"encoder"`
Body string `yaml:"body" json:"body"`
BodyFromFile string `yaml:"bodyFromFile" json:"bodyFromFile"`
Header map[string]string `yaml:"header" json:"header"`
StatusCode int `yaml:"statusCode" json:"statusCode"`
BodyData []byte
}

type Webhook struct {
Expand Down
4 changes: 3 additions & 1 deletion pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1405,7 +1405,9 @@ func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (repl
}

server := mock.NewInMemoryServer(ctx, int(in.GetPort())).WithTLS(dServer.GetTLS())
server.Start(s.mockWriter, in.Prefix)
if err = server.Start(s.mockWriter, in.Prefix); err != nil {
return
}
server.WithLogWriter(s)
s.loader = server
}
Expand Down
Loading