Skip to content

Commit

Permalink
feat: add api methods to get test and test suite with latest execution (
Browse files Browse the repository at this point in the history
#1155)

* feat: add api methods to get test and test suite with latest execution

* fix: cli support for test and test suite with execution

* fix: add cli formatting and change mongodb query
  • Loading branch information
vsukhin authored Mar 29, 2022
1 parent 2038978 commit b906b39
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 13 deletions.
86 changes: 85 additions & 1 deletion api/v1/testkube.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,38 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Execution"
$ref: "#/components/schemas/TestSuite"
500:
description: "problem with getting test suite from storage"
content:
application/problem+json:
schema:
type: array
items:
$ref: "#/components/schemas/Problem"

/test-suite-with-executions/{id}:
get:
parameters:
- in: path
name: id
schema:
type: string
required: true
description: ID of the test suite
tags:
- test-suites
- api
summary: "Get test suite by ID with execution"
description: "Returns test suite with given name with execution"
operationId: getTestSuiteByIDWithExecution
responses:
200:
description: successful operation
content:
application/json:
schema:
$ref: "#/components/schemas/TestSuiteWithExecution"
500:
description: "problem with getting test suite from storage"
content:
Expand Down Expand Up @@ -502,6 +533,37 @@ paths:
items:
$ref: "#/components/schemas/Problem"

/test-with-executions/{id}:
get:
tags:
- tests
- api
parameters:
- in: path
name: id
schema:
type: string
required: true
description: ID of the test
summary: "Get test with execution"
description: "Gets the specified test with execution"
operationId: getTestWithExecution
responses:
200:
description: "successful operation"
content:
application/json:
schema:
$ref: "#/components/schemas/TestWithExecution"
502:
description: "problem with read information from kubernetes cluster"
content:
application/problem+json:
schema:
type: array
items:
$ref: "#/components/schemas/Problem"

/tests/{id}/executions:
post:
parameters:
Expand Down Expand Up @@ -1636,6 +1698,28 @@ components:
- start-test
- end-test

TestWithExecution:
description: Test with latest Execution result
type: object
required:
- test
properties:
test:
$ref: "#/components/schemas/Test"
latest_execution:
$ref: "#/components/schemas/Execution"

TestSuiteWithExecution:
description: Test suite with latest execution result
type: object
required:
- testSuite
properties:
testSuite:
$ref: "#/components/schemas/TestSuite"
latest_execution:
$ref: "#/components/schemas/TestSuiteExecution"

#
# Errors
#
Expand Down
22 changes: 19 additions & 3 deletions cmd/kubectl-testkube/commands/tests/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import (
)

func NewGetTestsCmd() *cobra.Command {
var selectors []string
var (
selectors []string
noExecution bool
)

cmd := &cobra.Command{
Use: "test <testName>",
Expand All @@ -30,9 +33,21 @@ func NewGetTestsCmd() *cobra.Command {

if len(args) > 0 {
name = args[0]
test, err := client.GetTest(name, namespace)
test, err := client.GetTestWithExecution(name, namespace)
ui.ExitOnError("getting test in namespace "+namespace, err)
render.Obj(cmd, test, os.Stdout, renderer.TestRenderer)

if test.Test != nil {
err = render.Obj(cmd, *test.Test, os.Stdout, renderer.TestRenderer)
ui.ExitOnError("rendering obj", err)
}

if test.LatestExecution != nil && !noExecution {
ui.NL()
ui.Info("Latest execution")
err = render.Obj(cmd, *test.LatestExecution, os.Stdout, renderer.ExecutionRenderer)
ui.ExitOnError("rendering obj", err)
}

} else {
tests, err = client.ListTests(namespace, strings.Join(selectors, ","))
ui.ExitOnError("getting all tests in namespace "+namespace, err)
Expand All @@ -42,6 +57,7 @@ func NewGetTestsCmd() *cobra.Command {
},
}
cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1")
cmd.Flags().BoolVar(&noExecution, "no-execution", false, "don't show latest execution")

return cmd
}
22 changes: 18 additions & 4 deletions cmd/kubectl-testkube/commands/testsuites/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
)

func NewGetTestSuiteCmd() *cobra.Command {
var selectors []string
var (
selectors []string
noExecution bool
)

cmd := &cobra.Command{
Use: "testsuite <testSuiteName>",
Expand All @@ -25,10 +28,19 @@ func NewGetTestSuiteCmd() *cobra.Command {

if len(args) > 0 {
name := args[0]
testSuite, err := client.GetTestSuite(name, namespace)
testSuite, err := client.GetTestSuiteWithExecution(name, namespace)
ui.ExitOnError("getting test suite "+name, err)
err = render.Obj(cmd, testSuite, os.Stdout, renderer.TestSuiteRenderer)
ui.ExitOnError("rendering obj", err)
if testSuite.TestSuite != nil {
err = render.Obj(cmd, *testSuite.TestSuite, os.Stdout, renderer.TestSuiteRenderer)
ui.ExitOnError("rendering obj", err)
}

if testSuite.LatestExecution != nil && !noExecution {
ui.NL()
ui.Info("Latest execution")
err = render.Obj(cmd, *testSuite.LatestExecution, os.Stdout, renderer.TestSuiteExecutionRenderer)
ui.ExitOnError("rendering obj", err)
}
} else {
testSuites, err := client.ListTestSuites(namespace, strings.Join(selectors, ","))
ui.ExitOnError("getting test suites", err)
Expand All @@ -40,5 +52,7 @@ func NewGetTestSuiteCmd() *cobra.Command {
}

cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1")
cmd.Flags().BoolVar(&noExecution, "no-execution", false, "don't show latest execution")

return cmd
}
6 changes: 6 additions & 0 deletions internal/app/api/v1/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ func (s TestkubeAPI) Init() {
tests.Get("/:id/executions/:executionID", s.GetExecutionHandler())
tests.Delete("/:id/executions/:executionID", s.AbortExecutionHandler())

testWithExecutions := s.Routes.Group("/test-with-executions")
testWithExecutions.Get("/:id", s.GetTestWithExecutionHandler())

testsuites := s.Routes.Group("/test-suites")

testsuites.Post("/", s.CreateTestSuiteHandler())
Expand All @@ -199,6 +202,9 @@ func (s TestkubeAPI) Init() {
testExecutions.Get("/", s.ListTestSuiteExecutionsHandler())
testExecutions.Get("/:executionID", s.GetTestSuiteExecutionHandler())

testSuiteWithExecutions := s.Routes.Group("/test-suite-with-executions")
testSuiteWithExecutions.Get("/:id", s.GetTestSuiteWithExecutionHandler())

labels := s.Routes.Group("/labels")
labels.Get("/", s.ListLabelsHandler())

Expand Down
40 changes: 37 additions & 3 deletions internal/app/api/v1/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (
"github.com/kubeshop/testkube/pkg/cronjob"
testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests"
"github.com/kubeshop/testkube/pkg/secret"
"go.mongodb.org/mongo-driver/mongo"

"github.com/kubeshop/testkube/pkg/jobs"
"k8s.io/apimachinery/pkg/api/errors"
)

// GetTestHandler is method for getting an existing tests
// GetTestHandler is method for getting an existing test
func (s TestkubeAPI) GetTestHandler() fiber.Handler {
return func(c *fiber.Ctx) error {
name := c.Params("id")
Expand All @@ -28,8 +29,41 @@ func (s TestkubeAPI) GetTestHandler() fiber.Handler {
return s.Error(c, http.StatusBadGateway, err)
}

tests := testsmapper.MapTestCRToAPI(*crTest)
return c.JSON(tests)
test := testsmapper.MapTestCRToAPI(*crTest)

return c.JSON(test)
}
}

// GetTestWithExecutionHandler is method for getting an existing test with execution
func (s TestkubeAPI) GetTestWithExecutionHandler() fiber.Handler {
return func(c *fiber.Ctx) error {
name := c.Params("id")
namespace := c.Query("namespace", "testkube")
crTest, err := s.TestsClient.Get(namespace, name)
if err != nil {
if errors.IsNotFound(err) {
return s.Error(c, http.StatusNotFound, err)
}

return s.Error(c, http.StatusBadGateway, err)
}

ctx := c.Context()
execution, err := s.ExecutionResults.GetLatestByTest(ctx, name)
if err != nil && err != mongo.ErrNoDocuments {
return s.Error(c, http.StatusInternalServerError, err)
}

test := testsmapper.MapTestCRToAPI(*crTest)
testWithExecution := testkube.TestWithExecution{
Test: &test,
}
if err == nil {
testWithExecution.LatestExecution = &execution
}

return c.JSON(testWithExecution)
}
}

Expand Down
33 changes: 33 additions & 0 deletions internal/app/api/v1/testsuites.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/gofiber/fiber/v2"
"go.mongodb.org/mongo-driver/mongo"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

Expand Down Expand Up @@ -101,6 +102,38 @@ func (s TestkubeAPI) GetTestSuiteHandler() fiber.Handler {
}
}

// GetTestSuiteWithExecutionHandler for getting TestSuite object with execution
func (s TestkubeAPI) GetTestSuiteWithExecutionHandler() fiber.Handler {
return func(c *fiber.Ctx) error {
name := c.Params("id")
namespace := c.Query("namespace", "testkube")
crTestSuite, err := s.TestsSuitesClient.Get(namespace, name)
if err != nil {
if errors.IsNotFound(err) {
return s.Warn(c, http.StatusNotFound, err)
}

return s.Error(c, http.StatusBadGateway, err)
}

ctx := c.Context()
execution, err := s.TestExecutionResults.GetLatestByTest(ctx, name)
if err != nil && err != mongo.ErrNoDocuments {
return s.Error(c, http.StatusInternalServerError, err)
}

testSuite := testsuitesmapper.MapCRToAPI(*crTestSuite)
testSuiteWithExecution := testkube.TestSuiteWithExecution{
TestSuite: &testSuite,
}
if err == nil {
testSuiteWithExecution.LatestExecution = &execution
}

return c.JSON(testSuiteWithExecution)
}
}

// DeleteTestSuiteHandler for deleting a TestSuite with id
func (s TestkubeAPI) DeleteTestSuiteHandler() fiber.Handler {
return func(c *fiber.Ctx) error {
Expand Down
2 changes: 2 additions & 0 deletions internal/pkg/api/repository/result/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type Repository interface {
Get(ctx context.Context, id string) (testkube.Execution, error)
// GetByNameAndTest gets execution result by name
GetByNameAndTest(ctx context.Context, name, testName string) (testkube.Execution, error)
// GetLatestByTest gets latest execution result by test
GetLatestByTest(ctx context.Context, testName string) (testkube.Execution, error)
// GetExecutions gets executions using a filter, use filter with no data for all
GetExecutions(ctx context.Context, filter Filter) ([]testkube.Execution, error)
// GetExecutionTotals gets the statistics on number of executions using a filter, but without paging
Expand Down
7 changes: 7 additions & 0 deletions internal/pkg/api/repository/result/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func (r *MongoRepository) GetByNameAndTest(ctx context.Context, name, testName s
return
}

func (r *MongoRepository) GetLatestByTest(ctx context.Context, testName string) (result testkube.Execution, err error) {
findOptions := options.FindOne()
findOptions.SetSort(bson.D{{"starttime", -1}})
err = r.Coll.FindOne(ctx, bson.M{"testname": testName}, findOptions).Decode(&result)
return
}

func (r *MongoRepository) GetNewestExecutions(ctx context.Context, limit int) (result []testkube.Execution, err error) {
result = make([]testkube.Execution, 0)
resultLimit := int64(limit)
Expand Down
2 changes: 2 additions & 0 deletions internal/pkg/api/repository/testresult/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Repository interface {
Get(ctx context.Context, id string) (testkube.TestSuiteExecution, error)
// GetByNameAndTest gets execution result by name
GetByNameAndTest(ctx context.Context, name, testName string) (testkube.TestSuiteExecution, error)
// GetLatestByTest gets latest execution result by test
GetLatestByTest(ctx context.Context, testName string) (testkube.TestSuiteExecution, error)
// GetExecutionsTotals gets executions total stats using a filter, use filter with no data for all
GetExecutionsTotals(ctx context.Context, filter ...Filter) (totals testkube.ExecutionsTotals, err error)
// GetExecutions gets executions using a filter, use filter with no data for all
Expand Down
11 changes: 9 additions & 2 deletions internal/pkg/api/repository/testresult/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ func (r *MongoRepository) Get(ctx context.Context, id string) (result testkube.T
}

func (r *MongoRepository) GetByNameAndTest(ctx context.Context, name, testName string) (result testkube.TestSuiteExecution, err error) {
err = r.Coll.FindOne(ctx, bson.M{"name": name, "testname": testName}).Decode(&result)
err = r.Coll.FindOne(ctx, bson.M{"name": name, "testsuite.name": testName}).Decode(&result)
return
}

func (r *MongoRepository) GetLatestByTest(ctx context.Context, testName string) (result testkube.TestSuiteExecution, err error) {
findOptions := options.FindOne()
findOptions.SetSort(bson.D{{"starttime", -1}})
err = r.Coll.FindOne(ctx, bson.M{"testsuite.name": testName}, findOptions).Decode(&result)
return
}

Expand Down Expand Up @@ -137,7 +144,7 @@ func composeQueryAndOpts(filter Filter) (bson.M, *options.FindOptions) {
startTimeQuery := bson.M{}

if filter.NameDefined() {
query["test.name"] = filter.Name()
query["testsuite.name"] = filter.Name()
}

if filter.TextSearchDefined() {
Expand Down
Loading

0 comments on commit b906b39

Please sign in to comment.