feat: support importing from Postman (#179)

* feat: support importing from Postman

* add ui validation

* fix the nested situation
This commit is contained in:
Rick 2023-08-23 16:32:08 +08:00 committed by GitHub
parent 78f517c2cb
commit 98a8d5d7d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1393 additions and 510 deletions

View File

@ -25,6 +25,7 @@ SOFTWARE.
package cmd
import (
"errors"
"fmt"
"os"
@ -50,6 +51,7 @@ func createConvertCommand() (c *cobra.Command) {
"The file pattern which try to execute the test cases. Brace expansion is supported, such as: test-suite-{1,2}.yaml")
flags.StringVarP(&opt.converter, "converter", "", "",
fmt.Sprintf("The converter format, supported: %s", util.Keys(converters)))
flags.StringVarP(&opt.source, "source", "", "", "The source format, supported: postman")
flags.StringVarP(&opt.target, "target", "t", "", "The target file path")
_ = c.MarkFlagRequired("pattern")
@ -60,11 +62,21 @@ func createConvertCommand() (c *cobra.Command) {
type convertOption struct {
pattern string
converter string
source string
target string
}
func (o *convertOption) preRunE(c *cobra.Command, args []string) (err error) {
o.target = util.EmptyThenDefault(o.target, "sample.jmx")
switch o.source {
case "postman":
o.target = util.EmptyThenDefault(o.target, "sample.yaml")
o.converter = "raw"
case "":
o.target = util.EmptyThenDefault(o.target, "sample.jmx")
default:
err = errors.New("only postman supported")
}
return
}
@ -74,23 +86,41 @@ func (o *convertOption) runE(c *cobra.Command, args []string) (err error) {
return
}
var output string
var suites []testing.TestSuite
if suites, err = loader.ListTestSuite(); err == nil {
if len(suites) == 0 {
err = fmt.Errorf("no suites found")
} else {
converter := generator.GetTestSuiteConverter(o.converter)
if converter == nil {
err = fmt.Errorf("no converter found")
} else {
output, err = converter.Convert(&suites[0])
}
}
var suite *testing.TestSuite
if o.source == "" {
suite, err = getSuiteFromFile(o.pattern)
} else {
suite, err = generator.NewPostmanImporter().ConvertFromFile(o.pattern)
}
if output != "" {
err = os.WriteFile(o.target, []byte(output), 0644)
if err != nil {
return
}
converter := generator.GetTestSuiteConverter(o.converter)
if converter == nil {
err = fmt.Errorf("no converter found")
} else {
var output string
output, err = converter.Convert(suite)
if output != "" {
err = os.WriteFile(o.target, []byte(output), 0644)
}
}
return
}
func getSuiteFromFile(pattern string) (suite *testing.TestSuite, err error) {
loader := testing.NewFileWriter("")
if err = loader.Put(pattern); err == nil {
var suites []testing.TestSuite
if suites, err = loader.ListTestSuite(); err == nil {
if len(suites) > 0 {
suite = &suites[0]
} else {
err = errors.New("no suites found")
}
}
}
return
}

View File

@ -25,6 +25,7 @@ SOFTWARE.
package cmd_test
import (
_ "embed"
"io"
"os"
"path"
@ -85,4 +86,19 @@ func TestConvert(t *testing.T) {
err := c.Execute()
assert.Error(t, err)
})
t.Run("not supported source format", func(t *testing.T) {
c.SetArgs([]string{"convert", "--source=fake"})
err := c.Execute()
assert.Error(t, err)
})
t.Run("convert from postmant", func(t *testing.T) {
tmpFile := path.Join(os.TempDir(), time.Now().String())
defer os.RemoveAll(tmpFile)
c.SetArgs([]string{"convert", "--source=postman", "--target=", tmpFile, "-p=testdata/postman.json"})
err := c.Execute()
assert.NoError(t, err)
})
}

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd_test
import (

35
cmd/testdata/postman.json vendored Normal file
View File

@ -0,0 +1,35 @@
{
"info": {
"_postman_id": "0da1f6bf-fdbb-46a5-ac46-873564e2259c",
"name": "New Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "6795120",
"_collection_link": "https://www.postman.com/ks-devops/workspace/kubesphere-devops/collection/6795120-0da1f6bf-fdbb-46a5-ac46-873564e2259c?action=share&creator=6795120&source=collection_link"
},
"item": [
{
"name": "New Request",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "key",
"value": "value",
"description": "description",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{}"
},
"url": {
"raw": "http://localhost?key=value"
}
}
}
]
}

View File

@ -137,6 +137,7 @@ function loadStores() {
loadStores()
const dialogVisible = ref(false)
const importDialogVisible = ref(false)
const suiteCreatingLoading = ref(false)
const suiteFormRef = ref<FormInstance>()
const testSuiteForm = reactive({
@ -144,20 +145,27 @@ const testSuiteForm = reactive({
api: '',
store: ''
})
const importSuiteFormRef = ref<FormInstance>()
const importSuiteForm = reactive({
url: '',
store: ''
})
function openTestSuiteCreateDialog() {
dialogVisible.value = true
}
function openTestSuiteImportDialog() {
importDialogVisible.value = true
}
const rules = reactive<FormRules<Suite>>({
name: [{ required: true, message: 'Name is required', trigger: 'blur' }],
store: [{ required: true, message: 'Location is required', trigger: 'blur' }]
})
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
console.log(formEl)
await formEl.validate((valid: boolean, fields) => {
console.log(valid, fields)
if (valid) {
suiteCreatingLoading.value = true
@ -184,6 +192,40 @@ const submitForm = async (formEl: FormInstance | undefined) => {
})
}
const importSuiteFormRules = reactive<FormRules<Suite>>({
url: [
{ required: true, message: 'URL is required', trigger: 'blur' },
{ type: 'url', message: 'Should be a valid URL value', trigger: 'blur' }
],
store: [{ required: true, message: 'Location is required', trigger: 'blur' }]
})
const importSuiteFormSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid: boolean, fields) => {
if (valid) {
suiteCreatingLoading.value = true
const requestOptions = {
method: 'POST',
headers: {
'X-Store-Name': importSuiteForm.store
},
body: JSON.stringify({
url: importSuiteForm.url
})
}
fetch('/server.Runner/ImportTestSuite', requestOptions)
.then((response) => response.json())
.then(() => {
loadStores()
importDialogVisible.value = false
formEl.resetFields()
})
}
})
}
const filterText = ref('')
watch(filterText, (val) => {
treeRef.value!.filter(val)
@ -209,6 +251,9 @@ const viewName = ref('testcase')
<el-button type="primary" @click="openTestSuiteCreateDialog"
data-intro="Click here to create a new test suite"
test-id="open-new-suite-dialog" :icon="Edit">New</el-button>
<el-button type="primary" @click="openTestSuiteImportDialog"
data-intro="Click here to import from Postman"
test-id="open-import-suite-dialog">Import</el-button>
<el-input v-model="filterText" placeholder="Filter keyword" test-id="search" />
<el-tree
@ -292,6 +337,44 @@ const viewName = ref('testcase')
</template>
</el-dialog>
<el-dialog v-model="importDialogVisible" title="Import Test Suite" width="30%" draggable>
<template #footer>
<span class="dialog-footer">
<el-form
:rules="importSuiteFormRules"
:model="importSuiteForm"
ref="importSuiteFormRef"
status-icon label-width="120px">
<el-form-item label="Location" prop="store">
<el-select v-model="importSuiteForm.store" class="m-2"
test-id="suite-import-form-store"
filterable=true
default-first-option=true
placeholder="Storage Location" size="middle">
<el-option
v-for="item in stores"
:key="item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
</el-form-item>
<el-form-item label="URL" prop="url">
<el-input v-model="importSuiteForm.url" test-id="suite-import-form-api" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="importSuiteFormSubmit(importSuiteFormRef)"
test-id="suite-import-submit"
>Import</el-button
>
</el-form-item>
</el-form>
</span>
</template>
</el-dialog>
<TemplateFunctions/>
</template>

168
pkg/generator/importer.go Normal file
View File

@ -0,0 +1,168 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package generator
import (
"encoding/json"
"io"
"net/http"
"os"
"github.com/linuxsuren/api-testing/pkg/testing"
)
type PostmanCollection struct {
Collection Postman `json:"collection"`
}
type Postman struct {
Info PostmanInfo `json:"info"`
Item []PostmanItem `json:"item"`
}
type PostmanInfo struct {
Name string
}
type PostmanItem struct {
Name string `json:"name"`
Request PostmanRequest `json:"request"`
Item []PostmanItem `json:"item"`
}
type PostmanRequest struct {
Method string `json:"method"`
URL PostmanURL `json:"url"`
Header Paris `json:"header"`
Body PostmanBody `json:"body"`
}
type PostmanBody struct {
Mode string `json:"mode"`
Raw string `json:"raw"`
}
type PostmanURL struct {
Raw string `json:"raw"`
Path []string `json:"path"`
Query Paris `json:"query"`
}
type Paris []Pair
type Pair struct {
Key string `json:"key"`
Value string `json:"value"`
}
func (p Paris) ToMap() (result map[string]string) {
count := len(p)
if count == 0 {
return
}
result = make(map[string]string, count)
for _, item := range p {
result[item.Key] = item.Value
}
return
}
type Importer interface {
Convert(data []byte) (*testing.TestSuite, error)
ConvertFromFile(dataFile string) (*testing.TestSuite, error)
ConvertFromURL(dataURL string) (*testing.TestSuite, error)
}
type postmanImporter struct {
}
// NewPostmanImporter returns a new postman importer
func NewPostmanImporter() Importer {
return &postmanImporter{}
}
// Convert converts the postman data to test suite
func (p *postmanImporter) Convert(data []byte) (suite *testing.TestSuite, err error) {
postman := &Postman{}
if err = json.Unmarshal(data, postman); err != nil {
return
}
if postman.Info.Name == "" {
postmanCollection := &PostmanCollection{}
if err = json.Unmarshal(data, postmanCollection); err != nil {
return
}
postman = &postmanCollection.Collection
}
suite = &testing.TestSuite{}
suite.Name = postman.Info.Name
suite.Items = make([]testing.TestCase, len(postman.Item))
for i, item := range postman.Item {
if len(item.Item) == 0 {
suite.Items[i] = testing.TestCase{
Name: item.Name,
Request: testing.Request{
Method: item.Request.Method,
API: item.Request.URL.Raw,
Body: item.Request.Body.Raw,
Header: item.Request.Header.ToMap(),
},
}
} else {
for _, sub := range item.Item {
suite.Items[i] = testing.TestCase{
Name: item.Name + " " + sub.Name,
Request: testing.Request{
Method: sub.Request.Method,
API: sub.Request.URL.Raw,
Body: sub.Request.Body.Raw,
Header: sub.Request.Header.ToMap(),
},
}
}
}
}
return
}
func (p *postmanImporter) ConvertFromFile(dataFile string) (suite *testing.TestSuite, err error) {
var data []byte
if data, err = os.ReadFile(dataFile); err == nil {
suite, err = p.Convert(data)
}
return
}
func (p *postmanImporter) ConvertFromURL(dataURL string) (suite *testing.TestSuite, err error) {
var resp *http.Response
if resp, err = http.Get(dataURL); err == nil {
var data []byte
if data, err = io.ReadAll(resp.Body); err == nil {
suite, err = p.Convert(data)
}
}
return
}

View File

@ -0,0 +1,107 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package generator
import (
"net/http"
"strings"
"testing"
_ "embed"
"github.com/h2non/gock"
"github.com/stretchr/testify/assert"
)
func TestPostmanImport(t *testing.T) {
importer := NewPostmanImporter()
converter := GetTestSuiteConverter("raw")
if !assert.NotNil(t, converter) {
return
}
t.Run("empty", func(t *testing.T) {
suite, err := importer.Convert([]byte(emptyJSON))
assert.NoError(t, err)
var result string
result, err = converter.Convert(suite)
assert.NoError(t, err)
assert.Equal(t, emptyJSON, strings.TrimSpace(result))
})
t.Run("simple postman, from []byte", func(t *testing.T) {
suite, err := importer.Convert([]byte(simplePostman))
assert.NoError(t, err)
var result string
result, err = converter.Convert(suite)
assert.NoError(t, err)
assert.Equal(t, expectedSuiteFromPostman, strings.TrimSpace(result), result)
})
t.Run("simple postman, from file", func(t *testing.T) {
suite, err := importer.ConvertFromFile("testdata/postman.json")
assert.NoError(t, err)
var result string
result, err = converter.Convert(suite)
assert.NoError(t, err)
assert.Equal(t, expectedSuiteFromPostman, strings.TrimSpace(result), result)
})
t.Run("simple postman, from URl", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Get("/").Reply(http.StatusOK).BodyString(simplePostman)
suite, err := importer.ConvertFromURL(urlFoo)
assert.NoError(t, err)
var result string
result, err = converter.Convert(suite)
assert.NoError(t, err)
assert.Equal(t, expectedSuiteFromPostman, strings.TrimSpace(result), result)
})
t.Run("nil data", func(t *testing.T) {
_, err := importer.Convert(nil)
assert.Error(t, err)
})
t.Run("pairs toMap", func(t *testing.T) {
pairs := Paris{}
assert.Equal(t, 0, len(pairs.ToMap()))
})
}
const emptyJSON = "{}"
const urlFoo = "http://foo"
//go:embed testdata/postman.json
var simplePostman string
//go:embed testdata/expected_suite_from_postman.yaml
var expectedSuiteFromPostman string

View File

@ -0,0 +1,9 @@
name: New Collection
items:
- name: New Request
request:
api: http://localhost?key=value
method: GET
header:
key: value
body: '{}'

35
pkg/generator/testdata/postman.json vendored Normal file
View File

@ -0,0 +1,35 @@
{
"info": {
"_postman_id": "0da1f6bf-fdbb-46a5-ac46-873564e2259c",
"name": "New Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "6795120",
"_collection_link": "https://www.postman.com/ks-devops/workspace/kubesphere-devops/collection/6795120-0da1f6bf-fdbb-46a5-ac46-873564e2259c?action=share&creator=6795120&source=collection_link"
},
"item": [
{
"name": "New Request",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "key",
"value": "value",
"description": "description",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{}"
},
"url": {
"raw": "http://localhost?key=value"
}
}
}
]
}

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
// Package server provides a GRPC based server
package server
@ -265,6 +289,45 @@ func (s *server) CreateTestSuite(ctx context.Context, in *TestSuiteIdentity) (re
return
}
func (s *server) ImportTestSuite(ctx context.Context, in *TestSuiteSource) (result *CommonResult, err error) {
result = &CommonResult{}
if in.Kind != "postman" && in.Kind != "" {
result.Success = false
result.Message = fmt.Sprintf("not support kind: %s", in.Kind)
return
}
var suite *testing.TestSuite
importer := generator.NewPostmanImporter()
if in.Url != "" {
suite, err = importer.ConvertFromURL(in.Url)
} else if in.Data != "" {
suite, err = importer.Convert([]byte(in.Data))
} else {
err = errors.New("url or data is required")
}
if err != nil {
result.Success = false
result.Message = err.Error()
return
}
loader := s.getLoader(ctx)
if err = loader.CreateSuite(suite.Name, suite.API); err != nil {
return
}
for _, item := range suite.Items {
if err = loader.CreateTestCase(suite.Name, item); err != nil {
break
}
}
result.Success = true
return
}
func (s *server) GetTestSuite(ctx context.Context, in *TestSuiteIdentity) (result *TestSuite, err error) {
loader := s.getLoader(ctx)
var suite *testing.TestSuite

View File

@ -1,3 +1,27 @@
/**
MIT License
Copyright (c) 2023 API Testing Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package server
import (
@ -626,6 +650,42 @@ func TestCodeGenerator(t *testing.T) {
assert.True(t, reply.Success)
}
})
t.Run("ImportTestSuite, url or data is required", func(t *testing.T) {
result, err := server.ImportTestSuite(ctx, &TestSuiteSource{})
assert.Error(t, err)
assert.False(t, result.Success)
assert.Equal(t, "url or data is required", result.Message)
})
t.Run("ImportTestSuite, invalid kind", func(t *testing.T) {
result, err := server.ImportTestSuite(ctx, &TestSuiteSource{Kind: "fake"})
assert.NoError(t, err)
assert.False(t, result.Success)
assert.Equal(t, "not support kind: fake", result.Message)
})
t.Run("ImportTestSuite, import from string", func(t *testing.T) {
result, err := server.ImportTestSuite(ctx, &TestSuiteSource{
Kind: "postman",
Data: simplePostman,
})
assert.NoError(t, err)
assert.True(t, result.Success)
})
t.Run("ImportTestSuite, import from URL", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Get("/").Reply(http.StatusOK).BodyString(simplePostman)
// already exist
result, err := server.ImportTestSuite(ctx, &TestSuiteSource{
Kind: "postman",
Url: urlFoo,
})
assert.Error(t, err)
assert.False(t, result.Success)
})
}
func TestFunctionsQueryStream(t *testing.T) {
@ -764,6 +824,9 @@ var simpleSuite string
//go:embed testdata/simple_testcase.yaml
var simpleTestCase string
//go:embed testdata/postman.json
var simplePostman string
const urlFoo = "http://foo"
type fakeServerStream struct {

File diff suppressed because it is too large Load Diff

View File

@ -133,6 +133,40 @@ func local_request_Runner_CreateTestSuite_0(ctx context.Context, marshaler runti
}
func request_Runner_ImportTestSuite_0(ctx context.Context, marshaler runtime.Marshaler, client RunnerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TestSuiteSource
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ImportTestSuite(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Runner_ImportTestSuite_0(ctx context.Context, marshaler runtime.Marshaler, server RunnerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TestSuiteSource
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ImportTestSuite(ctx, &protoReq)
return msg, metadata, err
}
func request_Runner_GetTestSuite_0(ctx context.Context, marshaler runtime.Marshaler, client RunnerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TestSuiteIdentity
var metadata runtime.ServerMetadata
@ -1209,6 +1243,31 @@ func RegisterRunnerHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser
})
mux.Handle("POST", pattern_Runner_ImportTestSuite_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/server.Runner/ImportTestSuite", runtime.WithHTTPPathPattern("/server.Runner/ImportTestSuite"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Runner_ImportTestSuite_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Runner_ImportTestSuite_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Runner_GetTestSuite_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -2023,6 +2082,28 @@ func RegisterRunnerHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli
})
mux.Handle("POST", pattern_Runner_ImportTestSuite_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/server.Runner/ImportTestSuite", runtime.WithHTTPPathPattern("/server.Runner/ImportTestSuite"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Runner_ImportTestSuite_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Runner_ImportTestSuite_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Runner_GetTestSuite_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -2671,6 +2752,8 @@ var (
pattern_Runner_CreateTestSuite_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"server.Runner", "CreateTestSuite"}, ""))
pattern_Runner_ImportTestSuite_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"server.Runner", "ImportTestSuite"}, ""))
pattern_Runner_GetTestSuite_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"server.Runner", "GetTestSuite"}, ""))
pattern_Runner_UpdateTestSuite_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"server.Runner", "UpdateTestSuite"}, ""))
@ -2737,6 +2820,8 @@ var (
forward_Runner_CreateTestSuite_0 = runtime.ForwardResponseMessage
forward_Runner_ImportTestSuite_0 = runtime.ForwardResponseMessage
forward_Runner_GetTestSuite_0 = runtime.ForwardResponseMessage
forward_Runner_UpdateTestSuite_0 = runtime.ForwardResponseMessage

View File

@ -10,6 +10,7 @@ service Runner {
rpc GetSuites(Empty) returns (Suites) {}
rpc CreateTestSuite(TestSuiteIdentity) returns (HelloReply) {}
rpc ImportTestSuite(TestSuiteSource) returns (CommonResult) {}
rpc GetTestSuite(TestSuiteIdentity) returns (TestSuite) {}
rpc UpdateTestSuite(TestSuite) returns (HelloReply) {}
rpc DeleteTestSuite(TestSuiteIdentity) returns (HelloReply) {}
@ -66,6 +67,12 @@ message TestCaseIdentity {
string testcase = 2;
}
message TestSuiteSource {
string kind = 1;
string url = 2;
string data = 3;
}
message TestSuite {
string name = 1;
string api = 2;

View File

@ -26,6 +26,7 @@ type RunnerClient interface {
Run(ctx context.Context, in *TestTask, opts ...grpc.CallOption) (*TestResult, error)
GetSuites(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Suites, error)
CreateTestSuite(ctx context.Context, in *TestSuiteIdentity, opts ...grpc.CallOption) (*HelloReply, error)
ImportTestSuite(ctx context.Context, in *TestSuiteSource, opts ...grpc.CallOption) (*CommonResult, error)
GetTestSuite(ctx context.Context, in *TestSuiteIdentity, opts ...grpc.CallOption) (*TestSuite, error)
UpdateTestSuite(ctx context.Context, in *TestSuite, opts ...grpc.CallOption) (*HelloReply, error)
DeleteTestSuite(ctx context.Context, in *TestSuiteIdentity, opts ...grpc.CallOption) (*HelloReply, error)
@ -98,6 +99,15 @@ func (c *runnerClient) CreateTestSuite(ctx context.Context, in *TestSuiteIdentit
return out, nil
}
func (c *runnerClient) ImportTestSuite(ctx context.Context, in *TestSuiteSource, opts ...grpc.CallOption) (*CommonResult, error) {
out := new(CommonResult)
err := c.cc.Invoke(ctx, "/server.Runner/ImportTestSuite", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *runnerClient) GetTestSuite(ctx context.Context, in *TestSuiteIdentity, opts ...grpc.CallOption) (*TestSuite, error) {
out := new(TestSuite)
err := c.cc.Invoke(ctx, "/server.Runner/GetTestSuite", in, out, opts...)
@ -389,6 +399,7 @@ type RunnerServer interface {
Run(context.Context, *TestTask) (*TestResult, error)
GetSuites(context.Context, *Empty) (*Suites, error)
CreateTestSuite(context.Context, *TestSuiteIdentity) (*HelloReply, error)
ImportTestSuite(context.Context, *TestSuiteSource) (*CommonResult, error)
GetTestSuite(context.Context, *TestSuiteIdentity) (*TestSuite, error)
UpdateTestSuite(context.Context, *TestSuite) (*HelloReply, error)
DeleteTestSuite(context.Context, *TestSuiteIdentity) (*HelloReply, error)
@ -440,6 +451,9 @@ func (UnimplementedRunnerServer) GetSuites(context.Context, *Empty) (*Suites, er
func (UnimplementedRunnerServer) CreateTestSuite(context.Context, *TestSuiteIdentity) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateTestSuite not implemented")
}
func (UnimplementedRunnerServer) ImportTestSuite(context.Context, *TestSuiteSource) (*CommonResult, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportTestSuite not implemented")
}
func (UnimplementedRunnerServer) GetTestSuite(context.Context, *TestSuiteIdentity) (*TestSuite, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTestSuite not implemented")
}
@ -594,6 +608,24 @@ func _Runner_CreateTestSuite_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _Runner_ImportTestSuite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TestSuiteSource)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RunnerServer).ImportTestSuite(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/server.Runner/ImportTestSuite",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RunnerServer).ImportTestSuite(ctx, req.(*TestSuiteSource))
}
return interceptor(ctx, in, info, handler)
}
func _Runner_GetTestSuite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TestSuiteIdentity)
if err := dec(in); err != nil {
@ -1143,6 +1175,10 @@ var Runner_ServiceDesc = grpc.ServiceDesc{
MethodName: "CreateTestSuite",
Handler: _Runner_CreateTestSuite_Handler,
},
{
MethodName: "ImportTestSuite",
Handler: _Runner_ImportTestSuite_Handler,
},
{
MethodName: "GetTestSuite",
Handler: _Runner_GetTestSuite_Handler,

35
pkg/server/testdata/postman.json vendored Normal file
View File

@ -0,0 +1,35 @@
{
"info": {
"_postman_id": "0da1f6bf-fdbb-46a5-ac46-873564e2259c",
"name": "New Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "6795120",
"_collection_link": "https://www.postman.com/ks-devops/workspace/kubesphere-devops/collection/6795120-0da1f6bf-fdbb-46a5-ac46-873564e2259c?action=share&creator=6795120&source=collection_link"
},
"item": [
{
"name": "New Request",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "key",
"value": "value",
"description": "description",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{}"
},
"url": {
"raw": "http://localhost?key=value"
}
}
}
]
}