Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support to config mock server on ui #552

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
26 changes: 13 additions & 13 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func createServerCmd(execer fakeruntime.Execer, httpServer server.HTTPServer) (c
flags.StringArrayVarP(&opt.mockConfig, "mock-config", "", nil, "The mock config files")
flags.StringVarP(&opt.mockPrefix, "mock-prefix", "", "/mock", "The mock server API prefix")
flags.StringVarP(&opt.extensionRegistry, "extension-registry", "", "docker.io", "The extension registry URL")
flags.DurationVarP(&opt.downloadTimeout, "download-timeout", "", time.Second*10, "The timeout of extension download")

// gc related flags
flags.IntVarP(&opt.gcPercent, "gc-percent", "", 100, "The GC percent of Go")
Expand Down Expand Up @@ -129,6 +130,7 @@ type serverOption struct {
configDir string
skyWalking string
extensionRegistry string
downloadTimeout time.Duration

auth string
oauthProvider string
Expand Down Expand Up @@ -251,6 +253,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {

extDownloader := downloader.NewStoreDownloader()
extDownloader.WithRegistry(o.extensionRegistry)
extDownloader.WithTimeout(o.downloadTimeout)
storeExtMgr := server.NewStoreExtManager(o.execer)
storeExtMgr.WithDownloader(extDownloader)
remoteServer := server.NewRemoteServer(loader, remote.NewGRPCloaderFromStore(), secretServer, storeExtMgr, o.configDir, o.grpcMaxRecvMsgSize)
Expand All @@ -264,9 +267,16 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
}

// create mock server controller
mockInMemoryReader := mock.NewInMemoryReader("")
var mockWriter mock.ReaderAndWriter
if len(o.mockConfig) > 0 {
cmd.Println("currently only one mock config is supported, will take the first one")
mockWriter = mock.NewLocalFileReader(o.mockConfig[0])
} else {
mockWriter = mock.NewInMemoryReader("")
}

dynamicMockServer := mock.NewInMemoryServer(0)
mockServerController := server.NewMockServerController(mockInMemoryReader, dynamicMockServer)
mockServerController := server.NewMockServerController(mockWriter, dynamicMockServer, o.httpPort)

clean := make(chan os.Signal, 1)
signal.Notify(clean, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
Expand Down Expand Up @@ -363,17 +373,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
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]), o.mockPrefix); err != nil {
return
}
combineHandlers.PutHandler(o.mockPrefix, mockServerHandler)
}

if handler, hErr := dynamicMockServer.SetupHandler(mockInMemoryReader, o.mockPrefix+"/server"); hErr != nil {
if handler, hErr := dynamicMockServer.SetupHandler(mockWriter, o.mockPrefix+"/server"); hErr != nil {
err = hErr
return
} else {
Expand Down
2 changes: 1 addition & 1 deletion console/atest-ui/src/components/EditButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ElInput } from 'element-plus'
import type { InputInstance } from 'element-plus'

const props = defineProps({
value: String,
value: String || Number,
})

const emit = defineEmits(['changed'])
Expand Down
24 changes: 20 additions & 4 deletions console/atest-ui/src/views/MockManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@ import { ref } from 'vue';
import { Codemirror } from 'vue-codemirror';
import { API } from './net';
import {useI18n} from "vue-i18n";
import EditButton from '../components/EditButton.vue'

const { t } = useI18n()

const mockConfig = ref('');
interface MockConfig {
Config: string
Prefix: string
Port: number
}
const mockConfig = ref({} as MockConfig);
const link = ref('')
API.GetMockConfig((d) => {
mockConfig.value = d.Config
mockConfig.value = d
link.value = window.location.origin + d.Prefix + "/api.json"
})
const prefixChanged = (p: string) => {
mockConfig.value.Prefix = p
}
const portChanged = (p: number) => {
mockConfig.value.Port = p
}
const tabActive = ref('yaml')
const insertSample = () => {
mockConfig.value = `objects:
mockConfig.value.Config = `objects:
- name: projects
initCount: 3
sample: |
Expand All @@ -39,10 +51,14 @@ items:
<el-divider direction="vertical" />
<el-link target="_blank" :href="link">{{ link }}</el-link> <!-- Noncompliant -->
</div>
<div>
API Prefix:<EditButton :value="mockConfig.Prefix" @changed="prefixChanged"/>
Port:<EditButton :value="mockConfig.Port" @changed="portChanged"/>
</div>
<div>
<el-tabs v-model="tabActive">
<el-tab-pane label="YAML" name="yaml">
<Codemirror v-model="mockConfig" />
<Codemirror v-model="mockConfig.Config" />
</el-tab-pane>
</el-tabs>
</div>
Expand Down
2 changes: 1 addition & 1 deletion console/atest-ui/src/views/SecretManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue'
import { Edit, Delete } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { API } from './net'
import type { Secret } from './net'
import { UIAPI } from './net-vue'
Expand Down
6 changes: 2 additions & 4 deletions console/atest-ui/src/views/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,15 +620,13 @@ function GetSuggestedAPIs(name: string,
.then(callback)
}

function ReloadMockServer(config: string) {
function ReloadMockServer(config: any) {
const requestOptions = {
method: 'POST',
headers: {
'X-Auth': getToken()
},
body: JSON.stringify({
Config: config
})
body: JSON.stringify(config)
}
fetch(`/api/v1/mock/reload`, requestOptions)
.then(DefaultResponseProcess)
Expand Down
8 changes: 0 additions & 8 deletions docs/site/content/zh/latest/releases/v0.0.1.md

This file was deleted.

33 changes: 33 additions & 0 deletions docs/site/content/zh/latest/tasks/extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
+++
title = "插件"
+++

`atest` 会把非核心、可扩展的功能以插件(extension)的形式实现。下面介绍有哪些插件,以及如何使用:

> 在不同的系统中,插件有着不同的表述,例如:extension、plugin 等。

| 类型 | 名称 | 描述 |
|------|------|------|
| 存储 | [orm](https://github.com/LinuxSuRen/atest-ext-store-orm) | 保存数据到关系型数据库中,例如:MySQL |
| 存储 | [s3](https://github.com/LinuxSuRen/atest-ext-store-s3) | 保存数据到对象存储中 |
| 存储 | [etcd](https://github.com/LinuxSuRen/atest-ext-store-etcd) | 保存数据到 Etcd 数据库中 |
| 存储 | [git](https://github.com/LinuxSuRen/atest-ext-store-git) | 保存数据到 Git 仓库中 |
| 存储 | [mongodb](https://github.com/LinuxSuRen/atest-ext-store-mongodb) | 保存数据到 MongDB 中 |

> `atest` 也是唯一支持如此丰富的存储的接口开发、测试的开源工具。

## 下载插件

我们建议通过如下的命令来下载插件:

```shell
atest extension orm
```

上面的命令,会识别当前的操作系统,自动下载最新版本的插件。当然,用户可以通过自行编译、手动下载的方式获取插件二进制文件。

`atest` 可以从任意支持 OCI 的镜像仓库中(命令参数说明中给出了支持的镜像服务地址)下载插件,也可以指定下载超时时间:

```shell
atest extension orm --registry ghcr.io --timeout 2ms
```
71 changes: 71 additions & 0 deletions docs/site/content/zh/latest/tasks/mock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
+++
title = "Mock 服务"
+++

Mock 服务在前后端并行开发、系统对接、设备对接场景下能起到非常好的作用,可以极大地降低团队之间、系统之间的耦合度。

用户可以通过命令行终端(CLI)、Web UI 的方式来使用 Mock 服务。

## 命令行

```shell
atest mock --prefix / --port 9090 mock.yaml
```

## Web

在 UI 上可以实现和命令行相同的功能,并可以通过页面编辑的方式修改、加载 Mock 服务配置。

## 语法

从整体上来看,我们的写法和 HTTP 的名称基本保持一致,用户无需再了解额外的名词。此外,提供两种描述 Mock 服务的方式:

* 针对某个数据对象的 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",
"color": "{{ randEnum "blue" "read" "pink" }}"
}
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
body: |
{
"count": 1,
"items": [{
"title": "fix: there is a bug on page {{ randEnum "one" }}",
"number": 123,
"message": "{{.Response.Header.server}}",
"author": "someone",
"status": "success"
}]
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ spec:

`ca`为 CA 证书的路径,`key`为与`cert`对应的私钥,这两项填写后代表启用 mTLS。(mTLS 尚未实现)

当`insecure`为`false`时,`cert`和`serverName`为必填项。
当`insecure`为`false`时,`cert`和`serverName`为必填项。
18 changes: 18 additions & 0 deletions docs/site/content/zh/latest/tasks/verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
+++
title = "测试用例验证"
+++

`atest` 采用 https://expr.medv.io 对 HTTP 请求响应的验证,比如:返回的数据列表长度验证、具体值的验证等等。下面给出一些例子:

> 需要注意的是,`data` 指的是 HTTP Response Body(响应体)的 JSON 对象。

## 数组长度判断

```yaml
- name: projectKinds
request:
api: /api/resources/projectKinds
expect:
verify:
- len(data.data) == 6
```
2 changes: 2 additions & 0 deletions pkg/mock/in_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,8 @@ func (s *inMemoryServer) GetPort() string {
func (s *inMemoryServer) Stop() (err error) {
if s.listener != nil {
err = s.listener.Close()
} else {
memLogger.Info("listener is nil")
}
if s.cancelFunc != nil {
s.cancelFunc()
Expand Down
10 changes: 8 additions & 2 deletions pkg/mock/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ package mock

import (
"errors"
"github.com/linuxsuren/api-testing/docs"
"os"

"github.com/linuxsuren/api-testing/docs"

"gopkg.in/yaml.v3"
)

Expand All @@ -37,7 +38,7 @@ type localFileReader struct {
data []byte
}

func NewLocalFileReader(file string) Reader {
func NewLocalFileReader(file string) ReaderAndWriter {
return &localFileReader{file: file}
}

Expand All @@ -48,6 +49,11 @@ func (r *localFileReader) Parse() (server *Server, err error) {
return
}

func (r *localFileReader) Write(data []byte) {
r.data = data
_ = os.WriteFile(r.file, r.data, 0644)
}

func (r *localFileReader) GetData() []byte {
return r.data
}
Expand Down
38 changes: 31 additions & 7 deletions pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"path/filepath"
reflect "reflect"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -1243,28 +1244,51 @@
// Start starts the mock server
type mockServerController struct {
UnimplementedMockServer
mockWriter mock.ReaderAndWriter
loader mock.Loadable
reader mock.Reader
mockWriter mock.ReaderAndWriter
loader mock.Loadable
reader mock.Reader
prefix string
combinePort int
}

func NewMockServerController(mockWriter mock.ReaderAndWriter, loader mock.Loadable) MockServer {
func NewMockServerController(mockWriter mock.ReaderAndWriter, loader mock.Loadable, combinePort int) MockServer {
return &mockServerController{
mockWriter: mockWriter,
loader: loader,
mockWriter: mockWriter,
loader: loader,
prefix: "/mock/server",
combinePort: combinePort,
}
}

func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (reply *Empty, err error) {
s.mockWriter.Write([]byte(in.Config))
s.prefix = in.Prefix
if dServer, ok := s.loader.(mock.DynamicServer); ok && dServer.GetPort() != strconv.Itoa(int(in.GetPort())) {
if strconv.Itoa(s.combinePort) != dServer.GetPort() {
if stopErr := dServer.Stop(); stopErr != nil {
remoteServerLogger.Info("failed to stop old server", "error", stopErr)
} else {
remoteServerLogger.Info("old server stopped", "port", dServer.GetPort())
}
}

server := mock.NewInMemoryServer(int(in.GetPort()))
server.Start(s.mockWriter, in.Prefix)
s.loader = server
}
err = s.loader.Load()
return
}
func (s *mockServerController) GetConfig(ctx context.Context, in *Empty) (reply *MockConfig, err error) {
reply = &MockConfig{
Prefix: "/mock/server",
Prefix: s.prefix,
Config: string(s.mockWriter.GetData()),
}
if dServer, ok := s.loader.(mock.DynamicServer); ok {
if port, pErr := strconv.Atoi(dServer.GetPort()); pErr == nil {
reply.Port = int32(port)
}
}
return
}

Expand Down
Loading
Loading