Skip to content

Commit

Permalink
feat: support to calculate the API coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen committed Jun 13, 2023
1 parent 909341b commit c651835
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 3 deletions.
7 changes: 7 additions & 0 deletions cmd/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"runtime"
"strings"

"github.com/antonmedv/expr/builtin"
"github.com/linuxsuren/api-testing/pkg/render"
"github.com/spf13/cobra"
)
Expand All @@ -36,6 +37,12 @@ func createFunctionCmd() (c *cobra.Command) {
cmd.Println(name, reflect.TypeOf(fn))
}
}

fmt.Println("====")
for key := range builtin.Builtins {

fmt.Println(key)
}
return
},
}
Expand Down
13 changes: 13 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync"
"time"

"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/linuxsuren/api-testing/pkg/limit"
"github.com/linuxsuren/api-testing/pkg/render"
"github.com/linuxsuren/api-testing/pkg/runner"
Expand All @@ -35,6 +36,7 @@ type runOption struct {
reportWriter runner.ReportResultWriter
report string
reportIgnore bool
swaggerURL string
level string
caseItems []string
}
Expand Down Expand Up @@ -77,6 +79,7 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, html, discard, std")
flags.StringVarP(&opt.reportFile, "report-file", "", "", "The file path of the report")
flags.BoolVarP(&opt.reportIgnore, "report-ignore", "", false, "Indicate if ignore the report output")
flags.StringVarP(&opt.swaggerURL, "swagger-url", "", "", "The URL of swagger")
flags.Int64VarP(&opt.thread, "thread", "", 1, "Threads of the execution")
flags.Int32VarP(&opt.qps, "qps", "", 5, "QPS")
flags.Int32VarP(&opt.burst, "burst", "", 5, "burst")
Expand Down Expand Up @@ -108,6 +111,16 @@ func (o *runOption) preRunE(cmd *cobra.Command, args []string) (err error) {
err = fmt.Errorf("not supported report type: '%s'", o.report)
}

fmt.Println(o.swaggerURL, o.report)
if err == nil {
var swaggerAPI apispec.APIConverage
if o.swaggerURL != "" {
if swaggerAPI, err = apispec.ParseURLToSwagger(o.swaggerURL); err == nil {
o.reportWriter.WithAPIConverage(swaggerAPI)
}
}
}

o.caseItems = args
return
}
Expand Down
98 changes: 98 additions & 0 deletions pkg/apispec/swagger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package apispec

import (
"encoding/json"
"io"
"net/http"
"regexp"
"strings"
)

type Swagger struct {
Swagger string `json:"swagger"`
Paths map[string]map[string]SwaggerAPI `json:"paths"`
Info SwaggerInfo `json:"info"`
}

type SwaggerAPI struct {
OperationId string `json:"operationId"`
Summary string `json:"summary"`
}

type SwaggerInfo struct {
Description string `json:"description"`
Title string `json:"title"`
Version string `json:"version"`
}

type APIConverage interface {
HaveAPI(path, method string) (exist bool)
APICount() (count int)
}

// HaveAPI check if the swagger has the API.
// If the path is /api/v1/names/linuxsuren, then will match /api/v1/names/{name}
func (s *Swagger) HaveAPI(path, method string) (exist bool) {
method = strings.ToLower(method)
for item := range s.Paths {
if matchAPI(path, item) {
for m := range s.Paths[item] {
if strings.ToLower(m) == method {
exist = true
return
}
}
}
}
return
}

func matchAPI(particularAPI, swaggerAPI string) (matched bool) {
result := swaggerAPIConvert(swaggerAPI)
reg, err := regexp.Compile(result)
if err == nil {
matched = reg.MatchString(particularAPI)
}
return
}

func swaggerAPIConvert(text string) (result string) {
result = text
reg, err := regexp.Compile("{.*}")
if err == nil {
result = reg.ReplaceAllString(text, ".*")
}
return
}

// APICount return the count of APIs
func (s *Swagger) APICount() (count int) {
for path := range s.Paths {
for range s.Paths[path] {
count++
}
}
return
}

func ParseToSwagger(data []byte) (swagger *Swagger, err error) {
swagger = &Swagger{}
err = json.Unmarshal(data, swagger)
return
}

func ParseURLToSwagger(swaggerURL string) (swagger *Swagger, err error) {
var resp *http.Response
if resp, err = http.Get(swaggerURL); err == nil && resp != nil && resp.StatusCode == http.StatusOK {
swagger, err = ParseStreamToSwagger(resp.Body)
}
return
}

func ParseStreamToSwagger(stream io.Reader) (swagger *Swagger, err error) {
var data []byte
if data, err = io.ReadAll(stream); err == nil {
swagger, err = ParseToSwagger(data)
}
return
}
111 changes: 111 additions & 0 deletions pkg/apispec/swagger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package apispec_test

import (
"net/http"
"testing"

_ "embed"

"github.com/h2non/gock"
"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/stretchr/testify/assert"
)

func TestParseURLToSwagger(t *testing.T) {
tests := []struct {
name string
swaggerURL string
verify func(t *testing.T, swagger *apispec.Swagger, err error)
}{{
name: "normal",
swaggerURL: "http://foo",
verify: func(t *testing.T, swagger *apispec.Swagger, err error) {
assert.NoError(t, err)
assert.Equal(t, "2.0", swagger.Swagger)
assert.Equal(t, apispec.SwaggerInfo{
Description: "sample",
Title: "sample",
Version: "1.0.0",
}, swagger.Info)
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gock.New(tt.swaggerURL).Get("/").Reply(200).BodyString(testdataSwaggerJSON)
defer gock.Off()

s, err := apispec.ParseURLToSwagger(tt.swaggerURL)
tt.verify(t, s, err)
})
}
}

func TestHaveAPI(t *testing.T) {
tests := []struct {
name string
swaggerURL string
path, method string
expectExist bool
}{{
name: "normal, exist",
swaggerURL: "http://foo",
path: "/api/v1/users",
method: http.MethodGet,
expectExist: true,
}, {
name: "create user, exist",
swaggerURL: "http://foo",
path: "/api/v1/users",
method: http.MethodPost,
expectExist: true,
}, {
name: "get a user, exist",
swaggerURL: "http://foo",
path: "/api/v1/users/linuxsuren",
method: http.MethodGet,
expectExist: true,
}, {
name: "normal, not exist",
swaggerURL: "http://foo",
path: "/api/v1/users",
method: http.MethodDelete,
expectExist: false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gock.New(tt.swaggerURL).Get("/").Reply(200).BodyString(testdataSwaggerJSON)
defer gock.Off()

swagger, err := apispec.ParseURLToSwagger(tt.swaggerURL)
assert.NoError(t, err)
exist := swagger.HaveAPI(tt.path, tt.method)
assert.Equal(t, tt.expectExist, exist)
})
}
}

func TestAPICount(t *testing.T) {
tests := []struct {
name string
swaggerURL string
expectCount int
}{{
name: "normal",
swaggerURL: "http://foo",
expectCount: 5,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gock.New(tt.swaggerURL).Get("/").Reply(200).BodyString(testdataSwaggerJSON)
defer gock.Off()

swagger, err := apispec.ParseURLToSwagger(tt.swaggerURL)
assert.NoError(t, err)
count := swagger.APICount()
assert.Equal(t, tt.expectCount, count)
})
}
}

//go:embed testdata/swagger.json
var testdataSwaggerJSON string
34 changes: 34 additions & 0 deletions pkg/apispec/testdata/swagger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"swagger": "2.0",
"info": {
"description": "sample",
"title": "sample",
"version": "1.0.0"
},
"paths": {
"/api/v1/users": {
"get": {
"summary": "summary",
"operationId": "getUsers"
},
"post": {
"summary": "summary",
"operationId": "createUser"
}
},
"/api/v1/users/{user}": {
"get": {
"summary": "summary",
"operationId": "getUser"
},
"delete": {
"summary": "summary",
"operationId": "deleteUser"
},
"put": {
"summary": "summary",
"operationId": "updateUser"
}
}
}
}
2 changes: 2 additions & 0 deletions pkg/runner/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/andreyvit/diff"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/vm"
"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/linuxsuren/api-testing/pkg/runner/kubernetes"
"github.com/linuxsuren/api-testing/pkg/testing"
fakeruntime "github.com/linuxsuren/go-fake-runtime"
Expand Down Expand Up @@ -153,6 +154,7 @@ func (r ReportResultSlice) Swap(i, j int) {
// ReportResultWriter is the interface of the report writer
type ReportResultWriter interface {
Output([]ReportResult) error
WithAPIConverage(apiConverage apispec.APIConverage) ReportResultWriter
}

// TestReporter is the interface of the report
Expand Down
10 changes: 9 additions & 1 deletion pkg/runner/writer_html.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
_ "embed"
"io"

"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/linuxsuren/api-testing/pkg/render"
)

type htmlResultWriter struct {
writer io.Writer
writer io.Writer
apiConverage apispec.APIConverage
}

// NewHTMLResultWriter creates a new htmlResultWriter
Expand All @@ -21,5 +23,11 @@ func (w *htmlResultWriter) Output(result []ReportResult) (err error) {
return render.RenderThenPrint("html-report", htmlReport, result, w.writer)
}

// WithAPIConverage sets the api coverage
func (w *htmlResultWriter) WithAPIConverage(apiConverage apispec.APIConverage) ReportResultWriter {
w.apiConverage = apiConverage
return w
}

//go:embed data/html.html
var htmlReport string
10 changes: 9 additions & 1 deletion pkg/runner/writer_markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
_ "embed"
"io"

"github.com/linuxsuren/api-testing/pkg/apispec"
"github.com/linuxsuren/api-testing/pkg/render"
)

type markdownResultWriter struct {
writer io.Writer
writer io.Writer
apiConverage apispec.APIConverage
}

// NewMarkdownResultWriter creates the Markdown writer
Expand All @@ -21,5 +23,11 @@ func (w *markdownResultWriter) Output(result []ReportResult) (err error) {
return render.RenderThenPrint("md-report", markdownReport, result, w.writer)
}

// WithAPIConverage sets the api coverage
func (w *markdownResultWriter) WithAPIConverage(apiConverage apispec.APIConverage) ReportResultWriter {
w.apiConverage = apiConverage
return w
}

//go:embed data/report.md
var markdownReport string
Loading

0 comments on commit c651835

Please sign in to comment.