Provisioning: Refactor to combine validation and test endpoint logic (#111965)
Provisioning: Refactor test endpoint
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
// SimpleRepositoryTester will validate the repository configuration, and then proceed to test the connection to the repository
|
||||
type SimpleRepositoryTester struct {
|
||||
validator RepositoryValidator
|
||||
}
|
||||
|
||||
func NewSimpleRepositoryTester(validator RepositoryValidator) SimpleRepositoryTester {
|
||||
return SimpleRepositoryTester{
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepository validates the repository and then runs a health check
|
||||
func (t *SimpleRepositoryTester) TestRepository(ctx context.Context, repo Repository) (*provisioning.TestResults, error) {
|
||||
errors := t.validator.ValidateRepository(repo)
|
||||
if len(errors) > 0 {
|
||||
rsp := &provisioning.TestResults{
|
||||
Code: http.StatusUnprocessableEntity, // Invalid
|
||||
Success: false,
|
||||
Errors: make([]provisioning.ErrorDetails, len(errors)),
|
||||
}
|
||||
for i, err := range errors {
|
||||
rsp.Errors[i] = provisioning.ErrorDetails{
|
||||
Type: metav1.CauseType(err.Type),
|
||||
Field: err.Field,
|
||||
Detail: err.Detail,
|
||||
}
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
return repo.Test(ctx)
|
||||
}
|
||||
|
||||
type VerifyAgainstExistingRepositories func(ctx context.Context, cfg *provisioning.Repository) *field.Error // defined this way to prevent an import cycle
|
||||
|
||||
// RepositoryTesterWithExistingChecker will validate the repository configuration, run a health check, and then compare it against existing repositories
|
||||
type RepositoryTesterWithExistingChecker struct {
|
||||
tester SimpleRepositoryTester
|
||||
verify VerifyAgainstExistingRepositories
|
||||
}
|
||||
|
||||
func NewRepositoryTesterWithExistingChecker(tester SimpleRepositoryTester, verify VerifyAgainstExistingRepositories) RepositoryTesterWithExistingChecker {
|
||||
return RepositoryTesterWithExistingChecker{
|
||||
tester: tester,
|
||||
verify: verify,
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepositoryAndCheckExisting validates the repository, runs a health check, and then compares it against existing repositories
|
||||
func (c *RepositoryTesterWithExistingChecker) TestRepositoryAndCheckExisting(ctx context.Context, repo Repository) (*provisioning.TestResults, error) {
|
||||
rsp, err := c.tester.TestRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rsp.Success {
|
||||
cfg := repo.Config()
|
||||
if validationErr := c.verify(ctx, cfg); validationErr != nil {
|
||||
rsp = &provisioning.TestResults{
|
||||
Success: false,
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseType(validationErr.Type),
|
||||
Field: validationErr.Field,
|
||||
Detail: validationErr.Detail,
|
||||
}},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
func TestTestRepository(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repository *MockRepository
|
||||
expectedCode int
|
||||
expectedErrs []provisioning.ErrorDetails
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "validation fails",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
// Missing required title
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
return m
|
||||
}(),
|
||||
expectedCode: http.StatusUnprocessableEntity,
|
||||
expectedErrs: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueRequired,
|
||||
Field: "spec.title",
|
||||
Detail: "a repository title must be given",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "test passes",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
m.On("Test", mock.Anything).Return(&provisioning.TestResults{
|
||||
Code: http.StatusOK,
|
||||
Success: true,
|
||||
}, nil)
|
||||
return m
|
||||
}(),
|
||||
expectedCode: http.StatusOK,
|
||||
expectedErrs: nil,
|
||||
},
|
||||
{
|
||||
name: "test fails with error",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
m.On("Test", mock.Anything).Return(nil, fmt.Errorf("test error"))
|
||||
return m
|
||||
}(),
|
||||
expectedError: fmt.Errorf("test error"),
|
||||
},
|
||||
{
|
||||
name: "test fails with results",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
m.On("Test", mock.Anything).Return(&provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: "spec.property",
|
||||
}},
|
||||
}, nil)
|
||||
return m
|
||||
}(),
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedErrs: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: "spec.property",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
tester := NewSimpleRepositoryTester(NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder, provisioning.SyncTargetTypeInstance}, true))
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results, err := tester.TestRepository(context.Background(), tt.repository)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tt.expectedError.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, results)
|
||||
require.Equal(t, tt.expectedCode, results.Code)
|
||||
|
||||
if tt.expectedErrs != nil {
|
||||
require.Equal(t, tt.expectedErrs, results.Errors)
|
||||
require.False(t, results.Success)
|
||||
} else {
|
||||
require.True(t, results.Success)
|
||||
require.Empty(t, results.Errors)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTester_TestRepository(t *testing.T) {
|
||||
repository := NewMockRepository(t)
|
||||
repository.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
repository.On("Validate").Return(field.ErrorList{})
|
||||
repository.On("Test", mock.Anything).Return(&provisioning.TestResults{
|
||||
Code: http.StatusOK,
|
||||
Success: true,
|
||||
}, nil)
|
||||
|
||||
tester := NewSimpleRepositoryTester(NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder, provisioning.SyncTargetTypeInstance}, true))
|
||||
results, err := tester.TestRepository(context.Background(), repository)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, results)
|
||||
require.Equal(t, http.StatusOK, results.Code)
|
||||
require.True(t, results.Success)
|
||||
}
|
||||
|
||||
func TestFromFieldError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fieldError *field.Error
|
||||
expectedCode int
|
||||
expectedField string
|
||||
expectedType metav1.CauseType
|
||||
expectedDetail string
|
||||
}{
|
||||
{
|
||||
name: "required field error",
|
||||
fieldError: &field.Error{
|
||||
Type: field.ErrorTypeRequired,
|
||||
Field: "spec.title",
|
||||
Detail: "a repository title must be given",
|
||||
},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedField: "spec.title",
|
||||
expectedType: metav1.CauseTypeFieldValueRequired,
|
||||
expectedDetail: "a repository title must be given",
|
||||
},
|
||||
{
|
||||
name: "not supported field error",
|
||||
fieldError: &field.Error{
|
||||
Type: field.ErrorTypeNotSupported,
|
||||
Field: "spec.workflow",
|
||||
Detail: "branch is only supported on git repositories",
|
||||
},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedField: "spec.workflow",
|
||||
expectedType: metav1.CauseTypeFieldValueNotSupported,
|
||||
expectedDetail: "branch is only supported on git repositories",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := FromFieldError(tt.fieldError)
|
||||
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, tt.expectedCode, result.Code)
|
||||
require.False(t, result.Success)
|
||||
require.Len(t, result.Errors, 1)
|
||||
|
||||
errorDetail := result.Errors[0]
|
||||
require.Equal(t, tt.expectedField, errorDetail.Field)
|
||||
require.Equal(t, tt.expectedType, errorDetail.Type)
|
||||
require.Equal(t, tt.expectedDetail, errorDetail.Detail)
|
||||
})
|
||||
}
|
||||
}
|
||||
+41
-51
@@ -1,10 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
@@ -12,57 +12,27 @@ import (
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
// RepositoryValidator interface for validating repositories against existing ones
|
||||
type RepositoryValidator interface {
|
||||
VerifyAgainstExistingRepositories(ctx context.Context, cfg *provisioning.Repository) *field.Error
|
||||
type RepositoryValidator struct {
|
||||
allowedTargets []provisioning.SyncTargetType
|
||||
allowImageRendering bool
|
||||
minSyncInterval time.Duration
|
||||
}
|
||||
|
||||
func TestRepository(ctx context.Context, repo Repository) (*provisioning.TestResults, error) {
|
||||
return TestRepositoryWithValidator(ctx, repo, nil)
|
||||
func NewValidator(minSyncInterval time.Duration, allowedTargets []provisioning.SyncTargetType, allowImageRendering bool) RepositoryValidator {
|
||||
// do not allow minsync interval to be less than 10
|
||||
if minSyncInterval <= 10*time.Second {
|
||||
minSyncInterval = 10 * time.Second
|
||||
}
|
||||
|
||||
return RepositoryValidator{
|
||||
allowedTargets: allowedTargets,
|
||||
allowImageRendering: allowImageRendering,
|
||||
minSyncInterval: minSyncInterval,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryWithValidator(ctx context.Context, repo Repository, validator RepositoryValidator) (*provisioning.TestResults, error) {
|
||||
errors := ValidateRepository(repo)
|
||||
if len(errors) > 0 {
|
||||
rsp := &provisioning.TestResults{
|
||||
Code: http.StatusUnprocessableEntity, // Invalid
|
||||
Success: false,
|
||||
Errors: make([]provisioning.ErrorDetails, len(errors)),
|
||||
}
|
||||
for i, err := range errors {
|
||||
rsp.Errors[i] = provisioning.ErrorDetails{
|
||||
Type: metav1.CauseType(err.Type),
|
||||
Field: err.Field,
|
||||
Detail: err.Detail,
|
||||
}
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
rsp, err := repo.Test(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rsp.Success && validator != nil {
|
||||
cfg := repo.Config()
|
||||
if validationErr := validator.VerifyAgainstExistingRepositories(ctx, cfg); validationErr != nil {
|
||||
rsp = &provisioning.TestResults{
|
||||
Success: false,
|
||||
Code: http.StatusUnprocessableEntity,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseType(validationErr.Type),
|
||||
Field: validationErr.Field,
|
||||
Detail: validationErr.Detail,
|
||||
}},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
func ValidateRepository(repo Repository) field.ErrorList {
|
||||
// ValidateRepository solely does configuration checks on the repository object. It does not run a health check or compare against existing repositories.
|
||||
func (v *RepositoryValidator) ValidateRepository(repo Repository) field.ErrorList {
|
||||
list := repo.Validate()
|
||||
cfg := repo.Config()
|
||||
|
||||
@@ -70,9 +40,22 @@ func ValidateRepository(repo Repository) field.ErrorList {
|
||||
list = append(list, field.Required(field.NewPath("spec", "title"), "a repository title must be given"))
|
||||
}
|
||||
|
||||
if cfg.Spec.Sync.Enabled && cfg.Spec.Sync.Target == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "sync", "target"),
|
||||
"The target type is required when sync is enabled"))
|
||||
if cfg.Spec.Sync.Enabled {
|
||||
if cfg.Spec.Sync.Target == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "sync", "target"),
|
||||
"The target type is required when sync is enabled"))
|
||||
} else if !slices.Contains(v.allowedTargets, cfg.Spec.Sync.Target) {
|
||||
list = append(list,
|
||||
field.Invalid(
|
||||
field.NewPath("spec", "target"),
|
||||
cfg.Spec.Sync.Target,
|
||||
"sync target is not supported"))
|
||||
}
|
||||
|
||||
if cfg.Spec.Sync.IntervalSeconds < int64(v.minSyncInterval.Seconds()) {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "sync", "intervalSeconds"),
|
||||
cfg.Spec.Sync.IntervalSeconds, fmt.Sprintf("Interval must be at least %d seconds", int64(v.minSyncInterval.Seconds()))))
|
||||
}
|
||||
}
|
||||
|
||||
// Reserved names (for now)
|
||||
@@ -131,6 +114,13 @@ func ValidateRepository(repo Repository) field.ErrorList {
|
||||
}
|
||||
}
|
||||
|
||||
if !v.allowImageRendering && cfg.Spec.GitHub != nil && cfg.Spec.GitHub.GenerateDashboardPreviews {
|
||||
list = append(list,
|
||||
field.Invalid(field.NewPath("spec", "generateDashboardPreviews"),
|
||||
cfg.Spec.GitHub.GenerateDashboardPreviews,
|
||||
"image rendering is not enabled"))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
+49
-192
@@ -1,12 +1,9 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
@@ -74,6 +71,28 @@ func TestValidateRepository(t *testing.T) {
|
||||
require.Contains(t, errors.ToAggregate().Error(), "spec.sync.target: Required value")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sync interval too low",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
Sync: provisioning.SyncOptions{
|
||||
Enabled: true,
|
||||
Target: provisioning.SyncTargetTypeFolder,
|
||||
IntervalSeconds: 5,
|
||||
},
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
return m
|
||||
}(),
|
||||
expectedErrs: 1,
|
||||
validateError: func(t *testing.T, errors field.ErrorList) {
|
||||
require.Contains(t, errors.ToAggregate().Error(), "spec.sync.intervalSeconds: Invalid value")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reserved name",
|
||||
repository: func() *MockRepository {
|
||||
@@ -132,6 +151,27 @@ func TestValidateRepository(t *testing.T) {
|
||||
require.Contains(t, errors.ToAggregate().Error(), "spec.github: Invalid value")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github enabled when image rendering is not allowed",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
Type: provisioning.GitHubRepositoryType,
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
GenerateDashboardPreviews: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
return m
|
||||
}(),
|
||||
expectedErrs: 1,
|
||||
validateError: func(t *testing.T, errors field.ErrorList) {
|
||||
require.Contains(t, errors.ToAggregate().Error(), "spec.generateDashboardPreviews: Invalid value")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mismatched git config",
|
||||
repository: func() *MockRepository {
|
||||
@@ -163,16 +203,18 @@ func TestValidateRepository(t *testing.T) {
|
||||
Sync: provisioning.SyncOptions{
|
||||
Enabled: true,
|
||||
IntervalSeconds: 5,
|
||||
Target: provisioning.SyncTargetTypeInstance,
|
||||
},
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
return m
|
||||
}(),
|
||||
expectedErrs: 3,
|
||||
expectedErrs: 4,
|
||||
// 1. missing title
|
||||
// 2. sync target missing
|
||||
// 3. reserved name
|
||||
// 4. sync target not supported
|
||||
},
|
||||
{
|
||||
name: "branch workflow for non-github repository",
|
||||
@@ -258,9 +300,10 @@ func TestValidateRepository(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
validator := NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder}, false)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
errors := ValidateRepository(tt.repository)
|
||||
errors := validator.ValidateRepository(tt.repository)
|
||||
require.Len(t, errors, tt.expectedErrs)
|
||||
if tt.validateError != nil {
|
||||
tt.validateError(t, errors)
|
||||
@@ -268,189 +311,3 @@ func TestValidateRepository(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestRepository(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repository *MockRepository
|
||||
expectedCode int
|
||||
expectedErrs []provisioning.ErrorDetails
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "validation fails",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
// Missing required title
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
return m
|
||||
}(),
|
||||
expectedCode: http.StatusUnprocessableEntity,
|
||||
expectedErrs: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueRequired,
|
||||
Field: "spec.title",
|
||||
Detail: "a repository title must be given",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "test passes",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
m.On("Test", mock.Anything).Return(&provisioning.TestResults{
|
||||
Code: http.StatusOK,
|
||||
Success: true,
|
||||
}, nil)
|
||||
return m
|
||||
}(),
|
||||
expectedCode: http.StatusOK,
|
||||
expectedErrs: nil,
|
||||
},
|
||||
{
|
||||
name: "test fails with error",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
m.On("Test", mock.Anything).Return(nil, fmt.Errorf("test error"))
|
||||
return m
|
||||
}(),
|
||||
expectedError: fmt.Errorf("test error"),
|
||||
},
|
||||
{
|
||||
name: "test fails with results",
|
||||
repository: func() *MockRepository {
|
||||
m := NewMockRepository(t)
|
||||
m.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
m.On("Validate").Return(field.ErrorList{})
|
||||
m.On("Test", mock.Anything).Return(&provisioning.TestResults{
|
||||
Code: http.StatusBadRequest,
|
||||
Success: false,
|
||||
Errors: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: "spec.property",
|
||||
}},
|
||||
}, nil)
|
||||
return m
|
||||
}(),
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedErrs: []provisioning.ErrorDetails{{
|
||||
Type: metav1.CauseTypeFieldValueInvalid,
|
||||
Field: "spec.property",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results, err := TestRepository(context.Background(), tt.repository)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tt.expectedError.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, results)
|
||||
require.Equal(t, tt.expectedCode, results.Code)
|
||||
|
||||
if tt.expectedErrs != nil {
|
||||
require.Equal(t, tt.expectedErrs, results.Errors)
|
||||
require.False(t, results.Success)
|
||||
} else {
|
||||
require.True(t, results.Success)
|
||||
require.Empty(t, results.Errors)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTester_TestRepository(t *testing.T) {
|
||||
repository := NewMockRepository(t)
|
||||
repository.On("Config").Return(&provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
Title: "Test Repo",
|
||||
},
|
||||
})
|
||||
repository.On("Validate").Return(field.ErrorList{})
|
||||
repository.On("Test", mock.Anything).Return(&provisioning.TestResults{
|
||||
Code: http.StatusOK,
|
||||
Success: true,
|
||||
}, nil)
|
||||
|
||||
results, err := TestRepository(context.Background(), repository)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, results)
|
||||
require.Equal(t, http.StatusOK, results.Code)
|
||||
require.True(t, results.Success)
|
||||
}
|
||||
|
||||
func TestFromFieldError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fieldError *field.Error
|
||||
expectedCode int
|
||||
expectedField string
|
||||
expectedType metav1.CauseType
|
||||
expectedDetail string
|
||||
}{
|
||||
{
|
||||
name: "required field error",
|
||||
fieldError: &field.Error{
|
||||
Type: field.ErrorTypeRequired,
|
||||
Field: "spec.title",
|
||||
Detail: "a repository title must be given",
|
||||
},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedField: "spec.title",
|
||||
expectedType: metav1.CauseTypeFieldValueRequired,
|
||||
expectedDetail: "a repository title must be given",
|
||||
},
|
||||
{
|
||||
name: "not supported field error",
|
||||
fieldError: &field.Error{
|
||||
Type: field.ErrorTypeNotSupported,
|
||||
Field: "spec.workflow",
|
||||
Detail: "branch is only supported on git repositories",
|
||||
},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedField: "spec.workflow",
|
||||
expectedType: metav1.CauseTypeFieldValueNotSupported,
|
||||
expectedDetail: "branch is only supported on git repositories",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := FromFieldError(tt.fieldError)
|
||||
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, tt.expectedCode, result.Code)
|
||||
require.False(t, result.Success)
|
||||
require.Len(t, result.Errors, 1)
|
||||
|
||||
errorDetail := result.Errors[0]
|
||||
require.Equal(t, tt.expectedField, errorDetail.Field)
|
||||
require.Equal(t, tt.expectedType, errorDetail.Type)
|
||||
require.Equal(t, tt.expectedDetail, errorDetail.Detail)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user