api-testing/pkg/server/remote_server.go

980 lines
26 KiB
Go

/**
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
import (
"bytes"
context "context"
"errors"
"fmt"
"io"
"os"
reflect "reflect"
"regexp"
"strings"
_ "embed"
"log"
"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/linuxsuren/api-testing/pkg/generator"
"github.com/linuxsuren/api-testing/pkg/render"
"github.com/linuxsuren/api-testing/pkg/runner"
"github.com/linuxsuren/api-testing/pkg/testing"
"github.com/linuxsuren/api-testing/pkg/util"
"github.com/linuxsuren/api-testing/pkg/version"
"github.com/linuxsuren/api-testing/sample"
"google.golang.org/grpc/metadata"
"gopkg.in/yaml.v3"
)
type server struct {
UnimplementedRunnerServer
loader testing.Writer
storeWriterFactory testing.StoreWriterFactory
configDir string
storeExtMgr ExtManager
secretServer SecretServiceServer
}
type SecretServiceServer interface {
GetSecrets(context.Context, *Empty) (*Secrets, error)
CreateSecret(context.Context, *Secret) (*CommonResult, error)
DeleteSecret(context.Context, *Secret) (*CommonResult, error)
UpdateSecret(context.Context, *Secret) (*CommonResult, error)
}
type SecertServiceGetable interface {
GetSecret(context.Context, *Secret) (*Secret, error)
}
type fakeSecretServer struct{}
var errNoSecretService = errors.New("no secret service found")
func (f *fakeSecretServer) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) {
err = errNoSecretService
return
}
func (f *fakeSecretServer) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
err = errNoSecretService
return
}
func (f *fakeSecretServer) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
err = errNoSecretService
return
}
func (f *fakeSecretServer) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
err = errNoSecretService
return
}
// NewRemoteServer creates a remote server instance
func NewRemoteServer(loader testing.Writer, storeWriterFactory testing.StoreWriterFactory, secretServer SecretServiceServer, storeExtMgr ExtManager, configDir string) RunnerServer {
if secretServer == nil {
secretServer = &fakeSecretServer{}
}
return &server{
loader: loader,
storeWriterFactory: storeWriterFactory,
configDir: configDir,
secretServer: secretServer,
storeExtMgr: storeExtMgr,
}
}
func withDefaultValue(old, defVal any) any {
if old == "" || old == nil {
old = defVal
}
return old
}
func parseSuiteWithItems(data []byte) (suite *testing.TestSuite, err error) {
suite, err = testing.ParseFromData(data)
if err == nil && (suite == nil || suite.Items == nil) {
err = errNoTestSuiteFound
}
return
}
func (s *server) getSuiteFromTestTask(task *TestTask) (suite *testing.TestSuite, err error) {
switch task.Kind {
case "suite":
suite, err = parseSuiteWithItems([]byte(task.Data))
case "testcase":
var testCase *testing.TestCase
if testCase, err = testing.ParseTestCaseFromData([]byte(task.Data)); err != nil {
return
}
suite = &testing.TestSuite{
Items: []testing.TestCase{*testCase},
}
case "testcaseInSuite":
suite, err = parseSuiteWithItems([]byte(task.Data))
if err != nil {
return
}
var targetTestcase *testing.TestCase
for _, item := range suite.Items {
if item.Name == task.CaseName {
targetTestcase = &item
break
}
}
if targetTestcase != nil {
parentCases := findParentTestCases(targetTestcase, suite)
log.Printf("find %d parent cases\n", len(parentCases))
suite.Items = append(parentCases, *targetTestcase)
} else {
err = fmt.Errorf("cannot found testcase %s", task.CaseName)
}
default:
err = fmt.Errorf("not support '%s'", task.Kind)
}
return
}
func resetEnv(oldEnv map[string]string) {
for key, val := range oldEnv {
os.Setenv(key, val)
}
}
func (s *server) getLoader(ctx context.Context) (loader testing.Writer) {
var ok bool
loader = s.loader
var mdd metadata.MD
if mdd, ok = metadata.FromIncomingContext(ctx); ok {
storeNameMeta := mdd.Get(HeaderKeyStoreName)
if len(storeNameMeta) > 0 {
storeName := storeNameMeta[0]
if storeName == "local" || storeName == "" {
return
}
loader, _ = s.getLoaderByStoreName(storeName)
}
}
return
}
// Run start to run the test task
func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, err error) {
task.Level = withDefaultValue(task.Level, "info").(string)
task.Env = withDefaultValue(task.Env, map[string]string{}).(map[string]string)
var suite *testing.TestSuite
// TODO may not safe in multiple threads
oldEnv := map[string]string{}
for key, val := range task.Env {
oldEnv[key] = os.Getenv(key)
os.Setenv(key, val)
}
defer func() {
resetEnv(oldEnv)
}()
if suite, err = s.getSuiteFromTestTask(task); err != nil {
return
}
log.Printf("prepare to run: %s, with level: %s\n", suite.Name, task.Level)
log.Printf("task kind: %s, %d to run\n", task.Kind, len(suite.Items))
dataContext := map[string]interface{}{}
if err = suite.Render(dataContext); err != nil {
reply.Error = err.Error()
err = nil
return
}
// inject the parameters from input
if len(task.Parameters) > 0 {
dataContext[testing.ContextKeyGlobalParam] = pairToMap(task.Parameters)
}
buf := new(bytes.Buffer)
reply = &TestResult{}
for _, testCase := range suite.Items {
suiteRunner := runner.GetTestSuiteRunner(suite)
suiteRunner.WithOutputWriter(buf)
suiteRunner.WithWriteLevel(task.Level)
suiteRunner.WithSecure(suite.Spec.Secure)
// reuse the API prefix
testCase.Request.RenderAPI(suite.API)
output, testErr := suiteRunner.RunTestCase(&testCase, dataContext, ctx)
if getter, ok := suiteRunner.(runner.ResponseRecord); ok {
resp := getter.GetResponseRecord()
reply.TestCaseResult = append(reply.TestCaseResult, &TestCaseResult{
StatusCode: int32(resp.StatusCode),
Body: resp.Body,
Header: mapToPair(resp.Header),
Id: testCase.ID,
Output: buf.String(),
})
}
if testErr == nil {
dataContext[testCase.Name] = output
} else {
reply.Error = testErr.Error()
break
}
}
if reply.Error != "" {
fmt.Fprintln(buf, reply.Error)
}
reply.Message = buf.String()
return
}
// GetVersion returns the version
func (s *server) GetVersion(ctx context.Context, in *Empty) (reply *HelloReply, err error) {
reply = &HelloReply{Message: version.GetVersion()}
return
}
func (s *server) GetSuites(ctx context.Context, in *Empty) (reply *Suites, err error) {
loader := s.getLoader(ctx)
reply = &Suites{
Data: make(map[string]*Items),
}
var suites []testing.TestSuite
if suites, err = loader.ListTestSuite(); err == nil && suites != nil {
for _, suite := range suites {
items := &Items{}
for _, item := range suite.Items {
items.Data = append(items.Data, item.Name)
}
reply.Data[suite.Name] = items
}
}
return
}
func (s *server) CreateTestSuite(ctx context.Context, in *TestSuiteIdentity) (reply *HelloReply, err error) {
reply = &HelloReply{}
loader := s.getLoader(ctx)
if loader == nil {
reply.Error = "no loader found"
} else {
err = loader.CreateSuite(in.Name, in.Api)
}
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
if suite, _, err = loader.GetSuite(in.Name); err == nil && suite != nil {
result = &TestSuite{
Name: suite.Name,
Api: suite.API,
Param: mapToPair(suite.Param),
Spec: &APISpec{
Kind: suite.Spec.Kind,
Url: suite.Spec.URL,
},
}
if suite.Spec.Secure != nil {
result.Spec.Secure = &Secure{
Insecure: suite.Spec.Secure.Insecure,
Cert: suite.Spec.Secure.CertFile,
Ca: suite.Spec.Secure.CAFile,
ServerName: suite.Spec.Secure.ServerName,
Key: suite.Spec.Secure.KeyFile,
}
}
if suite.Spec.GRPC != nil {
result.Spec.Grpc = &GRPC{
Import: suite.Spec.GRPC.ImportPath,
ServerReflection: suite.Spec.GRPC.ServerReflection,
Protofile: suite.Spec.GRPC.ProtoFile,
Protoset: suite.Spec.GRPC.ProtoSet,
}
}
}
return
}
func convertToTestingTestSuite(in *TestSuite) (suite *testing.TestSuite) {
suite = &testing.TestSuite{
Name: in.Name,
API: in.Api,
Param: pairToMap(in.Param),
}
if in.Spec != nil {
suite.Spec = testing.APISpec{
Kind: in.Spec.Kind,
URL: in.Spec.Url,
}
}
return
}
func (s *server) UpdateTestSuite(ctx context.Context, in *TestSuite) (reply *HelloReply, err error) {
reply = &HelloReply{}
loader := s.getLoader(ctx)
err = loader.UpdateSuite(*convertToTestingTestSuite(in))
return
}
func (s *server) DeleteTestSuite(ctx context.Context, in *TestSuiteIdentity) (reply *HelloReply, err error) {
reply = &HelloReply{}
loader := s.getLoader(ctx)
err = loader.DeleteSuite(in.Name)
return
}
func (s *server) ListTestCase(ctx context.Context, in *TestSuiteIdentity) (result *Suite, err error) {
var items []testing.TestCase
loader := s.getLoader(ctx)
if items, err = loader.ListTestCase(in.Name); err == nil {
result = &Suite{}
for _, item := range items {
result.Items = append(result.Items, convertToGRPCTestCase(item))
}
}
return
}
func (s *server) GetTestCase(ctx context.Context, in *TestCaseIdentity) (reply *TestCase, err error) {
var result testing.TestCase
loader := s.getLoader(ctx)
if result, err = loader.GetTestCase(in.Suite, in.Testcase); err == nil {
reply = convertToGRPCTestCase(result)
}
return
}
func convertToGRPCTestCase(testCase testing.TestCase) (result *TestCase) {
req := &Request{
Api: testCase.Request.API,
Method: testCase.Request.Method,
Query: mapToPair(testCase.Request.Query),
Header: mapToPair(testCase.Request.Header),
Form: mapToPair(testCase.Request.Form),
Body: testCase.Request.Body,
}
resp := &Response{
StatusCode: int32(testCase.Expect.StatusCode),
Body: testCase.Expect.Body,
Header: mapToPair(testCase.Expect.Header),
BodyFieldsExpect: mapInterToPair(testCase.Expect.BodyFieldsExpect),
Verify: testCase.Expect.Verify,
Schema: testCase.Expect.Schema,
}
result = &TestCase{
Name: testCase.Name,
Request: req,
Response: resp,
}
return
}
func (s *server) RunTestCase(ctx context.Context, in *TestCaseIdentity) (result *TestCaseResult, err error) {
var targetTestSuite testing.TestSuite
loader := s.getLoader(ctx)
targetTestSuite, err = loader.GetTestSuite(in.Suite, true)
if err != nil {
err = nil
result = &TestCaseResult{
Error: fmt.Sprintf("not found suite: %s", in.Suite),
}
return
}
var data []byte
if data, err = yaml.Marshal(targetTestSuite); err == nil {
task := &TestTask{
Kind: "testcaseInSuite",
Data: string(data),
CaseName: in.Testcase,
Level: "debug",
Parameters: in.Parameters,
}
var reply *TestResult
if reply, err = s.Run(ctx, task); err == nil && len(reply.TestCaseResult) > 0 {
lastIndex := len(reply.TestCaseResult) - 1
lastItem := reply.TestCaseResult[lastIndex]
result = &TestCaseResult{
Output: reply.Message,
Error: reply.Error,
Body: lastItem.Body,
Header: lastItem.Header,
StatusCode: lastItem.StatusCode,
}
} else if err != nil {
result = &TestCaseResult{
Error: err.Error(),
}
} else {
result = &TestCaseResult{
Output: reply.Message,
Error: reply.Error,
}
}
}
return
}
func mapInterToPair(data map[string]interface{}) (pairs []*Pair) {
pairs = make([]*Pair, 0)
for k, v := range data {
pairs = append(pairs, &Pair{
Key: k,
Value: fmt.Sprintf("%v", v),
})
}
return
}
func mapToPair(data map[string]string) (pairs []*Pair) {
pairs = make([]*Pair, 0)
for k, v := range data {
pairs = append(pairs, &Pair{
Key: k,
Value: v,
})
}
return
}
func pairToInterMap(pairs []*Pair) (data map[string]interface{}) {
data = make(map[string]interface{})
for _, pair := range pairs {
if pair.Key == "" {
continue
}
data[pair.Key] = pair.Value
}
return
}
func pairToMap(pairs []*Pair) (data map[string]string) {
data = make(map[string]string)
for _, pair := range pairs {
if pair.Key == "" {
continue
}
data[pair.Key] = pair.Value
}
return
}
func convertToTestingTestCase(in *TestCase) (result testing.TestCase) {
result = testing.TestCase{
Name: in.Name,
}
req := in.Request
resp := in.Response
if req != nil {
result.Request.API = req.Api
result.Request.Method = req.Method
result.Request.Body = req.Body
result.Request.Header = pairToMap(req.Header)
result.Request.Form = pairToMap(req.Form)
result.Request.Query = pairToMap(req.Query)
}
if resp != nil {
result.Expect.Body = strings.TrimSpace(resp.Body)
result.Expect.Schema = strings.TrimSpace(resp.Schema)
result.Expect.StatusCode = int(resp.StatusCode)
result.Expect.Verify = util.RemoeEmptyFromSlice(resp.Verify)
result.Expect.ConditionalVerify = convertConditionalVerify(resp.ConditionalVerify)
result.Expect.BodyFieldsExpect = pairToInterMap(resp.BodyFieldsExpect)
result.Expect.Header = pairToMap(resp.Header)
}
return
}
func convertConditionalVerify(verify []*ConditionalVerify) (result []testing.ConditionalVerify) {
if verify != nil {
result = make([]testing.ConditionalVerify, 0)
for _, item := range verify {
result = append(result, testing.ConditionalVerify{
Condition: item.Condition,
Verify: item.Verify,
})
}
}
return
}
func (s *server) CreateTestCase(ctx context.Context, in *TestCaseWithSuite) (reply *HelloReply, err error) {
reply = &HelloReply{}
loader := s.getLoader(ctx)
err = loader.CreateTestCase(in.SuiteName, convertToTestingTestCase(in.Data))
return
}
func (s *server) UpdateTestCase(ctx context.Context, in *TestCaseWithSuite) (reply *HelloReply, err error) {
reply = &HelloReply{}
if in.Data == nil {
err = errors.New("data is required")
return
}
loader := s.getLoader(ctx)
err = loader.UpdateTestCase(in.SuiteName, convertToTestingTestCase(in.Data))
return
}
func (s *server) DeleteTestCase(ctx context.Context, in *TestCaseIdentity) (reply *HelloReply, err error) {
loader := s.getLoader(ctx)
err = loader.DeleteTestCase(in.Suite, in.Testcase)
return
}
// code generator
func (s *server) ListCodeGenerator(ctx context.Context, in *Empty) (reply *SimpleList, err error) {
reply = &SimpleList{}
generators := generator.GetCodeGenerators()
for name := range generators {
reply.Data = append(reply.Data, &Pair{
Key: name,
})
}
return
}
func (s *server) GenerateCode(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) {
reply = &CommonResult{}
instance := generator.GetCodeGenerator(in.Generator)
if instance == nil {
reply.Success = false
reply.Message = fmt.Sprintf("generator '%s' not found", in.Generator)
} else {
var result testing.TestCase
var suite testing.TestSuite
loader := s.getLoader(ctx)
if suite, err = loader.GetTestSuite(in.TestSuite, true); err != nil {
return
}
dataContext := map[string]interface{}{}
if err = suite.Render(dataContext); err != nil {
return
}
if result, err = loader.GetTestCase(in.TestSuite, in.TestCase); err == nil {
result.Request.RenderAPI(suite.API)
output, genErr := instance.Generate(&result)
reply.Success = genErr == nil
reply.Message = util.OrErrorMessage(genErr, output)
}
}
return
}
// converter
func (s *server) ListConverter(ctx context.Context, in *Empty) (reply *SimpleList, err error) {
reply = &SimpleList{}
converters := generator.GetTestSuiteConverters()
for name := range converters {
reply.Data = append(reply.Data, &Pair{
Key: name,
})
}
return
}
func (s *server) ConvertTestSuite(ctx context.Context, in *CodeGenerateRequest) (reply *CommonResult, err error) {
reply = &CommonResult{}
instance := generator.GetTestSuiteConverter(in.Generator)
if instance == nil {
reply.Success = false
reply.Message = fmt.Sprintf("converter '%s' not found", in.Generator)
} else {
var result testing.TestSuite
loader := s.getLoader(ctx)
if result, err = loader.GetTestSuite(in.TestSuite, true); err == nil {
output, genErr := instance.Convert(&result)
reply.Success = genErr == nil
reply.Message = util.OrErrorMessage(genErr, output)
}
}
return
}
// Sample returns a sample of the test task
func (s *server) Sample(ctx context.Context, in *Empty) (reply *HelloReply, err error) {
reply = &HelloReply{Message: sample.TestSuiteGitLab}
return
}
// PopularHeaders returns a list of popular headers
func (s *server) PopularHeaders(ctx context.Context, in *Empty) (pairs *Pairs, err error) {
pairs = &Pairs{
Data: []*Pair{},
}
err = yaml.Unmarshal([]byte(popularHeaders), &pairs.Data)
return
}
// GetSuggestedAPIs returns a list of suggested APIs
func (s *server) GetSuggestedAPIs(ctx context.Context, in *TestSuiteIdentity) (reply *TestCases, err error) {
reply = &TestCases{}
var suite *testing.TestSuite
loader := s.getLoader(ctx)
if suite, _, err = loader.GetSuite(in.Name); err != nil || suite == nil {
return
}
if suite.Spec.URL == "" {
return
}
log.Println("Finding APIs from", in.Name, "with loader", reflect.TypeOf(loader))
var swaggerAPI *apispec.Swagger
if swaggerAPI, err = apispec.ParseURLToSwagger(suite.Spec.URL); err == nil && swaggerAPI != nil {
for api, item := range swaggerAPI.Paths {
for method, oper := range item {
reply.Data = append(reply.Data, &TestCase{
Name: oper.OperationId,
Request: &Request{
Api: api,
Method: strings.ToUpper(method),
},
})
}
}
}
return
}
// FunctionsQuery returns a list of functions
func (s *server) FunctionsQuery(ctx context.Context, in *SimpleQuery) (reply *Pairs, err error) {
reply = &Pairs{}
in.Name = strings.ToLower(in.Name)
for name, fn := range render.FuncMap() {
lowerCaseName := strings.ToLower(name)
if in.Name == "" || strings.Contains(lowerCaseName, in.Name) {
reply.Data = append(reply.Data, &Pair{
Key: name,
Value: fmt.Sprintf("%v", reflect.TypeOf(fn)),
})
}
}
return
}
// FunctionsQueryStream works like FunctionsQuery but is implemented in bidirectional streaming
func (s *server) FunctionsQueryStream(srv Runner_FunctionsQueryStreamServer) error {
ctx := srv.Context()
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
in, err := srv.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
reply := &Pairs{}
in.Name = strings.ToLower(in.Name)
for name, fn := range render.FuncMap() {
lowerCaseName := strings.ToLower(name)
if in.Name == "" || strings.Contains(lowerCaseName, in.Name) {
reply.Data = append(reply.Data, &Pair{
Key: name,
Value: fmt.Sprintf("%v", reflect.TypeOf(fn)),
})
}
}
if err := srv.Send(reply); err != nil {
return err
}
}
}
}
func (s *server) GetStoreKinds(context.Context, *Empty) (kinds *StoreKinds, err error) {
storeFactory := testing.NewStoreFactory(s.configDir)
var stores []testing.StoreKind
if stores, err = storeFactory.GetStoreKinds(); err == nil {
kinds = &StoreKinds{}
for _, store := range stores {
kinds.Data = append(kinds.Data, &StoreKind{
Name: store.Name,
Enabled: store.Enabled,
Url: store.URL,
})
}
}
return
}
func (s *server) GetStores(ctx context.Context, in *Empty) (reply *Stores, err error) {
storeFactory := testing.NewStoreFactory(s.configDir)
var stores []testing.Store
if stores, err = storeFactory.GetStores(); err == nil {
reply = &Stores{
Data: make([]*Store, 0),
}
for _, item := range stores {
grpcStore := ToGRPCStore(item)
storeStatus, sErr := s.VerifyStore(ctx, &SimpleQuery{Name: item.Name})
grpcStore.Ready = sErr == nil && storeStatus.Success
grpcStore.Password = "******" // return a placeholder instead of the actual value for the security reason
reply.Data = append(reply.Data, grpcStore)
}
reply.Data = append(reply.Data, &Store{
Name: "local",
Kind: &StoreKind{},
Ready: true,
})
}
return
}
func (s *server) CreateStore(ctx context.Context, in *Store) (reply *Store, err error) {
reply = &Store{}
storeFactory := testing.NewStoreFactory(s.configDir)
store := ToNormalStore(in)
if err = storeFactory.CreateStore(store); err == nil && s.storeExtMgr != nil {
err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
}
return
}
func (s *server) UpdateStore(ctx context.Context, in *Store) (reply *Store, err error) {
reply = &Store{}
storeFactory := testing.NewStoreFactory(s.configDir)
store := ToNormalStore(in)
if err = storeFactory.UpdateStore(store); err == nil && s.storeExtMgr != nil {
err = s.storeExtMgr.Start(store.Kind.Name, store.Kind.URL)
}
return
}
func (s *server) DeleteStore(ctx context.Context, in *Store) (reply *Store, err error) {
reply = &Store{}
storeFactory := testing.NewStoreFactory(s.configDir)
err = storeFactory.DeleteStore(in.Name)
return
}
func (s *server) VerifyStore(ctx context.Context, in *SimpleQuery) (reply *CommonResult, err error) {
// TODO need to implement
reply = &CommonResult{}
var loader testing.Writer
if loader, err = s.getLoaderByStoreName(in.Name); err == nil && loader != nil {
verifyErr := loader.Verify()
reply.Success = verifyErr == nil
reply.Message = util.OKOrErrorMessage(verifyErr)
}
return
}
// secret related interfaces
func (s *server) GetSecrets(ctx context.Context, in *Empty) (reply *Secrets, err error) {
return s.secretServer.GetSecrets(ctx, in)
}
func (s *server) CreateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
return s.secretServer.CreateSecret(ctx, in)
}
func (s *server) DeleteSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
return s.secretServer.DeleteSecret(ctx, in)
}
func (s *server) UpdateSecret(ctx context.Context, in *Secret) (reply *CommonResult, err error) {
return s.secretServer.UpdateSecret(ctx, in)
}
func (s *server) getLoaderByStoreName(storeName string) (loader testing.Writer, err error) {
var store *testing.Store
store, err = testing.NewStoreFactory(s.configDir).GetStore(storeName)
if err == nil && store != nil {
loader, err = s.storeWriterFactory.NewInstance(*store)
if err != nil {
err = fmt.Errorf("failed to new grpc loader from store %s, err: %v", store.Name, err)
}
} else {
err = fmt.Errorf("failed to get store %s, err: %v", storeName, err)
}
return
}
//go:embed data/headers.yaml
var popularHeaders string
func findParentTestCases(testcase *testing.TestCase, suite *testing.TestSuite) (testcases []testing.TestCase) {
reg, matchErr := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`)
targetReg, targetErr := regexp.Compile(`\.\w*`)
expectNames := new(UniqueSlice[string])
if matchErr == nil && targetErr == nil {
var expectName string
for _, val := range testcase.Request.Header {
if matched := reg.MatchString(val); matched {
expectName = targetReg.FindString(val)
expectName = strings.TrimPrefix(expectName, ".")
expectNames.Push(expectName)
}
}
findExpectNames(testcase.Request.API, expectNames)
findExpectNames(testcase.Request.Body, expectNames)
log.Println("expect test case names", expectNames.GetAll())
for _, item := range suite.Items {
if expectNames.Exist(item.Name) {
testcases = append(testcases, item)
}
}
}
return
}
func findExpectNames(target string, expectNames *UniqueSlice[string]) {
reg, _ := regexp.Compile(`(.*?\{\{.*\.\w*.*?\}\})`)
targetReg, _ := regexp.Compile(`\.\w*`)
for _, sub := range reg.FindStringSubmatch(target) {
// remove {{ and }}
if left, leftErr := regexp.Compile(`.*\{\{`); leftErr == nil {
body := left.ReplaceAllString(sub, "")
expectName := targetReg.FindString(body)
expectName = strings.TrimPrefix(expectName, ".")
expectNames.Push(expectName)
}
}
}
// UniqueSlice represents an unique slice
type UniqueSlice[T comparable] struct {
data []T
}
// Push pushes an item if it's not exist
func (s *UniqueSlice[T]) Push(item T) *UniqueSlice[T] {
if s.data == nil {
s.data = []T{item}
} else {
for _, it := range s.data {
if it == item {
return s
}
}
s.data = append(s.data, item)
}
return s
}
// Exist checks if the item exist, return true it exists
func (s *UniqueSlice[T]) Exist(item T) bool {
if s.data != nil {
for _, it := range s.data {
if it == item {
return true
}
}
}
return false
}
// GetAll returns all the items
func (s *UniqueSlice[T]) GetAll() []T {
return s.data
}
var errNoTestSuiteFound = errors.New("no test suite found")