api-testing/pkg/testing/loader_file.go

473 lines
10 KiB
Go

/*
Copyright 2023-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 testing
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"sync"
"github.com/linuxsuren/api-testing/pkg/logging"
"github.com/linuxsuren/api-testing/pkg/util"
)
var (
loaderLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("loader")
)
type fileLoader struct {
paths []string
index int
parent string
lock *sync.RWMutex
}
// NewFileLoader creates the instance of file loader
func NewFileLoader() Loader {
return &fileLoader{index: -1, lock: &sync.RWMutex{}}
}
func NewFileWriter(parent string) Writer {
return &fileLoader{index: -1, parent: parent, lock: &sync.RWMutex{}}
}
// HasMore returns if there are more test cases
func (l *fileLoader) HasMore() bool {
l.index++
return l.index < len(l.paths) && l.index >= 0
}
// Load returns the test case content
func (l *fileLoader) Load() (data []byte, err error) {
targetFile := l.paths[l.index]
data, err = loadData(targetFile)
return
}
func loadData(targetFile string) (data []byte, err error) {
if strings.HasPrefix(targetFile, "http://") || strings.HasPrefix(targetFile, "https://") {
var ok bool
data, ok, err = gRPCCompitableRequest(targetFile)
if !ok && err == nil {
var resp *http.Response
if resp, err = http.Get(targetFile); err == nil {
data, err = io.ReadAll(resp.Body)
}
}
} else {
data, err = os.ReadFile(targetFile)
}
return
}
func gRPCCompitableRequest(targetURLStr string) (data []byte, ok bool, err error) {
if !strings.Contains(targetURLStr, "server.Runner/ConvertTestSuite") {
return
}
var targetURL *url.URL
if targetURL, err = url.Parse(targetURLStr); err != nil {
return
}
suite := targetURL.Query().Get("suite")
if suite == "" {
err = fmt.Errorf("suite is required")
return
}
payload := new(bytes.Buffer)
payload.WriteString(fmt.Sprintf(`{"TestSuite":"%s", "Generator":"raw"}`, suite))
var resp *http.Response
if resp, err = http.Post(targetURLStr, "", payload); err == nil {
if data, err = io.ReadAll(resp.Body); err != nil {
return
}
var gRPCData map[string]interface{}
if err = json.Unmarshal(data, &gRPCData); err == nil {
var obj interface{}
obj, ok = gRPCData["message"]
data = []byte(fmt.Sprintf("%v", obj))
}
}
return
}
// Put adds the test case path
func (l *fileLoader) Put(item string) (err error) {
l.lock.Lock()
defer l.lock.Unlock()
if l.parent == "" {
l.parent = filepath.Dir(item)
}
if strings.HasPrefix(item, "http://") || strings.HasPrefix(item, "https://") {
l.paths = append(l.paths, item)
return
}
for _, pattern := range util.Expand(item) {
var files []string
if files, err = filepath.Glob(pattern); err == nil {
l.paths = append(l.paths, files...)
}
loaderLogger.Info(pattern, "pattern", len(files))
}
return
}
// GetContext returns the context of current test case
func (l *fileLoader) GetContext() string {
return filepath.Dir(l.paths[l.index])
}
// GetCount returns the count of test cases
func (l *fileLoader) GetCount() int {
return len(l.paths)
}
// Reset resets the index
func (l *fileLoader) Reset() {
l.index = -1
}
func (l *fileLoader) ListTestSuite() (suites []TestSuite, err error) {
l.lock.RLocker().Lock()
defer l.lock.RUnlock()
for _, target := range l.paths {
var data []byte
var loadErr error
if data, loadErr = loadData(target); loadErr != nil {
loaderLogger.Info("failed to load data", "error", loadErr)
continue
}
var testSuite *TestSuite
if testSuite, loadErr = Parse(data); loadErr != nil {
loaderLogger.Info("failed to parse data", "error", loadErr, "from", target)
continue
}
suites = append(suites, *testSuite)
}
return
}
func (l *fileLoader) GetTestSuite(name string, full bool) (suite TestSuite, err error) {
var items []TestSuite
if items, err = l.ListTestSuite(); err == nil {
for _, item := range items {
if item.Name == name {
suite = item
break
}
}
}
return
}
func (l *fileLoader) CreateSuite(name, api string) (err error) {
if name == "" {
err = fmt.Errorf("name is required")
return
}
var absPath string
var suite *TestSuite
if suite, absPath, err = l.GetSuite(name); err != nil {
return
}
if suite != nil {
err = fmt.Errorf("suite %s already exists", name)
} else {
if l.parent == "" {
l.parent = filepath.Dir(absPath)
}
if err = os.MkdirAll(l.parent, 0755); err != nil {
err = fmt.Errorf("failed to create %q", l.parent)
return
}
newSuiteFile := path.Join(l.parent, fmt.Sprintf("%s.yaml", name))
if newSuiteFile, err = filepath.Abs(newSuiteFile); err == nil {
loaderLogger.Info("new suite file", "file", newSuiteFile)
suite := &TestSuite{
Name: name,
API: api,
}
if err = SaveTestSuiteToFile(suite, newSuiteFile); err == nil {
l.Put(newSuiteFile)
}
}
}
return
}
func (l *fileLoader) GetSuite(name string) (suite *TestSuite, absPath string, err error) {
l.lock.RLock()
defer l.lock.RUnlock()
for i := range l.paths {
suitePath := l.paths[i]
if absPath, err = filepath.Abs(suitePath); err != nil {
return
}
if suite, err = ParseTestSuiteFromFile(absPath); err != nil {
suite = nil
continue
}
if suite.Name == name {
return
} else {
suite = nil
}
}
return
}
// UpdateSuite updates the suite
func (l *fileLoader) UpdateSuite(suite TestSuite) (err error) {
var absPath string
var oldSuite *TestSuite
if oldSuite, absPath, err = l.GetSuite(suite.Name); err == nil {
suite.Items = oldSuite.Items // only update the suite info
err = SaveTestSuiteToFile(&suite, absPath)
}
return
}
func (l *fileLoader) DeleteSuite(name string) (err error) {
l.lock.Lock()
defer l.lock.Unlock()
for i := range l.paths {
suitePath := l.paths[i]
var suite *TestSuite
if suite, err = ParseTestSuiteFromFile(suitePath); err != nil {
continue
}
if suite.Name == name {
err = os.Remove(suitePath)
l.paths = append(l.paths[:i], l.paths[i+1:]...)
return
}
}
err = fmt.Errorf("suite %s not found", name)
return
}
func (l *fileLoader) LoadAndParse(suite string, mode string) (testcases []TestCase, testSuiteYaml []byte, err error) {
defer func() {
l.Reset()
}()
for l.HasMore() {
var data []byte
if data, err = l.Load(); err != nil {
continue
}
var testSuite *TestSuite
if testSuite, err = Parse(data); err != nil {
return
}
if testSuite.Name != suite {
continue
}
switch mode {
case "yaml":
testSuiteYaml = data
default:
testcases = testSuite.Items
}
break
}
return
}
func (l *fileLoader) ListTestCase(suite string) (testcases []TestCase, err error) {
testcases, _, err = l.LoadAndParse(suite, "testcases")
return
}
func (l *fileLoader) GetTestSuiteYaml(suite string) (testSuiteYaml []byte, err error) {
_, testSuiteYaml, err = l.LoadAndParse(suite, "yaml")
return
}
func (l *fileLoader) GetTestCase(suite, name string) (testcase TestCase, err error) {
var items []TestCase
if items, err = l.ListTestCase(suite); err == nil {
found := false
for _, item := range items {
if item.Name == name {
testcase = item
found = true
break
}
}
if !found {
err = fmt.Errorf("testcase %s not found", name)
}
}
return
}
func (l *fileLoader) CreateTestCase(suiteName string, testcase TestCase) error {
return l.createOrUpdate(suiteName, testcase, false)
}
func (l *fileLoader) UpdateTestCase(suite string, testcase TestCase) error {
return l.createOrUpdate(suite, testcase, true)
}
func (l *fileLoader) createOrUpdate(suiteName string, testcase TestCase, update bool) (err error) {
var suite *TestSuite
var suiteFilepath string
for i := range l.paths {
suitePath := l.paths[i]
if suite, err = ParseTestSuiteFromFile(suitePath); err != nil {
continue
}
if suite.Name == suiteName {
suiteFilepath = suitePath
break
}
suite = nil
}
if suite != nil {
found := false
for i := range suite.Items {
if suite.Items[i].Name == testcase.Name {
suite.Items[i] = testcase
found = true
break
}
}
if !found {
suite.Items = append(suite.Items, testcase)
} else if !update {
err = fmt.Errorf("test case %s already exists", testcase.Name)
return
}
err = SaveTestSuiteToFile(suite, suiteFilepath)
}
return
}
func (l *fileLoader) DeleteTestCase(suiteName, testcase string) (err error) {
var suite *TestSuite
var suiteFilepath string
for i := range l.paths {
suitePath := l.paths[i]
if suite, err = ParseTestSuiteFromFile(suitePath); err != nil {
continue
}
if suite.Name == suiteName {
suiteFilepath = suitePath
break
}
suite = nil
}
if suite != nil {
found := false
for i := range suite.Items {
if suite.Items[i].Name == testcase {
suite.Items = append(suite.Items[:i], suite.Items[i+1:]...)
found = true
break
}
}
if !found {
err = fmt.Errorf("testcase %s not found", testcase)
return
}
err = SaveTestSuiteToFile(suite, suiteFilepath)
}
return
}
func (l *fileLoader) CreateHistoryTestCase(testcaseResult TestCaseResult, suiteName *TestSuite, historyHeader map[string]string) (err error) { // always be okay
return
}
func (l *fileLoader) ListHistoryTestSuite() (suites []HistoryTestSuite, err error) {
return
}
func (l *fileLoader) GetHistoryTestCaseWithResult(id string) (testcase HistoryTestResult, err error) {
return
}
func (l *fileLoader) GetHistoryTestCase(id string) (testcase HistoryTestCase, err error) {
return
}
func (l *fileLoader) DeleteHistoryTestCase(id string) (err error) {
return
}
func (l *fileLoader) DeleteAllHistoryTestCase(suite, name string) (err error) {
return
}
func (l *fileLoader) GetTestCaseAllHistory(suite, name string) (historyTestCase []HistoryTestCase, err error) {
return
}
func (l *fileLoader) Verify() (readOnly bool, err error) {
// always be okay
return
}
func (l *fileLoader) PProf(string) []byte {
// not support
return nil
}
func (l *fileLoader) Close() {
// not support
}