feat: add grpc server support (#22)
* feat: add grpc server support * publish image --------- Co-authored-by: Rick <linuxsuren@users.noreply.github.com>
This commit is contained in:
parent
136e82de50
commit
e2340a20a8
|
@ -36,3 +36,5 @@ jobs:
|
|||
github_token: ${{ secrets.GH_PUBLISH_SECRETS }}
|
||||
version: v1.14.0
|
||||
args: release --skip-publish --rm-dist --snapshot
|
||||
- name: Image
|
||||
run: make build-image
|
||||
|
|
|
@ -5,6 +5,10 @@ on:
|
|||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-20.04
|
||||
|
@ -24,3 +28,33 @@ jobs:
|
|||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PUBLISH_SECRETS }}
|
||||
|
||||
image:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.0
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GH_PUBLISH_SECRETS }}
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
bin/
|
||||
.idea/
|
||||
coverage.out
|
||||
dist/
|
||||
|
|
|
@ -12,6 +12,10 @@ builds:
|
|||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
ldflags:
|
||||
- -w
|
||||
- -s
|
||||
- -X github.com/linuxsuren/api-testing/cmd.version={{.Version}}
|
||||
archives:
|
||||
- name_template: "{{ .Binary }}-{{ .Os }}-{{ .Arch }}"
|
||||
format_overrides:
|
||||
|
|
16
Dockerfile
16
Dockerfile
|
@ -20,20 +20,6 @@ LABEL "maintainer"="Rick <linuxsuren@gmail.com>"
|
|||
|
||||
LABEL "Name"="API testing"
|
||||
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US.UTF-8
|
||||
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
openssh-client \
|
||||
libc6-compat \
|
||||
libstdc++
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
COPY --from=builder /workspace/atest /usr/bin/atest
|
||||
COPY --from=hd /usr/local/bin/hd /usr/local/bin/hd
|
||||
RUN hd i kubernetes-sigs/kubectl && \
|
||||
hd i k3d
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
ENTRYPOINT [atest, server]
|
||||
|
|
19
Makefile
19
Makefile
|
@ -2,9 +2,26 @@ build:
|
|||
mkdir -p bin
|
||||
rm -rf bin/atest
|
||||
go build -o bin/atest main.go
|
||||
|
||||
goreleaser:
|
||||
goreleaser build --rm-dist --snapshot
|
||||
build-image:
|
||||
docker build -t ghcr.io/linuxsuren/api-testing:dev .
|
||||
copy: build
|
||||
sudo cp bin/atest /usr/local/bin/
|
||||
test:
|
||||
go test ./... -cover -v -coverprofile=coverage.out
|
||||
go tool cover -func=coverage.out
|
||||
grpc:
|
||||
protoc --go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||
pkg/server/server.proto
|
||||
grpc-js:
|
||||
protoc -I=pkg/server server.proto \
|
||||
--js_out=import_style=commonjs:bin \
|
||||
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:bin
|
||||
grpc-java:
|
||||
protoc --plugin=protoc-gen-grpc-java=/usr/local/bin/protoc-gen-grpc-java \
|
||||
--grpc-java_out=bin --proto_path=pkg/server server.proto
|
||||
install-tool:
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
|
||||
|
|
|
@ -2,13 +2,18 @@ package cmd
|
|||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// should be injected during the build process
|
||||
var version string
|
||||
|
||||
// NewRootCmd creates the root command
|
||||
func NewRootCmd() (c *cobra.Command) {
|
||||
c = &cobra.Command{
|
||||
Use: "atest",
|
||||
Short: "API testing tool",
|
||||
}
|
||||
c.Version = version
|
||||
c.AddCommand(createInitCommand(),
|
||||
createRunCommand(), createSampleCmd())
|
||||
createRunCommand(), createSampleCmd(),
|
||||
createServerCmd())
|
||||
return
|
||||
}
|
||||
|
|
|
@ -45,4 +45,8 @@ func TestCreateRunCommand(t *testing.T) {
|
|||
|
||||
init := createInitCommand()
|
||||
assert.Equal(t, "init", init.Use)
|
||||
|
||||
server := createServerCmd()
|
||||
assert.NotNil(t, server)
|
||||
assert.Equal(t, "server", server.Use)
|
||||
}
|
||||
|
|
25
cmd/run.go
25
cmd/run.go
|
@ -32,6 +32,7 @@ type runOption struct {
|
|||
reporter runner.TestReporter
|
||||
reportWriter runner.ReportResultWriter
|
||||
report string
|
||||
reportIgnore bool
|
||||
}
|
||||
|
||||
func newDefaultRunOption() *runOption {
|
||||
|
@ -71,14 +72,22 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
|
|||
flags.Int64VarP(&opt.thread, "thread", "", 1, "Threads of the execution")
|
||||
flags.Int32VarP(&opt.qps, "qps", "", 5, "QPS")
|
||||
flags.Int32VarP(&opt.burst, "burst", "", 5, "burst")
|
||||
flags.StringVarP(&opt.report, "report", "", "", "The type of target report")
|
||||
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, discard, std")
|
||||
return
|
||||
}
|
||||
|
||||
func (o *runOption) preRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
writer := cmd.OutOrStdout()
|
||||
|
||||
switch o.report {
|
||||
case "markdown", "md":
|
||||
o.reportWriter = runner.NewMarkdownResultWriter(cmd.OutOrStdout())
|
||||
o.reportWriter = runner.NewMarkdownResultWriter(writer)
|
||||
case "discard":
|
||||
o.reportWriter = runner.NewDiscardResultWriter()
|
||||
case "", "std":
|
||||
o.reportWriter = runner.NewResultWriter(writer)
|
||||
default:
|
||||
err = fmt.Errorf("not supported report type: '%s'", o.report)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -97,17 +106,18 @@ func (o *runOption) runE(cmd *cobra.Command, args []string) (err error) {
|
|||
for i := range files {
|
||||
item := files[i]
|
||||
if err = o.runSuiteWithDuration(item); err != nil {
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// print the report
|
||||
if err == nil {
|
||||
var results []runner.ReportResult
|
||||
if results, err = o.reporter.ExportAllReportResults(); err == nil {
|
||||
err = o.reportWriter.Output(results)
|
||||
if results, reportErr := o.reporter.ExportAllReportResults(); reportErr == nil {
|
||||
if reportErr = o.reportWriter.Output(results); reportErr != nil {
|
||||
cmd.Println("failed to Output all reports", reportErr)
|
||||
}
|
||||
} else {
|
||||
cmd.Println("failed to export all reports", reportErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -205,6 +215,7 @@ func (o *runOption) runSuite(suite string, dataContext map[string]interface{}, c
|
|||
simpleRunner := runner.NewSimpleTestCaseRunner()
|
||||
simpleRunner.WithTestReporter(o.reporter)
|
||||
if output, err = simpleRunner.RunTestCase(&testCase, dataContext, ctxWithTimeout); err != nil && !o.requestIgnoreError {
|
||||
err = fmt.Errorf("failed to run '%s', %v", testCase.Name, err)
|
||||
return
|
||||
} else {
|
||||
err = nil
|
||||
|
|
|
@ -107,3 +107,72 @@ func TestRootCmd(t *testing.T) {
|
|||
assert.NotNil(t, c)
|
||||
assert.Equal(t, "atest", c.Use)
|
||||
}
|
||||
|
||||
func TestPreRunE(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opt *runOption
|
||||
verify func(*testing.T, *runOption, error)
|
||||
}{{
|
||||
name: "markdown report",
|
||||
opt: &runOption{
|
||||
report: "markdown",
|
||||
},
|
||||
verify: func(t *testing.T, ro *runOption, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, ro.reportWriter)
|
||||
},
|
||||
}, {
|
||||
name: "md report",
|
||||
opt: &runOption{
|
||||
report: "md",
|
||||
},
|
||||
verify: func(t *testing.T, ro *runOption, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, ro.reportWriter)
|
||||
},
|
||||
}, {
|
||||
name: "discard report",
|
||||
opt: &runOption{
|
||||
report: "discard",
|
||||
},
|
||||
verify: func(t *testing.T, ro *runOption, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, ro.reportWriter)
|
||||
},
|
||||
}, {
|
||||
name: "std report",
|
||||
opt: &runOption{
|
||||
report: "std",
|
||||
},
|
||||
verify: func(t *testing.T, ro *runOption, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, ro.reportWriter)
|
||||
},
|
||||
}, {
|
||||
name: "empty report",
|
||||
opt: &runOption{
|
||||
report: "",
|
||||
},
|
||||
verify: func(t *testing.T, ro *runOption, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, ro.reportWriter)
|
||||
},
|
||||
}, {
|
||||
name: "invalid report",
|
||||
opt: &runOption{
|
||||
report: "fake",
|
||||
},
|
||||
verify: func(t *testing.T, ro *runOption, err error) {
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, ro.reportWriter)
|
||||
},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &cobra.Command{}
|
||||
err := tt.opt.preRunE(c, nil)
|
||||
tt.verify(t, tt.opt, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// Package cmd provides all the commands
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/server"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func createServerCmd() (c *cobra.Command) {
|
||||
opt := &serverOption{}
|
||||
c = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Run as a server mode",
|
||||
RunE: opt.runE,
|
||||
}
|
||||
flags := c.Flags()
|
||||
flags.IntVarP(&opt.port, "port", "p", 9090, "The RPC server port")
|
||||
flags.BoolVarP(&opt.printProto, "print-proto", "", false, "Print the proto content and exit")
|
||||
return
|
||||
}
|
||||
|
||||
type serverOption struct {
|
||||
port int
|
||||
printProto bool
|
||||
}
|
||||
|
||||
func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
|
||||
if o.printProto {
|
||||
for _, val := range server.GetProtos() {
|
||||
cmd.Println(val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var lis net.Listener
|
||||
lis, err = net.Listen("tcp", fmt.Sprintf(":%d", o.port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
server.RegisterRunnerServer(s, server.NewRemoteServer())
|
||||
log.Printf("server listening at %v", lis.Addr())
|
||||
s.Serve(lis)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPrintProto(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
verify func(*testing.T, *bytes.Buffer, error)
|
||||
}{{
|
||||
name: "print ptoto only",
|
||||
args: []string{"server", "--print-proto"},
|
||||
verify: func(t *testing.T, buf *bytes.Buffer, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, strings.HasPrefix(buf.String(), `syntax = "proto3";`))
|
||||
},
|
||||
}, {
|
||||
name: "invalid port",
|
||||
args: []string{"server", "-p=-1"},
|
||||
verify: func(t *testing.T, buf *bytes.Buffer, err error) {
|
||||
assert.NotNil(t, err)
|
||||
},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
root := NewRootCmd()
|
||||
root.SetOut(buf)
|
||||
root.SetArgs(tt.args)
|
||||
err := root.Execute()
|
||||
tt.verify(t, buf, err)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
k3d cluster create
|
||||
k3d cluster list
|
||||
|
||||
atest init -k "$2" --wait-namespace "$3" --wait-resource "$4"
|
||||
atest run -p "$1"
|
9
go.mod
9
go.mod
|
@ -6,11 +6,13 @@ require (
|
|||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
|
||||
github.com/antonmedv/expr v1.12.1
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/h2non/gock v1.2.0
|
||||
github.com/linuxsuren/unstructured v0.0.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/sync v0.1.0
|
||||
google.golang.org/grpc v1.54.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
|
@ -18,7 +20,7 @@ require (
|
|||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.1.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
|
@ -31,5 +33,10 @@ require (
|
|||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package server
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// GetProtos returns the proto files.
|
||||
// Key is filename, value is the content.
|
||||
func GetProtos() map[string]string {
|
||||
return map[string]string{
|
||||
"server.proto": protoServer,
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed server.proto
|
||||
var protoServer string
|
|
@ -0,0 +1,20 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/server"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetProtos(t *testing.T) {
|
||||
protos := server.GetProtos()
|
||||
assert.Equal(t, 1, len(protos))
|
||||
|
||||
exists := []string{"server.proto"}
|
||||
for _, key := range exists {
|
||||
content, ok := protos[key]
|
||||
assert.True(t, ok)
|
||||
assert.NotEmpty(t, content)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// Package server provides a GRPC based server
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
context "context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/render"
|
||||
"github.com/linuxsuren/api-testing/pkg/runner"
|
||||
"github.com/linuxsuren/api-testing/pkg/testing"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
UnimplementedRunnerServer
|
||||
}
|
||||
|
||||
// NewRemoteServer creates a remote server instance
|
||||
func NewRemoteServer() RunnerServer {
|
||||
return &server{}
|
||||
}
|
||||
|
||||
// Run start to run the test task
|
||||
func (s *server) Run(ctx context.Context, task *TestTask) (reply *HelloReply, err error) {
|
||||
var suite *testing.TestSuite
|
||||
|
||||
switch task.Kind {
|
||||
case "suite":
|
||||
if suite, err = testing.ParseFromData([]byte(task.Data)); err != nil {
|
||||
return
|
||||
} else if suite == nil || suite.Items == nil {
|
||||
err = fmt.Errorf("no test suite found")
|
||||
return
|
||||
}
|
||||
case "testcase":
|
||||
var testCase *testing.TestCase
|
||||
if testCase, err = testing.ParseTestCaseFromData([]byte(task.Data)); err != nil {
|
||||
return
|
||||
}
|
||||
suite = &testing.TestSuite{
|
||||
Items: []testing.TestCase{*testCase},
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("not support '%s'", task.Kind)
|
||||
return
|
||||
}
|
||||
|
||||
dataContext := map[string]interface{}{}
|
||||
|
||||
var result string
|
||||
if result, err = render.Render("base api", suite.API, dataContext); err == nil {
|
||||
suite.API = result
|
||||
suite.API = strings.TrimSuffix(suite.API, "/")
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for _, testCase := range suite.Items {
|
||||
simpleRunner := runner.NewSimpleTestCaseRunner()
|
||||
simpleRunner.WithOutputWriter(buf)
|
||||
|
||||
// reuse the API prefix
|
||||
if strings.HasPrefix(testCase.Request.API, "/") {
|
||||
testCase.Request.API = fmt.Sprintf("%s%s", suite.API, testCase.Request.API)
|
||||
}
|
||||
|
||||
var output interface{}
|
||||
if output, err = simpleRunner.RunTestCase(&testCase, dataContext, ctx); err == nil {
|
||||
dataContext[testCase.Name] = output
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
reply = &HelloReply{Message: buf.String()}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/h2non/gock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemoteServer(t *testing.T) {
|
||||
server := NewRemoteServer()
|
||||
_, err := server.Run(context.TODO(), &TestTask{
|
||||
Kind: "fake",
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
|
||||
gock.New("http://foo").Get("/").Reply(http.StatusOK).JSON(&server)
|
||||
_, err = server.Run(context.TODO(), &TestTask{
|
||||
Kind: "suite",
|
||||
Data: simpleSuite,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
gock.New("http://bar").Get("/").Reply(http.StatusOK).JSON(&server)
|
||||
_, err = server.Run(context.TODO(), &TestTask{
|
||||
Kind: "testcase",
|
||||
Data: simpleTestCase,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
//go:embed testdata/simple.yaml
|
||||
var simpleSuite string
|
||||
|
||||
//go:embed testdata/simple_testcase.yaml
|
||||
var simpleTestCase string
|
|
@ -0,0 +1,133 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: pkg/server/server.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type TestTask struct {
|
||||
Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *TestTask) Reset() { *m = TestTask{} }
|
||||
func (m *TestTask) String() string { return proto.CompactTextString(m) }
|
||||
func (*TestTask) ProtoMessage() {}
|
||||
func (*TestTask) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_36fb7b77b8f76c98, []int{0}
|
||||
}
|
||||
|
||||
func (m *TestTask) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_TestTask.Unmarshal(m, b)
|
||||
}
|
||||
func (m *TestTask) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_TestTask.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *TestTask) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TestTask.Merge(m, src)
|
||||
}
|
||||
func (m *TestTask) XXX_Size() int {
|
||||
return xxx_messageInfo_TestTask.Size(m)
|
||||
}
|
||||
func (m *TestTask) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_TestTask.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_TestTask proto.InternalMessageInfo
|
||||
|
||||
func (m *TestTask) GetData() string {
|
||||
if m != nil {
|
||||
return m.Data
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TestTask) GetKind() string {
|
||||
if m != nil {
|
||||
return m.Kind
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type HelloReply struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *HelloReply) Reset() { *m = HelloReply{} }
|
||||
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloReply) ProtoMessage() {}
|
||||
func (*HelloReply) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_36fb7b77b8f76c98, []int{1}
|
||||
}
|
||||
|
||||
func (m *HelloReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_HelloReply.Unmarshal(m, b)
|
||||
}
|
||||
func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *HelloReply) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_HelloReply.Merge(m, src)
|
||||
}
|
||||
func (m *HelloReply) XXX_Size() int {
|
||||
return xxx_messageInfo_HelloReply.Size(m)
|
||||
}
|
||||
func (m *HelloReply) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_HelloReply.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_HelloReply proto.InternalMessageInfo
|
||||
|
||||
func (m *HelloReply) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*TestTask)(nil), "server.TestTask")
|
||||
proto.RegisterType((*HelloReply)(nil), "server.HelloReply")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("pkg/server/server.proto", fileDescriptor_36fb7b77b8f76c98)
|
||||
}
|
||||
|
||||
var fileDescriptor_36fb7b77b8f76c98 = []byte{
|
||||
// 194 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x8f, 0xbd, 0x6b, 0xc3, 0x30,
|
||||
0x10, 0xc5, 0xeb, 0xb6, 0xb8, 0xed, 0x4d, 0x45, 0x4b, 0x4d, 0xa7, 0xe2, 0xa1, 0x74, 0xa8, 0x25,
|
||||
0x70, 0x86, 0xec, 0x99, 0x32, 0x0b, 0x4f, 0xd9, 0xe4, 0xf8, 0x50, 0x84, 0x65, 0x49, 0xe8, 0x23,
|
||||
0x24, 0xff, 0x7d, 0xf0, 0x17, 0x9e, 0xee, 0xbd, 0x1f, 0xdc, 0xdd, 0x7b, 0xf0, 0xe5, 0x7a, 0xc9,
|
||||
0x02, 0xfa, 0x2b, 0xfa, 0x65, 0x50, 0xe7, 0x6d, 0xb4, 0x24, 0x9f, 0x5d, 0x59, 0xc3, 0x7b, 0x83,
|
||||
0x21, 0x36, 0x22, 0xf4, 0x84, 0xc0, 0x6b, 0x27, 0xa2, 0x28, 0xb2, 0x9f, 0xec, 0xef, 0x83, 0x4f,
|
||||
0x7a, 0x64, 0xbd, 0x32, 0x5d, 0xf1, 0x3c, 0xb3, 0x51, 0x97, 0xbf, 0x00, 0x47, 0xd4, 0xda, 0x72,
|
||||
0x74, 0xfa, 0x4e, 0x0a, 0x78, 0x1b, 0x30, 0x04, 0x21, 0x71, 0x59, 0x5c, 0x6d, 0xbd, 0x87, 0x9c,
|
||||
0x27, 0x63, 0xd0, 0x93, 0x0a, 0x5e, 0x78, 0x32, 0xe4, 0x93, 0x2e, 0x19, 0xd6, 0x97, 0xdf, 0x64,
|
||||
0x25, 0xdb, 0xc1, 0xf2, 0xe9, 0x40, 0x4f, 0xff, 0x52, 0xc5, 0x4b, 0x6a, 0xe9, 0xd9, 0x0e, 0x4c,
|
||||
0x2b, 0x93, 0x6e, 0x21, 0x79, 0x34, 0x4c, 0x38, 0x55, 0x45, 0x0c, 0x51, 0x19, 0xc9, 0xb6, 0x66,
|
||||
0x6d, 0x3e, 0x75, 0xda, 0x3d, 0x02, 0x00, 0x00, 0xff, 0xff, 0x89, 0x35, 0xcb, 0xb0, 0xee, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option go_package = "github.com/linuxsuren/api-testing/pkg/server";
|
||||
|
||||
package server;
|
||||
|
||||
service Runner {
|
||||
rpc Run (TestTask) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
message TestTask {
|
||||
string data = 1;
|
||||
string kind = 2;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.22.2
|
||||
// source: pkg/server/server.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// RunnerClient is the client API for Runner service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type RunnerClient interface {
|
||||
Run(ctx context.Context, in *TestTask, opts ...grpc.CallOption) (*HelloReply, error)
|
||||
}
|
||||
|
||||
type runnerClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewRunnerClient(cc grpc.ClientConnInterface) RunnerClient {
|
||||
return &runnerClient{cc}
|
||||
}
|
||||
|
||||
func (c *runnerClient) Run(ctx context.Context, in *TestTask, opts ...grpc.CallOption) (*HelloReply, error) {
|
||||
out := new(HelloReply)
|
||||
err := c.cc.Invoke(ctx, "/server.Runner/Run", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RunnerServer is the server API for Runner service.
|
||||
// All implementations must embed UnimplementedRunnerServer
|
||||
// for forward compatibility
|
||||
type RunnerServer interface {
|
||||
Run(context.Context, *TestTask) (*HelloReply, error)
|
||||
mustEmbedUnimplementedRunnerServer()
|
||||
}
|
||||
|
||||
// UnimplementedRunnerServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedRunnerServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedRunnerServer) Run(context.Context, *TestTask) (*HelloReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Run not implemented")
|
||||
}
|
||||
func (UnimplementedRunnerServer) mustEmbedUnimplementedRunnerServer() {}
|
||||
|
||||
// UnsafeRunnerServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to RunnerServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeRunnerServer interface {
|
||||
mustEmbedUnimplementedRunnerServer()
|
||||
}
|
||||
|
||||
func RegisterRunnerServer(s grpc.ServiceRegistrar, srv RunnerServer) {
|
||||
s.RegisterService(&Runner_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Runner_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TestTask)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(RunnerServer).Run(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/server.Runner/Run",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(RunnerServer).Run(ctx, req.(*TestTask))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Runner_ServiceDesc is the grpc.ServiceDesc for Runner service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Runner_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "server.Runner",
|
||||
HandlerType: (*RunnerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Run",
|
||||
Handler: _Runner_Run_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "pkg/server/server.proto",
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/linuxsuren/api-testing/pkg/server"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestXxx(t *testing.T) {
|
||||
unimplemented := &server.UnimplementedRunnerServer{}
|
||||
_, err := unimplemented.Run(context.TODO(), nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
name: simple
|
||||
items:
|
||||
- name: get
|
||||
request:
|
||||
api: http://foo
|
|
@ -0,0 +1,3 @@
|
|||
name: get
|
||||
request:
|
||||
api: http://bar
|
|
@ -1,6 +1,7 @@
|
|||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -13,12 +14,25 @@ import (
|
|||
func Parse(configFile string) (testSuite *TestSuite, err error) {
|
||||
var data []byte
|
||||
if data, err = os.ReadFile(configFile); err == nil {
|
||||
testSuite = &TestSuite{}
|
||||
err = yaml.Unmarshal(data, testSuite)
|
||||
testSuite, err = ParseFromData(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseFromData parses data and returns the test suite
|
||||
func ParseFromData(data []byte) (testSuite *TestSuite, err error) {
|
||||
testSuite = &TestSuite{}
|
||||
err = yaml.Unmarshal(data, testSuite)
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTestCaseFromData parses the data to a test case
|
||||
func ParseTestCaseFromData(data []byte) (testCase *TestCase, err error) {
|
||||
testCase = &TestCase{}
|
||||
err = yaml.Unmarshal(data, testCase)
|
||||
return
|
||||
}
|
||||
|
||||
// Render injects the template based context
|
||||
func (r *Request) Render(ctx interface{}) (err error) {
|
||||
// template the API
|
||||
|
@ -26,6 +40,7 @@ func (r *Request) Render(ctx interface{}) (err error) {
|
|||
if result, err = render.Render("api", r.API, ctx); err == nil {
|
||||
r.API = result
|
||||
} else {
|
||||
err = fmt.Errorf("failed render '%s', %v", r.API, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -191,3 +193,20 @@ func TestEmptyThenDefault(t *testing.T) {
|
|||
assert.Equal(t, 1, zeroThenDefault(0, 1))
|
||||
assert.Equal(t, 1, zeroThenDefault(1, 2))
|
||||
}
|
||||
|
||||
func TestTestCase(t *testing.T) {
|
||||
testCase, err := ParseTestCaseFromData([]byte(testCaseContent))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, &TestCase{
|
||||
Name: "projects",
|
||||
Request: Request{
|
||||
API: "https://foo",
|
||||
},
|
||||
Expect: Response{
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
}, testCase)
|
||||
}
|
||||
|
||||
//go:embed testdata/testcase.yaml
|
||||
var testCaseContent string
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
name: projects
|
||||
request:
|
||||
api: https://foo
|
||||
expect:
|
||||
statusCode: 200
|
Loading…
Reference in New Issue