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 import native data #539

Merged
merged 2 commits into from
Sep 30, 2024
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
12 changes: 11 additions & 1 deletion console/atest-ui/src/views/TestSuite.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const suite = ref({
}
}
} as Suite)
const shareLink = ref('')
function load() {
const store = Cache.GetCurrentStore()
if (!props.name || store.name === '') return
Expand All @@ -48,6 +49,8 @@ function load() {
value: ''
} as Pair)
}

shareLink.value = `${window.location.href}api/v1/suites/${e.name}/yaml?store=${store.name}`
},
(e) => {
ElMessage.error('Oops, ' + e)
Expand Down Expand Up @@ -225,7 +228,11 @@ const yamlDialogVisible = ref(false)
function viewYaml() {
yamlDialogVisible.value = true
API.GetTestSuiteYaml(props.name, (d) => {
yamlFormat.value = yaml.dump(yaml.load(atob(d.data)))
try {
yamlFormat.value = yaml.dump(yaml.load(atob(d.data)))
} catch (e) {
ElMessage.error('Oops, ' + e)
}
})
}

Expand Down Expand Up @@ -330,6 +337,9 @@ const targetSuiteDuplicateName = ref('')
<el-divider />
</div>

<div class="button-container">
Share link: <el-input readonly v-model="shareLink" style="width: 80%" />
</div>
<div class="button-container">
<el-button type="primary" @click="save" v-if="!Cache.GetCurrentStore().readOnly">{{
t('button.save')
Expand Down
31 changes: 28 additions & 3 deletions console/atest-ui/src/views/TestingPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ const testSuiteForm = reactive({
const importSuiteFormRef = ref<FormInstance>()
const importSuiteForm = reactive({
url: '',
store: ''
store: '',
kind: ''
})

function openTestSuiteCreateDialog() {
Expand Down Expand Up @@ -290,7 +291,8 @@ const importSuiteFormRules = reactive<FormRules<Suite>>({
{ 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' }]
store: [{ required: true, message: 'Location is required', trigger: 'blur' }],
kind: [{ required: true, message: 'Kind is required', trigger: 'blur' }]
})
const importSuiteFormSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
Expand All @@ -302,6 +304,8 @@ const importSuiteFormSubmit = async (formEl: FormInstance | undefined) => {
loadStores()
importDialogVisible.value = false
formEl.resetFields()
}, (e) => {
ElMessage.error(e)
})
}
})
Expand Down Expand Up @@ -348,6 +352,14 @@ const suiteKinds = [{
"name": "tRPC",
}]

const importSourceKinds = [{
"name": "Postman",
"value": "postman"
}, {
"name": "Native",
"value": "native"
}]

</script>

<template>
Expand Down Expand Up @@ -470,7 +482,6 @@ const suiteKinds = [{
</el-dialog>

<el-dialog v-model="importDialogVisible" title="Import Test Suite" width="30%" draggable>
<span>Supported source URL: Postman collection share link</span>
<template #footer>
<span class="dialog-footer">
<el-form
Expand All @@ -492,6 +503,20 @@ const suiteKinds = [{
/>
</el-select>
</el-form-item>
<el-form-item label="Kind" prop="kind">
<el-select v-model="importSuiteForm.kind" class="m-2"
filterable=true
test-id="suite-import-form-kind"
default-first-option=true
placeholder="Kind" size="middle">
<el-option
v-for="item in importSourceKinds"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="URL" prop="url">
<el-input v-model="importSuiteForm.url" test-id="suite-import-form-api" placeholder="https://api.postman.com/collections/xxx" />
</el-form-item>
Expand Down
15 changes: 7 additions & 8 deletions console/atest-ui/src/views/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ function CreateTestSuite(suite: TestSuite,
interface ImportSource {
store: string
url: string
kind: string
}

function UpdateTestSuite(suite: any,
Expand Down Expand Up @@ -145,7 +146,7 @@ function ConvertTestSuite(suiteName: string, genertor: string,
}

function DuplicateTestSuite(sourceSuiteName: string, targetSuiteName: string,
callback: (d: any) => void, errHandle?: ((reason: any) => PromiseLike<never>) | undefined | null ) {
callback: (d: any) => void, errHandle?: ((reason: any) => PromiseLike<never>) | undefined | null) {
const requestOptions = {
method: 'POST',
headers: {
Expand All @@ -162,21 +163,19 @@ function DuplicateTestSuite(sourceSuiteName: string, targetSuiteName: string,
.then(callback).catch(errHandle)
}

function ImportTestSuite(source: ImportSource, callback: (d: any) => void) {
function ImportTestSuite(source: ImportSource, callback: (d: any) => void,
errHandle?: (e: any) => void | null) {
const requestOptions = {
method: 'POST',
headers: {
'X-Store-Name': source.store,
'X-Auth': getToken()
},
body: JSON.stringify({
url: source.url
})
body: JSON.stringify(source)
}

fetch(`/api/v1/suites/import`, requestOptions)
.then(DefaultResponseProcess)
.then(callback)
fetch(`/api/v1/suites/import`, requestOptions).
then(DefaultResponseProcess).then(callback).catch(errHandle)
}

interface TestCase {
Expand Down
41 changes: 14 additions & 27 deletions pkg/generator/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ package generator

import (
"encoding/json"
"io"
"net/http"
"os"

"github.com/linuxsuren/api-testing/pkg/testing"
)
Expand Down Expand Up @@ -92,8 +89,12 @@ func (p Paris) ToMap() (result map[string]string) {
return
}

type Importer interface {
type DataImporter interface {
Convert(data []byte) (*testing.TestSuite, error)
}

type Importer interface {
DataImporter
ConvertFromFile(dataFile string) (*testing.TestSuite, error)
ConvertFromURL(dataURL string) (*testing.TestSuite, error)
}
Expand Down Expand Up @@ -122,13 +123,18 @@ func (p *postmanImporter) Convert(data []byte) (suite *testing.TestSuite, err er

suite = &testing.TestSuite{}
suite.Name = postman.Info.Name
if err = p.convertItems(postman.Item, "", suite); err != nil {
return
}

err = p.convertItems(postman.Item, "", suite)
return
}

func (p *postmanImporter) ConvertFromFile(dataFile string) (*testing.TestSuite, error) {
return convertFromFile(dataFile, p)
}

func (p *postmanImporter) ConvertFromURL(dataURLStr string) (*testing.TestSuite, error) {
return convertFromURL(dataURLStr, p)
}

func (p *postmanImporter) convertItems(items []PostmanItem, prefix string, suite *testing.TestSuite) (err error) {
for _, item := range items {
itemName := prefix + item.Name
Expand All @@ -150,22 +156,3 @@ func (p *postmanImporter) convertItems(items []PostmanItem, prefix string, suite
}
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
}
91 changes: 91 additions & 0 deletions pkg/generator/importer_native.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Copyright 2024 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.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package generator

import (
"encoding/base64"
"encoding/json"
"io"
"net/http"
"net/url"
"os"

"github.com/linuxsuren/api-testing/pkg/testing"
)

type nativeImporter struct {
}

type nativeData struct {
Data string `json:"data"`
}

func NewNativeImporter() Importer {
return &nativeImporter{}
}

func (p *nativeImporter) Convert(data []byte) (suite *testing.TestSuite, err error) {
nativeData := nativeData{}
if err = json.Unmarshal(data, &nativeData); err == nil {
var data []byte
if data, err = base64.StdEncoding.DecodeString(nativeData.Data); err == nil {
suite, err = testing.Parse(data)
}
}
return
}

func (p *nativeImporter) ConvertFromFile(dataFile string) (*testing.TestSuite, error) {
return convertFromFile(dataFile, p)
}

func (p *nativeImporter) ConvertFromURL(dataURLStr string) (*testing.TestSuite, error) {
return convertFromURL(dataURLStr, p)
}

func convertFromFile(dataFile string, dataImport DataImporter) (suite *testing.TestSuite, err error) {
var data []byte
if data, err = os.ReadFile(dataFile); err == nil {
suite, err = dataImport.Convert(data)
}
return
}

func convertFromURL(dataURLStr string, dataImport DataImporter) (suite *testing.TestSuite, err error) {
var req *http.Request
var resp *http.Response
var dataURL *url.URL

if dataURL, err = url.Parse(dataURLStr); err == nil {
req, err = http.NewRequest(http.MethodGet, dataURLStr, nil)
}

if err == nil {
// put all query params as headers
for k, v := range dataURL.Query() {
req.Header.Add(k, v[0])
}

if resp, err = http.DefaultClient.Do(req); err == nil {
var data []byte
if data, err = io.ReadAll(resp.Body); err == nil {
suite, err = dataImport.Convert(data)
}
}
}
return
}
52 changes: 52 additions & 0 deletions pkg/generator/importer_native_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2024 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.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generator

import (
"bytes"
"net/http"
"testing"

"github.com/h2non/gock"
"github.com/stretchr/testify/assert"

_ "embed"
)

func TestNativeImporter(t *testing.T) {
importer := NewNativeImporter()

t.Run("simple native, from []byte", func(t *testing.T) {
_, err := importer.Convert(simpleNativeData)
assert.NoError(t, err)
})

t.Run("read from file", func(t *testing.T) {
_, err := importer.ConvertFromFile("testdata/native.json")
assert.NoError(t, err)
})

t.Run("read from URL", func(t *testing.T) {
defer gock.Off()
gock.New(urlFoo).Get("/").Reply(http.StatusOK).Body(bytes.NewBuffer(simpleNativeData))

_, err := importer.ConvertFromURL(urlFoo)
assert.NoError(t, err)
})
}

//go:embed testdata/native.json
var simpleNativeData []byte
Loading
Loading