Skip to content

Commit

Permalink
feat: support getting suggested APIs from spec
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen committed Jul 14, 2023
1 parent 3040a92 commit ecc6454
Show file tree
Hide file tree
Showing 30 changed files with 1,180 additions and 551 deletions.
3 changes: 3 additions & 0 deletions .github/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

make test-all
8 changes: 8 additions & 0 deletions CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ Print the code of lines:
```shell
git ls-files | xargs cloc
```

## Setup development environment
It's highly recommended you to configure the git pre-commit hook. It will force to run unit tests before commit.
Run the following command:

```shell
make install-precheck
```
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ COPY pkg/ pkg/
COPY extensions/ extensions/
COPY console/atest-ui atest-ui/
COPY sample/ sample/
COPY docs/ docs/
COPY go.mod go.mod
COPY go.sum go.sum
COPY go.work go.work
Expand Down
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ run-image:
docker run -p 7070:7070 -p 8080:8080 ghcr.io/linuxsuren/api-testing:master
run-server:
go run . server --local-storage 'sample/*.yaml' --console-path console/atest-ui/dist
copy: build
run-console:
cd console/atest-ui && npm run dev
copy:
sudo cp bin/atest /usr/local/bin/
copy-restart: build
copy-restart: build-embed-ui
atest service stop
make copy
atest service restart
Expand All @@ -39,6 +41,9 @@ test-store-orm:
go tool cover -func=store-orm-coverage.out
test-all: test test-collector test-store-orm

install-precheck:
cp .github/pre-commit .git/hooks/pre-commit

grpc:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
Expand Down
4 changes: 4 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/linuxsuren/api-testing/pkg/util"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

func createServerCmd(gRPCServer gRPCServer, httpServer server.HTTPServer) (c *cobra.Command) {
Expand Down Expand Up @@ -102,6 +103,9 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
removeServer := server.NewRemoteServer(loader)
s := o.gRPCServer
go func() {
if gRPCServer, ok := s.(reflection.GRPCServer); ok {
reflection.Register(gRPCServer)
}
server.RegisterRunnerServer(s, removeServer)
log.Printf("gRPC server listening at %v", lis.Addr())
s.Serve(lis)
Expand Down
83 changes: 83 additions & 0 deletions console/atest-ui/src/views/APIInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps({
suite: String,
address: String,
method: String,
operationId: String,
})
const emit = defineEmits(['update:address', 'update:method', 'update:operationId'])
function loadCache(callback : Function) {
if (localCache.value.length > 0) {
callback()
return
}
const suite = props.suite
const requestOptions = {
method: 'POST',
body: JSON.stringify({
name: suite
})
};
fetch('/server.Runner/GetSuggestedAPIs', requestOptions)
.then(response => response.json())
.then(e => {
localCache.value = e.data
callback()
})
}
const querySuggestedAPIs = (queryString: string, cb: (arg: any) => void) => {
loadCache(function(){
const results = queryString
? localCache.value.filter(createFilter(queryString))
: localCache.value
cb(results.slice(0, 10))
})
}
const localCache = ref({} as TestCase[])
interface Request {
api: string,
method: string,
}
interface TestCase {
name: string,
request: Request,
}
const createFilter = (queryString: string) => {
return (v: TestCase) => {
return (
v.request.api.toLowerCase().indexOf(queryString.toLowerCase()) !== -1
)
}
}
const handleSelect = (item: TestCase) => {
emit('update:address', item.request.api)
// emit('update:method', item.request.method)
// emit('update:operationId', item.name)
}
</script>

<template>
<el-autocomplete
:value="address"
@input="$emit('update:address', $event)"
:fetch-suggestions="querySuggestedAPIs"
@select="handleSelect"
placeholder="API Address"
style="width: 70%; margin-left: 5px; margin-right: 5px;"
>
<template #default="{ item }">
<div class="value">{{ item.request.method }}</div>
<span class="link">{{ item.request.api }}</span>
</template>
</el-autocomplete>
</template>
3 changes: 2 additions & 1 deletion console/atest-ui/src/views/TestCase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ElMessage } from 'element-plus'
import { Edit, Delete } from '@element-plus/icons-vue'
import JsonViewer from 'vue-json-viewer'
import _ from 'lodash';
import APIINput from './APIInput.vue'
const props = defineProps({
name: String,
Expand Down Expand Up @@ -397,7 +398,7 @@ function flattenObject(obj: any): any {
<el-select v-model="testCaseWithSuite.data.request.method" class="m-2" placeholder="Method" size="middle">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input v-model="testCaseWithSuite.data.request.api" placeholder="API Address" style="width: 70%; margin-left: 5px; margin-right: 5px;"/>
<APIINput v-model="testCaseWithSuite.data.request.api" :suite="suite" />
<el-button type="primary" @click="sendRequest" :loading="requestLoading">Send</el-button>
</el-header>

Expand Down
67 changes: 35 additions & 32 deletions console/atest-ui/src/views/TestSuite.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ElMessage } from 'element-plus'
import { reactive, ref, watch } from 'vue'
import { Edit } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import APIINput from './APIInput.vue'
const props = defineProps({
name: String,
Expand All @@ -12,9 +13,20 @@ const emit = defineEmits(['updated'])
interface Suite {
name: string;
api: string;
spec: {
kind: string;
url: string;
}
}
const suite = ref({} as Suite)
const suite = ref({
name: "",
api: "",
spec: {
kind: "",
url: "",
}
} as Suite)
function load() {
const requestOptions = {
method: 'POST',
Expand All @@ -25,10 +37,7 @@ function load() {
fetch('/server.Runner/GetTestSuite', requestOptions)
.then(response => response.json())
.then(e => {
suite.value = {
name: e.name,
api: e.api,
} as Suite
suite.value = e
}).catch(e => {
ElMessage.error('Oops, ' + e)
});
Expand Down Expand Up @@ -61,6 +70,7 @@ const testCaseForm = reactive({
suiteName: "",
name: "",
api: "",
method: "GET",
})
const rules = reactive<FormRules<Suite>>({
name: [
Expand All @@ -69,23 +79,9 @@ const rules = reactive<FormRules<Suite>>({
})
function openNewTestCaseDialog() {
loadTestSuites()
dialogVisible.value = true
}
function loadTestSuites() {
const requestOptions = {
method: 'POST'
};
fetch('/server.Runner/GetSuites', requestOptions)
.then(response => response.json())
.then(d => {
Object.keys(d.data).map(k => {
testSuiteList.value.push(k)
})
});
}
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid: boolean, fields) => {
Expand All @@ -95,7 +91,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
const requestOptions = {
method: 'POST',
body: JSON.stringify({
suiteName: testCaseForm.suiteName,
suiteName: props.name,
data: {
name: testCaseForm.name,
request: {
Expand Down Expand Up @@ -139,13 +135,27 @@ function del() {
}
const suiteCreatingLoading = ref(false)
const testSuiteList = ref([])
const apiSpecKinds = [
{
value: 'swagger',
label: 'Swagger',
},
{
value: 'openapi',
label: 'OpenAPI',
}
]
</script>

<template>
<div class="common-layout">
<el-text class="mx-1" type="primary">{{suite.name}}</el-text>
<el-input class="mx-1" v-model="suite.api" placeholder="API"></el-input>
<el-select v-model="suite.spec.kind" class="m-2" placeholder="API Spec Kind" size="middle">
<el-option v-for="item in apiSpecKinds" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input class="mx-1" v-model="suite.spec.url" placeholder="API Spec URL"></el-input>

<el-button type="primary" @click="save">Save</el-button>
<el-button type="primary" @click="del">Delete</el-button>
Expand All @@ -164,21 +174,14 @@ const testSuiteList = ref([])
label-width="120px"
class="demo-ruleForm"
>
<el-form-item label="Suite" prop="suite">
<el-select class="m-2" v-model="testCaseForm.suiteName" placeholder="Select" size="large">
<el-option
v-for="item in testSuiteList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="Name" prop="name">
<el-input v-model="testCaseForm.name" />
</el-form-item>
<el-form-item label="Method" prop="method">
<el-input v-model="testCaseForm.method" />
</el-form-item>
<el-form-item label="API" prop="api">
<el-input v-model="testCaseForm.api" placeholder="http://foo" />
<APIINput v-model="testCaseForm.api" :suite="name" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(testcaseFormRef)" :loading="suiteCreatingLoading">Submit</el-button>
Expand Down
22 changes: 22 additions & 0 deletions sample/api-testing-schema.json → docs/api-testing-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
"api": {
"type": "string"
},
"spec": {
"type": "object",
"spec": {
"$ref": "#/definitions/Spec"
}
},
"items": {
"type": "array",
"items": {
Expand All @@ -27,6 +33,22 @@
],
"title": "APITesting"
},
"Spec": {
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": ["openapi", "swagger"]
},
"url": {
"type": "string",
"qt-uri-protocols": [
"https", "http"
]
}
}
},
"Item": {
"type": "object",
"additionalProperties": true,
Expand Down
6 changes: 6 additions & 0 deletions docs/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package docs

import _ "embed"

//go:embed api-testing-schema.json
var Schema string
8 changes: 8 additions & 0 deletions extensions/store-orm/pkg/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,21 @@ func ConvertToDBTestSuite(suite *remote.TestSuite) (result *TestSuite) {
Name: suite.Name,
API: suite.Api,
}
if suite.Spec != nil {
result.SpecKind = suite.Spec.Kind
result.SpecURL = suite.Spec.Url
}
return
}

func ConvertToGRPCTestSuite(suite *TestSuite) (result *remote.TestSuite) {
result = &remote.TestSuite{
Name: suite.Name,
Api: suite.API,
Spec: &remote.APISpec{
Kind: suite.SpecKind,
Url: suite.SpecURL,
},
}
return
}
Expand Down
1 change: 1 addition & 0 deletions extensions/store-orm/pkg/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func TestConvertTestSuite(t *testing.T) {
assert.Equal(t, &remote.TestSuite{
Name: "name",
Api: "api",
Spec: &remote.APISpec{},
}, result)
})
}
Expand Down
6 changes: 4 additions & 2 deletions extensions/store-orm/pkg/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type TestCase struct {
}

type TestSuite struct {
Name string
API string
Name string
API string
SpecKind string
SpecURL string
}

0 comments on commit ecc6454

Please sign in to comment.