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:
Rick 2023-04-04 13:18:51 +08:00 committed by GitHub
parent 136e82de50
commit e2340a20a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1808 additions and 35 deletions

View File

@ -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

View File

@ -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
.gitignore vendored
View File

@ -1,3 +1,4 @@
bin/
.idea/
coverage.out
dist/

View File

@ -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:

View File

@ -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]

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
})
}
}

51
cmd/server.go Normal file
View File

@ -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
}

40
cmd/server_test.go Normal file
View File

@ -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)
})
}
}

View File

@ -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
View File

@ -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
)

1080
go.sum

File diff suppressed because it is too large Load Diff

14
pkg/server/proto.go Normal file
View File

@ -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

20
pkg/server/proto_test.go Normal file
View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

133
pkg/server/server.pb.go Normal file
View File

@ -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,
}

18
pkg/server/server.proto Normal file
View File

@ -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;
}

View File

@ -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",
}

View File

@ -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)
}

5
pkg/server/testdata/simple.yaml vendored Normal file
View File

@ -0,0 +1,5 @@
name: simple
items:
- name: get
request:
api: http://foo

View File

@ -0,0 +1,3 @@
name: get
request:
api: http://bar

View File

@ -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
}

View File

@ -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

5
pkg/testing/testdata/testcase.yaml vendored Normal file
View File

@ -0,0 +1,5 @@
name: projects
request:
api: https://foo
expect:
statusCode: 200