Skip to content

Commit 02d7e23

Browse files
authored
feat: add ui for the api-testing (#102)
* feat: add ui project for the atest * add grpc gateway support * complete the very basic ui of atest * add local storage flag into the server sub-command * add unit tests for the remote server * put the console into the docker image * remove unused files from console * add more unit tests * support to execute test case on ui * fix the code smells located by sonarqube --------- Co-authored-by: rick <linuxsuren@users.noreply.github.com>
1 parent 7ff05a8 commit 02d7e23

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+17208
-128
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ dist/
66
.vscode/launch.json
77
sample.yaml
88
.DS_Store
9+
console/atest-ui/node_modules

Dockerfile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ WORKDIR /workspace
44
COPY cmd/ cmd/
55
COPY pkg/ pkg/
66
COPY extensions/ extensions/
7+
COPY console/atest-ui atest-ui/
78
COPY sample/ sample/
89
COPY go.mod go.mod
910
COPY go.sum go.sum
@@ -17,6 +18,13 @@ RUN go mod download
1718
RUN CGO_ENABLE=0 go build -ldflags "-w -s" -o atest .
1819
RUN CGO_ENABLE=0 go build -ldflags "-w -s" -o atest-collector extensions/collector/main.go
1920

21+
FROM node:20-alpine3.17 AS ui
22+
23+
WORKDIR /workspace
24+
COPY --from=builder /workspace/atest-ui .
25+
RUN npm install --ignore-scripts
26+
RUN npm run build-only
27+
2028
FROM ubuntu:23.04
2129

2230
LABEL "com.github.actions.name"="API testing"
@@ -35,4 +43,8 @@ COPY --from=builder /workspace/atest-collector /usr/local/bin/atest-collector
3543
COPY --from=builder /workspace/LICENSE /LICENSE
3644
COPY --from=builder /workspace/README.md /README.md
3745

38-
CMD ["atest", "server"]
46+
RUN mkdir -p /var/www
47+
COPY --from=builder /workspace/sample /var/www/sample
48+
COPY --from=ui /workspace/dist /var/www/html
49+
50+
CMD ["atest", "server", "--console-path=/var/www/html", "--local-storage=/var/www/sample/testsuite-*.yaml"]

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ goreleaser:
77
build-image:
88
docker build -t ghcr.io/linuxsuren/api-testing:dev .
99
run-image:
10-
docker run ghcr.io/linuxsuren/api-testing:dev
10+
docker run -p 7070:7070 -p 8080:8080 ghcr.io/linuxsuren/api-testing:dev
1111
copy: build
1212
sudo cp bin/atest /usr/local/bin/
1313
copy-restart: build
@@ -24,13 +24,25 @@ grpc:
2424
protoc --go_out=. --go_opt=paths=source_relative \
2525
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
2626
pkg/server/server.proto
27+
grpc-gw:
28+
protoc -I . --grpc-gateway_out . \
29+
--grpc-gateway_opt logtostderr=true \
30+
--grpc-gateway_opt paths=source_relative \
31+
--grpc-gateway_opt generate_unbound_methods=true \
32+
pkg/server/server.proto
2733
grpc-js:
2834
protoc -I=pkg/server server.proto \
2935
--js_out=import_style=commonjs:bin \
3036
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:bin
37+
# https://github.com/grpc/grpc-web
38+
grpc-ts:
39+
protoc -I=pkg/server server.proto \
40+
--js_out=import_style=commonjs,binary:console/atest-ui/src \
41+
--grpc-web_out=import_style=typescript,mode=grpcwebtext:console/atest-ui/src
3142
grpc-java:
3243
protoc --plugin=protoc-gen-grpc-java=/usr/local/bin/protoc-gen-grpc-java \
3344
--grpc-java_out=bin --proto_path=pkg/server server.proto
3445
install-tool:
3546
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
3647
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
48+
hd i protoc-gen-grpc-web

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This is a API testing tool.
1515
* Validate the response body with [JSON schema](https://json-schema.org/)
1616
* Pre and post handle with the API request
1717
* Output reference between TestCase
18-
* Run in server mode, and provide the [gRPC endpoint](pkg/server/server.proto)
18+
* Run in server mode, and provide the [gRPC](pkg/server/server.proto) and HTTP endpoint
1919
* [VS Code extension](https://github.com/LinuxSuRen/vscode-api-testing) support
2020
* [HTTP API record](extensions/collector)
2121

@@ -66,9 +66,9 @@ consume: 1m2.153686448s
6666
6767
## Use in Docker
6868
69-
Use `atest` as server mode in Docker:
69+
Use `atest` as server mode in Docker, then you could visit the UI from `8080` and the gRPC endpoint from `7070`:
7070
```
71-
docker run -p 7070:7070 ghcr.io/linuxsuren/api-testing
71+
docker run -p 7070:7070 -p 8080:8080 ghcr.io/linuxsuren/api-testing
7272
```
7373
7474
Use `atest-collector` in Docker:

cmd/function_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/linuxsuren/api-testing/cmd"
8+
"github.com/linuxsuren/api-testing/pkg/server"
89
fakeruntime "github.com/linuxsuren/go-fake-runtime"
910
"github.com/stretchr/testify/assert"
1011
)
@@ -35,7 +36,8 @@ func TestCreateFunctionCommand(t *testing.T) {
3536
}}
3637
for _, tt := range tests {
3738
t.Run(tt.name, func(t *testing.T) {
38-
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, cmd.NewFakeGRPCServer())
39+
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"},
40+
cmd.NewFakeGRPCServer(), server.NewFakeHTTPServer())
3941

4042
buf := new(bytes.Buffer)
4143
c.SetOut(buf)

cmd/jsonschema_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import (
66
"testing"
77

88
"github.com/linuxsuren/api-testing/cmd"
9+
"github.com/linuxsuren/api-testing/pkg/server"
910
fakeruntime "github.com/linuxsuren/go-fake-runtime"
1011
"github.com/stretchr/testify/assert"
1112
)
1213

1314
func TestJSONSchemaCmd(t *testing.T) {
14-
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, cmd.NewFakeGRPCServer())
15+
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"},
16+
cmd.NewFakeGRPCServer(), server.NewFakeHTTPServer())
1517

1618
buf := new(bytes.Buffer)
1719
c.SetOut(buf)

cmd/root.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package cmd
33
import (
44
"os"
55

6+
"github.com/linuxsuren/api-testing/pkg/server"
67
"github.com/linuxsuren/api-testing/pkg/version"
78
fakeruntime "github.com/linuxsuren/go-fake-runtime"
89
"github.com/spf13/cobra"
910
)
1011

1112
// NewRootCmd creates the root command
12-
func NewRootCmd(execer fakeruntime.Execer, gRPCServer gRPCServer) (c *cobra.Command) {
13+
func NewRootCmd(execer fakeruntime.Execer, gRPCServer gRPCServer,
14+
httpServer server.HTTPServer) (c *cobra.Command) {
1315
c = &cobra.Command{
1416
Use: "atest",
1517
Short: "API testing tool",
@@ -18,7 +20,7 @@ func NewRootCmd(execer fakeruntime.Execer, gRPCServer gRPCServer) (c *cobra.Comm
1820
c.Version = version.GetVersion()
1921
c.AddCommand(createInitCommand(execer),
2022
createRunCommand(), createSampleCmd(),
21-
createServerCmd(gRPCServer), createJSONSchemaCmd(),
23+
createServerCmd(gRPCServer, httpServer), createJSONSchemaCmd(),
2224
createServiceCommand(execer), createFunctionCmd())
2325
return
2426
}

cmd/root_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/stretchr/testify/assert"
77

8+
"github.com/linuxsuren/api-testing/pkg/server"
89
fakeruntime "github.com/linuxsuren/go-fake-runtime"
910
)
1011

@@ -15,18 +16,18 @@ func TestCreateRunCommand(t *testing.T) {
1516
init := createInitCommand(fakeruntime.FakeExecer{})
1617
assert.Equal(t, "init", init.Use)
1718

18-
server := createServerCmd(&fakeGRPCServer{})
19-
assert.NotNil(t, server)
20-
assert.Equal(t, "server", server.Use)
19+
s := createServerCmd(&fakeGRPCServer{}, server.NewFakeHTTPServer())
20+
assert.NotNil(t, s)
21+
assert.Equal(t, "server", s.Use)
2122

22-
root := NewRootCmd(fakeruntime.FakeExecer{}, NewFakeGRPCServer())
23+
root := NewRootCmd(fakeruntime.FakeExecer{}, NewFakeGRPCServer(), server.NewFakeHTTPServer())
2324
root.SetArgs([]string{"init", "-k=demo.yaml", "--wait-namespace", "demo", "--wait-resource", "demo"})
2425
err := root.Execute()
2526
assert.Nil(t, err)
2627
}
2728

2829
func TestRootCmd(t *testing.T) {
29-
c := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, NewFakeGRPCServer())
30+
c := NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, NewFakeGRPCServer(), server.NewFakeHTTPServer())
3031
assert.NotNil(t, c)
3132
assert.Equal(t, "atest", c.Use)
3233
}

cmd/sample_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55
"testing"
66

77
"github.com/linuxsuren/api-testing/cmd"
8+
"github.com/linuxsuren/api-testing/pkg/server"
89
"github.com/linuxsuren/api-testing/sample"
910
fakeruntime "github.com/linuxsuren/go-fake-runtime"
1011
"github.com/stretchr/testify/assert"
1112
)
1213

1314
func TestSampleCmd(t *testing.T) {
14-
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"}, cmd.NewFakeGRPCServer())
15+
c := cmd.NewRootCmd(fakeruntime.FakeExecer{ExpectOS: "linux"},
16+
cmd.NewFakeGRPCServer(), server.NewFakeHTTPServer())
1517

1618
buf := new(bytes.Buffer)
1719
c.SetOut(buf)

cmd/server.go

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,44 @@ import (
55
"fmt"
66
"log"
77
"net"
8+
"net/http"
9+
"path"
810

11+
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
912
"github.com/linuxsuren/api-testing/pkg/server"
13+
"github.com/linuxsuren/api-testing/pkg/testing"
1014
"github.com/spf13/cobra"
1115
"google.golang.org/grpc"
1216
)
1317

14-
func createServerCmd(gRPCServer gRPCServer) (c *cobra.Command) {
15-
opt := &serverOption{gRPCServer: gRPCServer}
18+
func createServerCmd(gRPCServer gRPCServer, httpServer server.HTTPServer) (c *cobra.Command) {
19+
opt := &serverOption{
20+
gRPCServer: gRPCServer,
21+
httpServer: httpServer,
22+
}
1623
c = &cobra.Command{
1724
Use: "server",
1825
Short: "Run as a server mode",
1926
RunE: opt.runE,
2027
}
2128
flags := c.Flags()
2229
flags.IntVarP(&opt.port, "port", "p", 7070, "The RPC server port")
30+
flags.IntVarP(&opt.httpPort, "http-port", "", 8080, "The HTTP server port")
2331
flags.BoolVarP(&opt.printProto, "print-proto", "", false, "Print the proto content and exit")
32+
flags.StringVarP(&opt.localStorage, "local-storage", "", "", "The local storage path")
33+
flags.StringVarP(&opt.consolePath, "console-path", "", "", "The path of the console")
2434
return
2535
}
2636

2737
type serverOption struct {
2838
gRPCServer gRPCServer
29-
port int
30-
printProto bool
39+
httpServer server.HTTPServer
40+
41+
port int
42+
httpPort int
43+
printProto bool
44+
localStorage string
45+
consolePath string
3146
}
3247

3348
func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
@@ -38,19 +53,56 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
3853
return
3954
}
4055

41-
var lis net.Listener
56+
var (
57+
lis net.Listener
58+
httplis net.Listener
59+
)
4260
lis, err = net.Listen("tcp", fmt.Sprintf(":%d", o.port))
4361
if err != nil {
4462
return
4563
}
64+
httplis, err = net.Listen("tcp", fmt.Sprintf(":%d", o.httpPort))
65+
if err != nil {
66+
return
67+
}
68+
69+
loader := testing.NewFileLoader()
70+
if o.localStorage != "" {
71+
if err = loader.Put(o.localStorage); err != nil {
72+
return
73+
}
74+
}
4675

76+
removeServer := server.NewRemoteServer(loader)
4777
s := o.gRPCServer
48-
server.RegisterRunnerServer(s, server.NewRemoteServer())
49-
log.Printf("server listening at %v", lis.Addr())
50-
s.Serve(lis)
78+
go func() {
79+
server.RegisterRunnerServer(s, removeServer)
80+
log.Printf("gRPC server listening at %v", lis.Addr())
81+
s.Serve(lis)
82+
}()
83+
84+
mux := runtime.NewServeMux()
85+
err = server.RegisterRunnerHandlerServer(cmd.Context(), mux, removeServer)
86+
if err == nil {
87+
mux.HandlePath("GET", "/", frontEndHandlerWithLocation(o.consolePath))
88+
mux.HandlePath("GET", "/assets/{asset}", frontEndHandlerWithLocation(o.consolePath))
89+
o.httpServer.WithHandler(mux)
90+
err = o.httpServer.Serve(httplis)
91+
}
5192
return
5293
}
5394

95+
func frontEndHandlerWithLocation(consolePath string) func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
96+
return func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
97+
target := r.URL.Path
98+
if target == "/" {
99+
target = "/index.html"
100+
}
101+
102+
http.ServeFile(w, r, path.Join(consolePath, target))
103+
}
104+
}
105+
54106
type gRPCServer interface {
55107
Serve(lis net.Listener) error
56108
grpc.ServiceRegistrar

0 commit comments

Comments
 (0)