github.com/kubeshop/testkube@v1.17.23/internal/app/api/v1/tests.go (about)

     1  package v1
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"net/http"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/gofiber/fiber/v2"
    13  	"go.mongodb.org/mongo-driver/mongo"
    14  	"k8s.io/apimachinery/pkg/api/errors"
    15  	"k8s.io/apimachinery/pkg/util/yaml"
    16  
    17  	testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3"
    18  	"github.com/kubeshop/testkube-operator/pkg/client/tests/v3"
    19  	testsclientv3 "github.com/kubeshop/testkube-operator/pkg/client/tests/v3"
    20  	"github.com/kubeshop/testkube-operator/pkg/secret"
    21  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    22  	"github.com/kubeshop/testkube/pkg/crd"
    23  	"github.com/kubeshop/testkube/pkg/executor/client"
    24  	executionsmapper "github.com/kubeshop/testkube/pkg/mapper/executions"
    25  	testsmapper "github.com/kubeshop/testkube/pkg/mapper/tests"
    26  	"github.com/kubeshop/testkube/pkg/repository/result"
    27  )
    28  
    29  // GetTestHandler is method for getting an existing test
    30  func (s TestkubeAPI) GetTestHandler() fiber.Handler {
    31  	return func(c *fiber.Ctx) error {
    32  		name := c.Params("id")
    33  		if name == "" {
    34  			return s.Error(c, http.StatusBadRequest, fmt.Errorf("failed to get test: id cannot be empty"))
    35  		}
    36  		errPrefix := fmt.Sprintf("failed to get test %s", name)
    37  		crTest, err := s.TestsClient.Get(name)
    38  		if err != nil {
    39  			if errors.IsNotFound(err) {
    40  				return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: client was unable to find test: %w", errPrefix, err))
    41  			}
    42  
    43  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client failed to find test: %w", errPrefix, err))
    44  		}
    45  
    46  		test := testsmapper.MapTestCRToAPI(*crTest)
    47  		if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML {
    48  			test.QuoteTestTextFields()
    49  			data, err := crd.GenerateYAML(crd.TemplateTest, []testkube.Test{test})
    50  			if err != nil {
    51  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err))
    52  			}
    53  			return s.getCRDs(c, data, err)
    54  		}
    55  
    56  		return c.JSON(test)
    57  	}
    58  }
    59  
    60  // GetTestWithExecutionHandler is method for getting an existing test with execution
    61  func (s TestkubeAPI) GetTestWithExecutionHandler() fiber.Handler {
    62  	return func(c *fiber.Ctx) error {
    63  		name := c.Params("id")
    64  		if name == "" {
    65  			return s.Error(c, http.StatusBadRequest, fmt.Errorf("failed to get test with execution: id cannot be empty"))
    66  		}
    67  		errPrefix := fmt.Sprintf("failed to get test %s with execution", name)
    68  		crTest, err := s.TestsClient.Get(name)
    69  		if err != nil {
    70  			if errors.IsNotFound(err) {
    71  				return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: client failed to find test: %w", errPrefix, err))
    72  			}
    73  
    74  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client failed to find test: %w", errPrefix, err))
    75  		}
    76  
    77  		test := testsmapper.MapTestCRToAPI(*crTest)
    78  		if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML {
    79  			test.QuoteTestTextFields()
    80  			data, err := crd.GenerateYAML(crd.TemplateTest, []testkube.Test{test})
    81  			if err != nil {
    82  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err))
    83  			}
    84  			return s.getCRDs(c, data, err)
    85  		}
    86  
    87  		ctx := c.Context()
    88  		execution, err := s.ExecutionResults.GetLatestByTest(ctx, name)
    89  		if err != nil && err != mongo.ErrNoDocuments {
    90  			return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: failed to get execution: %w", errPrefix, err))
    91  		}
    92  
    93  		return c.JSON(testkube.TestWithExecution{
    94  			Test:            &test,
    95  			LatestExecution: execution,
    96  		})
    97  	}
    98  }
    99  
   100  func (s TestkubeAPI) getFilteredTestList(c *fiber.Ctx) (*testsv3.TestList, error) {
   101  
   102  	crTests, err := s.TestsClient.List(c.Query("selector"))
   103  	if err != nil {
   104  		return nil, fmt.Errorf("client failed to list tests: %w", err)
   105  	}
   106  
   107  	search := c.Query("textSearch")
   108  	if search != "" {
   109  		// filter items array
   110  		for i := len(crTests.Items) - 1; i >= 0; i-- {
   111  			if !strings.Contains(crTests.Items[i].Name, search) {
   112  				crTests.Items = append(crTests.Items[:i], crTests.Items[i+1:]...)
   113  			}
   114  		}
   115  	}
   116  
   117  	testType := c.Query("type")
   118  	if testType != "" {
   119  		// filter items array
   120  		for i := len(crTests.Items) - 1; i >= 0; i-- {
   121  			if !strings.Contains(crTests.Items[i].Spec.Type_, testType) {
   122  				crTests.Items = append(crTests.Items[:i], crTests.Items[i+1:]...)
   123  			}
   124  		}
   125  	}
   126  
   127  	return crTests, nil
   128  }
   129  
   130  // ListTestsHandler is a method for getting list of all available tests
   131  func (s TestkubeAPI) ListTestsHandler() fiber.Handler {
   132  	return func(c *fiber.Ctx) error {
   133  		errPrefix := "failed to list tests"
   134  		crTests, err := s.getFilteredTestList(c)
   135  		if err != nil {
   136  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: unable to get filtered tests: %w", errPrefix, err))
   137  		}
   138  
   139  		tests := testsmapper.MapTestListKubeToAPI(*crTests)
   140  		if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML {
   141  			for i := range tests {
   142  				tests[i].QuoteTestTextFields()
   143  			}
   144  
   145  			data, err := crd.GenerateYAML(crd.TemplateTest, tests)
   146  			if err != nil {
   147  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err))
   148  			}
   149  			return s.getCRDs(c, data, err)
   150  		}
   151  
   152  		return c.JSON(tests)
   153  	}
   154  }
   155  
   156  // ListTestsHandler is a method for getting list of all available tests
   157  func (s TestkubeAPI) TestMetricsHandler() fiber.Handler {
   158  	return func(c *fiber.Ctx) error {
   159  		testName := c.Params("id")
   160  
   161  		const DefaultLimit = 0
   162  		limit, err := strconv.Atoi(c.Query("limit", strconv.Itoa(DefaultLimit)))
   163  		if err != nil {
   164  			limit = DefaultLimit
   165  		}
   166  
   167  		const DefaultLastDays = 7
   168  		last, err := strconv.Atoi(c.Query("last", strconv.Itoa(DefaultLastDays)))
   169  		if err != nil {
   170  			last = DefaultLastDays
   171  		}
   172  
   173  		metrics, err := s.ExecutionResults.GetTestMetrics(context.Background(), testName, limit, last)
   174  		if err != nil {
   175  			return s.Error(c, http.StatusInternalServerError, fmt.Errorf("failed to get metrics for test %s: %w", testName, err))
   176  		}
   177  
   178  		return c.JSON(metrics)
   179  	}
   180  }
   181  
   182  // getLatestExecutions return latest executions either by starttime or endtime for tests
   183  func (s TestkubeAPI) getLatestExecutions(ctx context.Context, testNames []string) (map[string]testkube.Execution, error) {
   184  	executions, err := s.ExecutionResults.GetLatestByTests(ctx, testNames)
   185  	if err != nil && err != mongo.ErrNoDocuments {
   186  		return nil, fmt.Errorf("could not get latest executions for tests %s sorted by start time: %w", testNames, err)
   187  	}
   188  
   189  	executionMap := make(map[string]testkube.Execution, len(executions))
   190  	for i := range executions {
   191  		executionMap[executions[i].TestName] = executions[i]
   192  	}
   193  	return executionMap, nil
   194  }
   195  
   196  // ListTestWithExecutionsHandler is a method for getting list of all available test with latest executions
   197  func (s TestkubeAPI) ListTestWithExecutionsHandler() fiber.Handler {
   198  	return func(c *fiber.Ctx) error {
   199  		errPrefix := "failed to list tests with executions"
   200  		crTests, err := s.getFilteredTestList(c)
   201  		if err != nil {
   202  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: unable to get filtered tests: %w", errPrefix, err))
   203  		}
   204  
   205  		tests := testsmapper.MapTestListKubeToAPI(*crTests)
   206  		if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML {
   207  			for i := range tests {
   208  				tests[i].QuoteTestTextFields()
   209  			}
   210  
   211  			data, err := crd.GenerateYAML(crd.TemplateTest, tests)
   212  			if err != nil {
   213  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err))
   214  			}
   215  			return s.getCRDs(c, data, err)
   216  		}
   217  
   218  		ctx := c.Context()
   219  		results := make([]testkube.TestWithExecutionSummary, 0, len(tests))
   220  		testNames := make([]string, len(tests))
   221  		for i := range tests {
   222  			testNames[i] = tests[i].Name
   223  		}
   224  
   225  		executionMap, err := s.getLatestExecutions(ctx, testNames)
   226  		if err != nil {
   227  			return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not get latest executions: %w", errPrefix, err))
   228  		}
   229  
   230  		for i := range tests {
   231  			if execution, ok := executionMap[tests[i].Name]; ok {
   232  				results = append(results, testkube.TestWithExecutionSummary{
   233  					Test:            &tests[i],
   234  					LatestExecution: executionsmapper.MapToSummary(&execution),
   235  				})
   236  			} else {
   237  				results = append(results, testkube.TestWithExecutionSummary{
   238  					Test: &tests[i],
   239  				})
   240  			}
   241  		}
   242  
   243  		sort.Slice(results, func(i, j int) bool {
   244  			iTime := results[i].Test.Created
   245  			if results[i].LatestExecution != nil {
   246  				iTime = results[i].LatestExecution.EndTime
   247  				if results[i].LatestExecution.StartTime.After(results[i].LatestExecution.EndTime) {
   248  					iTime = results[i].LatestExecution.StartTime
   249  				}
   250  			}
   251  
   252  			jTime := results[j].Test.Created
   253  			if results[j].LatestExecution != nil {
   254  				jTime = results[j].LatestExecution.EndTime
   255  				if results[j].LatestExecution.StartTime.After(results[j].LatestExecution.EndTime) {
   256  					jTime = results[j].LatestExecution.StartTime
   257  				}
   258  			}
   259  
   260  			return iTime.After(jTime)
   261  		})
   262  
   263  		status := c.Query("status")
   264  		if status != "" {
   265  			statusList, err := testkube.ParseExecutionStatusList(status, ",")
   266  			if err != nil {
   267  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: execution status filter invalid: %w", errPrefix, err))
   268  			}
   269  
   270  			statusMap := statusList.ToMap()
   271  			// filter items array
   272  			for i := len(results) - 1; i >= 0; i-- {
   273  				if results[i].LatestExecution != nil && results[i].LatestExecution.Status != nil {
   274  					if _, ok := statusMap[*results[i].LatestExecution.Status]; ok {
   275  						continue
   276  					}
   277  				}
   278  
   279  				results = append(results[:i], results[i+1:]...)
   280  			}
   281  		}
   282  
   283  		var page, pageSize int
   284  		pageParam := c.Query("page", "")
   285  		if pageParam != "" {
   286  			pageSize = result.PageDefaultLimit
   287  			page, err = strconv.Atoi(pageParam)
   288  			if err != nil {
   289  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: test page filter invalid: %w", errPrefix, err))
   290  			}
   291  		}
   292  
   293  		pageSizeParam := c.Query("pageSize", "")
   294  		if pageSizeParam != "" {
   295  			pageSize, err = strconv.Atoi(pageSizeParam)
   296  			if err != nil {
   297  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: test page size filter invalid: %w", errPrefix, err))
   298  			}
   299  		}
   300  
   301  		if pageParam != "" || pageSizeParam != "" {
   302  			startPos := page * pageSize
   303  			endPos := (page + 1) * pageSize
   304  			if startPos < len(results) {
   305  				if endPos > len(results) {
   306  					endPos = len(results)
   307  				}
   308  
   309  				results = results[startPos:endPos]
   310  			}
   311  		}
   312  
   313  		return c.JSON(results)
   314  	}
   315  }
   316  
   317  // CreateTestHandler creates new test CR based on test content
   318  func (s TestkubeAPI) CreateTestHandler() fiber.Handler {
   319  	return func(c *fiber.Ctx) error {
   320  		errPrefix := "failed to create test"
   321  		var test *testsv3.Test
   322  		var secrets map[string]string
   323  		if string(c.Request().Header.ContentType()) == mediaTypeYAML {
   324  			test = &testsv3.Test{}
   325  			testSpec := string(c.Body())
   326  			decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSpec), len(testSpec))
   327  			if err := decoder.Decode(&test); err != nil {
   328  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err))
   329  			}
   330  
   331  			errPrefix = errPrefix + " " + test.Name
   332  		} else {
   333  			var request testkube.TestUpsertRequest
   334  			err := c.BodyParser(&request)
   335  			if err != nil {
   336  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: failed to unmarshal request: %w", errPrefix, err))
   337  			}
   338  
   339  			err = testkube.ValidateUpsertTestRequest(request)
   340  			if err != nil {
   341  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: invalid test: %w", errPrefix, err))
   342  			}
   343  
   344  			errPrefix = errPrefix + " " + request.Name
   345  			if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML {
   346  				request.QuoteTestTextFields()
   347  				data, err := crd.GenerateYAML(crd.TemplateTest, []testkube.TestUpsertRequest{request})
   348  				if err != nil {
   349  					return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not build CRD: %w", errPrefix, err))
   350  				}
   351  
   352  				return s.getCRDs(c, data, err)
   353  			}
   354  
   355  			s.Log.Infow("creating test", "request", request)
   356  
   357  			test = testsmapper.MapUpsertToSpec(request)
   358  			test.Namespace = s.Namespace
   359  			if request.Content != nil && request.Content.Repository != nil && !s.disableSecretCreation {
   360  				secrets = createTestSecretsData(request.Content.Repository.Username, request.Content.Repository.Token)
   361  			}
   362  		}
   363  
   364  		createdTest, err := s.TestsClient.Create(test, s.disableSecretCreation, tests.Option{Secrets: secrets})
   365  
   366  		s.Metrics.IncCreateTest(test.Spec.Type_, err)
   367  
   368  		if err != nil {
   369  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not create test: %w", errPrefix, err))
   370  		}
   371  
   372  		c.Status(http.StatusCreated)
   373  		return c.JSON(createdTest)
   374  	}
   375  }
   376  
   377  // UpdateTestHandler updates an existing test CR based on test content
   378  func (s TestkubeAPI) UpdateTestHandler() fiber.Handler {
   379  	return func(c *fiber.Ctx) error {
   380  		errPrefix := "failed to update test"
   381  		var request testkube.TestUpdateRequest
   382  		if string(c.Request().Header.ContentType()) == mediaTypeYAML {
   383  			var test testsv3.Test
   384  			testSpec := string(c.Body())
   385  			decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSpec), len(testSpec))
   386  			if err := decoder.Decode(&test); err != nil {
   387  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err))
   388  			}
   389  
   390  			request = testsmapper.MapSpecToUpdate(&test)
   391  		} else {
   392  			err := c.BodyParser(&request)
   393  			if err != nil {
   394  				return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json request: %w", errPrefix, err))
   395  			}
   396  		}
   397  
   398  		var name string
   399  		if request.Name != nil {
   400  			name = *request.Name
   401  			errPrefix = errPrefix + " " + name
   402  		}
   403  
   404  		err := testkube.ValidateUpdateTestRequest(request)
   405  		if err != nil {
   406  			return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: invalid test: %w", errPrefix, err))
   407  		}
   408  
   409  		// we need to get resource first and load its metadata.ResourceVersion
   410  		test, err := s.TestsClient.Get(name)
   411  		if err != nil {
   412  			if errors.IsNotFound(err) {
   413  				return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: client could not find test: %w", errPrefix, err))
   414  			}
   415  
   416  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not get test: %w", errPrefix, err))
   417  		}
   418  
   419  		// map update test but load spec only to not override metadata.ResourceVersion
   420  		testSpec := testsmapper.MapUpdateToSpec(request, test)
   421  
   422  		s.Log.Infow("updating test", "request", request)
   423  
   424  		var option *tests.Option
   425  		if request.Content != nil && (*request.Content) != nil && (*request.Content).Repository != nil && *(*request.Content).Repository != nil {
   426  			username := (*(*request.Content).Repository).Username
   427  			token := (*(*request.Content).Repository).Token
   428  			if (username != nil || token != nil) && !s.disableSecretCreation {
   429  				data, err := s.SecretClient.Get(secret.GetMetadataName(name, client.SecretTest))
   430  				if err != nil && !errors.IsNotFound(err) {
   431  					return s.Error(c, http.StatusBadGateway, err)
   432  				}
   433  
   434  				option = &tests.Option{Secrets: updateTestSecretsData(data, username, token)}
   435  			}
   436  		}
   437  
   438  		if isRepositoryEmpty(testSpec.Spec) {
   439  			testSpec.Spec.Content.Repository = nil
   440  		}
   441  
   442  		var updatedTest *testsv3.Test
   443  		if option != nil {
   444  			updatedTest, err = s.TestsClient.Update(testSpec, s.disableSecretCreation, *option)
   445  		} else {
   446  			updatedTest, err = s.TestsClient.Update(testSpec, s.disableSecretCreation)
   447  		}
   448  
   449  		s.Metrics.IncUpdateTest(testSpec.Spec.Type_, err)
   450  
   451  		if err != nil {
   452  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not update test %w", errPrefix, err))
   453  		}
   454  
   455  		return c.JSON(updatedTest)
   456  	}
   457  }
   458  
   459  func isRepositoryEmpty(s testsv3.TestSpec) bool {
   460  	return s.Content != nil &&
   461  		s.Content.Repository != nil &&
   462  		s.Content.Repository.Type_ == "" &&
   463  		s.Content.Repository.Uri == "" &&
   464  		s.Content.Repository.Branch == "" &&
   465  		s.Content.Repository.Path == "" &&
   466  		s.Content.Repository.Commit == "" &&
   467  		s.Content.Repository.WorkingDir == ""
   468  }
   469  
   470  // DeleteTestHandler is a method for deleting a test with id
   471  func (s TestkubeAPI) DeleteTestHandler() fiber.Handler {
   472  	return func(c *fiber.Ctx) error {
   473  		name := c.Params("id")
   474  		if name == "" {
   475  			return s.Error(c, http.StatusBadRequest, fmt.Errorf("failed to delete test: id cannot be empty"))
   476  		}
   477  		errPrefix := fmt.Sprintf("failed to delete test %s", name)
   478  		err := s.TestsClient.Delete(name)
   479  		if err != nil {
   480  			if errors.IsNotFound(err) {
   481  				return s.Warn(c, http.StatusNotFound, fmt.Errorf("%s: client could not find test: %w", errPrefix, err))
   482  			}
   483  
   484  			if _, ok := err.(*testsclientv3.DeleteDependenciesError); ok {
   485  				return s.Warn(c, http.StatusInternalServerError, fmt.Errorf("client deleted test %s but deleting test dependencies(secrets) returned errors: %w", name, err))
   486  			}
   487  
   488  			return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: client could not delete test: %w", errPrefix, err))
   489  		}
   490  
   491  		skipExecutions := c.Query("skipDeleteExecutions", "")
   492  		if skipExecutions != "true" {
   493  			// delete executions for test
   494  			if err = s.ExecutionResults.DeleteByTest(c.Context(), name); err != nil {
   495  				return s.Warn(c, http.StatusInternalServerError, fmt.Errorf("test %s was deleted but deleting test executions returned error: %w", name, err))
   496  			}
   497  		}
   498  
   499  		return c.SendStatus(http.StatusNoContent)
   500  	}
   501  }
   502  
   503  // AbortTestHandler is a method for aborting a executions of a test with id
   504  func (s TestkubeAPI) AbortTestHandler() fiber.Handler {
   505  	return func(c *fiber.Ctx) error {
   506  		ctx := c.Context()
   507  		name := c.Params("id")
   508  		if name == "" {
   509  			return s.Error(c, http.StatusBadRequest, fmt.Errorf("failed to abort test: id cannot be empty"))
   510  		}
   511  		errPrefix := fmt.Sprintf("failed to abort test %s", name)
   512  		filter := result.NewExecutionsFilter().WithTestName(name).WithStatus(string(testkube.RUNNING_ExecutionStatus))
   513  		executions, err := s.ExecutionResults.GetExecutions(ctx, filter)
   514  		if err != nil {
   515  			if err == mongo.ErrNoDocuments {
   516  				return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: executions with name %s not found", errPrefix, name))
   517  			}
   518  			return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not get executions: %w", errPrefix, err))
   519  		}
   520  
   521  		var results []testkube.ExecutionResult
   522  		for _, execution := range executions {
   523  			res, errAbort := s.Executor.Abort(ctx, &execution)
   524  			if errAbort != nil {
   525  				s.Log.Errorw("aborting execution failed", "execution", execution, "error", errAbort)
   526  				err = errAbort
   527  			}
   528  			s.Metrics.IncAbortTest(execution.TestType, res.IsFailed())
   529  			results = append(results, *res)
   530  		}
   531  
   532  		return c.JSON(results)
   533  	}
   534  }
   535  
   536  // DeleteTestsHandler for deleting all tests
   537  func (s TestkubeAPI) DeleteTestsHandler() fiber.Handler {
   538  	return func(c *fiber.Ctx) error {
   539  		errPrefix := "failed to delete tests"
   540  		var err error
   541  		var testNames []string
   542  		selector := c.Query("selector")
   543  		if selector == "" {
   544  			err = s.TestsClient.DeleteAll()
   545  		} else {
   546  			var testList *testsv3.TestList
   547  			testList, err = s.TestsClient.List(selector)
   548  			if err != nil {
   549  				if !errors.IsNotFound(err) {
   550  					return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not list tests: %w", errPrefix, err))
   551  				}
   552  			} else {
   553  				for _, item := range testList.Items {
   554  					testNames = append(testNames, item.Name)
   555  				}
   556  			}
   557  
   558  			err = s.TestsClient.DeleteByLabels(selector)
   559  		}
   560  
   561  		if err != nil {
   562  			if errors.IsNotFound(err) {
   563  				return s.Warn(c, http.StatusNotFound, fmt.Errorf("%s: could not find tests: %w", errPrefix, err))
   564  			}
   565  
   566  			return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: could not delete tests: %w", errPrefix, err))
   567  		}
   568  
   569  		// delete all executions for tests
   570  		if selector == "" {
   571  			err = s.ExecutionResults.DeleteAll(c.Context())
   572  		} else {
   573  			err = s.ExecutionResults.DeleteByTests(c.Context(), testNames)
   574  		}
   575  
   576  		if err != nil {
   577  			return s.Error(c, http.StatusInternalServerError, fmt.Errorf("%s: could not delete executions: %w", errPrefix, err))
   578  		}
   579  
   580  		return c.SendStatus(http.StatusNoContent)
   581  	}
   582  }
   583  
   584  func createTestSecretsData(username, token string) map[string]string {
   585  	if username == "" && token == "" {
   586  		return nil
   587  	}
   588  
   589  	data := make(map[string]string, 0)
   590  	if username != "" {
   591  		data[client.GitUsernameSecretName] = username
   592  	}
   593  
   594  	if token != "" {
   595  		data[client.GitTokenSecretName] = token
   596  	}
   597  
   598  	return data
   599  }
   600  
   601  func updateTestSecretsData(data map[string]string, username, token *string) map[string]string {
   602  	if data == nil {
   603  		data = make(map[string]string)
   604  	}
   605  
   606  	if username != nil {
   607  		if *username == "" {
   608  			delete(data, client.GitUsernameSecretName)
   609  		} else {
   610  			data[client.GitUsernameSecretName] = *username
   611  		}
   612  	}
   613  
   614  	if token != nil {
   615  		if *token == "" {
   616  			delete(data, client.GitTokenSecretName)
   617  		} else {
   618  			data[client.GitTokenSecretName] = *token
   619  		}
   620  	}
   621  
   622  	return data
   623  }