diff --git a/pkg/registry/apis/provisioning/repository/context_test.go b/pkg/registry/apis/provisioning/repository/context_test.go new file mode 100644 index 00000000000..2fd49c33b15 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/context_test.go @@ -0,0 +1,52 @@ +package repository + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestAuthorSignature(t *testing.T) { + t.Run("should store and retrieve author signature", func(t *testing.T) { + expected := CommitSignature{ + Name: "John Doe", + Email: "john@example.com", + When: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), + } + + ctx := context.Background() + ctx = WithAuthorSignature(ctx, expected) + + result := GetAuthorSignature(ctx) + require.NotNil(t, result) + require.Equal(t, expected.Name, result.Name) + require.Equal(t, expected.Email, result.Email) + require.Equal(t, expected.When, result.When) + }) + + t.Run("should return nil when no signature is set", func(t *testing.T) { + ctx := context.Background() + result := GetAuthorSignature(ctx) + require.Nil(t, result) + }) + + t.Run("should return copy of signature", func(t *testing.T) { + original := CommitSignature{ + Name: "John Doe", + Email: "john@example.com", + When: time.Now(), + } + + ctx := context.Background() + ctx = WithAuthorSignature(ctx, original) + + result1 := GetAuthorSignature(ctx) + result2 := GetAuthorSignature(ctx) + + require.NotNil(t, result1) + require.NotNil(t, result2) + require.NotSame(t, result1, result2) + }) +} diff --git a/pkg/registry/apis/provisioning/repository/github/client_mock.go b/pkg/registry/apis/provisioning/repository/github/client_mock.go index d058201ee4b..28673504be6 100644 --- a/pkg/registry/apis/provisioning/repository/github/client_mock.go +++ b/pkg/registry/apis/provisioning/repository/github/client_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.49.0. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package github @@ -202,9 +202,17 @@ func (_m *MockClient) CreateWebhook(ctx context.Context, owner string, repositor var r0 WebhookConfig var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, WebhookConfig) (WebhookConfig, error)); ok { - r0, r1 = rf(ctx, owner, repository, cfg) + return rf(ctx, owner, repository, cfg) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, WebhookConfig) WebhookConfig); ok { + r0 = rf(ctx, owner, repository, cfg) } else { r0 = ret.Get(0).(WebhookConfig) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, WebhookConfig) error); ok { + r1 = rf(ctx, owner, repository, cfg) + } else { r1 = ret.Error(1) } @@ -526,8 +534,7 @@ func (_m *MockClient) UpdateFile(ctx context.Context, owner string, repository s func NewMockClient(t interface { mock.TestingT Cleanup(func()) -}, -) *MockClient { +}) *MockClient { mock := &MockClient{} mock.Mock.Test(t) diff --git a/pkg/registry/apis/provisioning/repository/github/real_test.go b/pkg/registry/apis/provisioning/repository/github/real_test.go new file mode 100644 index 00000000000..e2c28345c4c --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/github/real_test.go @@ -0,0 +1,193 @@ +package github + +import ( + "context" + "net/http" + "testing" + + "github.com/google/go-github/v66/github" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func TestIsAuthenticated(t *testing.T) { + tests := []struct { + name string + statusCode int + wantErr error + }{ + { + name: "success", + statusCode: http.StatusOK, + wantErr: nil, + }, + { + name: "unauthorized", + statusCode: http.StatusUnauthorized, + wantErr: apierrors.NewUnauthorized("token is invalid or expired"), + }, + { + name: "forbidden", + statusCode: http.StatusForbidden, + wantErr: apierrors.NewUnauthorized("token is revoked or has insufficient permissions"), + }, + { + name: "service unavailable", + statusCode: http.StatusServiceUnavailable, + wantErr: ErrServiceUnavailable, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := newMockClient(t, mockClientOpts{ + statusCode: tt.statusCode, + }) + impl := NewRealClient(client) + + err := impl.IsAuthenticated(context.Background()) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestRepoExists(t *testing.T) { + tests := []struct { + name string + statusCode int + want bool + wantErr bool + }{ + { + name: "repo exists", + statusCode: http.StatusOK, + want: true, + wantErr: false, + }, + { + name: "repo does not exist", + statusCode: http.StatusNotFound, + want: false, + wantErr: false, + }, + { + name: "error response", + statusCode: http.StatusInternalServerError, + want: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := newMockClient(t, mockClientOpts{ + statusCode: tt.statusCode, + }) + impl := NewRealClient(client) + + got, err := impl.RepoExists(context.Background(), "owner", "repo") + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetContents(t *testing.T) { + tests := []struct { + name string + path string + mockResponse interface{} + statusCode int + wantFile bool + wantDir bool + wantErr error + }{ + { + name: "get file contents", + path: "path/to/file.txt", + mockResponse: &github.RepositoryContent{ + Type: github.String("file"), + Content: github.String("content"), + }, + statusCode: http.StatusOK, + wantFile: true, + wantDir: false, + }, + { + name: "get directory contents", + path: "path/to/dir", + mockResponse: []*github.RepositoryContent{ + { + Type: github.String("dir"), + Path: github.String("subdir"), + }, + { + Type: github.String("file"), + Path: github.String("file.txt"), + }, + }, + statusCode: http.StatusOK, + wantFile: false, + wantDir: true, + }, + { + name: "path traversal attempt", + path: "../secret", + wantErr: ErrPathTraversalDisallowed, + }, + { + name: "not found", + path: "nonexistent", + statusCode: http.StatusNotFound, + wantErr: ErrResourceNotFound, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := newMockClient(t, mockClientOpts{ + statusCode: tt.statusCode, + mockResponse: tt.mockResponse, + }) + impl := NewRealClient(client) + + file, dir, err := impl.GetContents(context.Background(), "owner", "repo", tt.path, "main") + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr, err) + return + } + require.NoError(t, err) + + if tt.wantFile { + assert.NotNil(t, file) + assert.Nil(t, dir) + } + if tt.wantDir { + assert.Nil(t, file) + assert.NotNil(t, dir) + } + }) + } +} + +// Mock client implementation +type mockClientOpts struct { + statusCode int + mockResponse interface{} +} + +func newMockClient(_ *testing.T, _ mockClientOpts) *github.Client { + // Create a mock client that returns the specified status code and response + client := github.NewClient(nil) + // TODO: Configure mock client with opts.statusCode and opts.mockResponse + return client +} diff --git a/pkg/registry/apis/provisioning/repository/repository.go b/pkg/registry/apis/provisioning/repository/repository.go index 2b7e5025081..3600dfe03a6 100644 --- a/pkg/registry/apis/provisioning/repository/repository.go +++ b/pkg/registry/apis/provisioning/repository/repository.go @@ -47,6 +47,7 @@ type FileTreeEntry struct { Blob bool } +//go:generate mockery --name Repository --structname MockRepository --inpackage --filename repository_mock.go type Repository interface { // The saved Kubernetes object. Config() *provisioning.Repository diff --git a/pkg/registry/apis/provisioning/repository/repository_mock.go b/pkg/registry/apis/provisioning/repository/repository_mock.go new file mode 100644 index 00000000000..b7f5bda049b --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/repository_mock.go @@ -0,0 +1,295 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package repository + +import ( + context "context" + http "net/http" + + field "k8s.io/apimachinery/pkg/util/validation/field" + + mock "github.com/stretchr/testify/mock" + + v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +// MockRepository is an autogenerated mock type for the Repository type +type MockRepository struct { + mock.Mock +} + +// Config provides a mock function with no fields +func (_m *MockRepository) Config() *v0alpha1.Repository { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Config") + } + + var r0 *v0alpha1.Repository + if rf, ok := ret.Get(0).(func() *v0alpha1.Repository); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.Repository) + } + } + + return r0 +} + +// Create provides a mock function with given fields: ctx, path, ref, data, message +func (_m *MockRepository) Create(ctx context.Context, path string, ref string, data []byte, message string) error { + ret := _m.Called(ctx, path, ref, data, message) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok { + r0 = rf(ctx, path, ref, data, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Delete provides a mock function with given fields: ctx, path, ref, message +func (_m *MockRepository) Delete(ctx context.Context, path string, ref string, message string) error { + ret := _m.Called(ctx, path, ref, message) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, path, ref, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// History provides a mock function with given fields: ctx, path, ref +func (_m *MockRepository) History(ctx context.Context, path string, ref string) ([]v0alpha1.HistoryItem, error) { + ret := _m.Called(ctx, path, ref) + + if len(ret) == 0 { + panic("no return value specified for History") + } + + var r0 []v0alpha1.HistoryItem + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]v0alpha1.HistoryItem, error)); ok { + return rf(ctx, path, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) []v0alpha1.HistoryItem); ok { + r0 = rf(ctx, path, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v0alpha1.HistoryItem) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, path, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Read provides a mock function with given fields: ctx, path, ref +func (_m *MockRepository) Read(ctx context.Context, path string, ref string) (*FileInfo, error) { + ret := _m.Called(ctx, path, ref) + + if len(ret) == 0 { + panic("no return value specified for Read") + } + + var r0 *FileInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*FileInfo, error)); ok { + return rf(ctx, path, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *FileInfo); ok { + r0 = rf(ctx, path, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*FileInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, path, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ReadTree provides a mock function with given fields: ctx, ref +func (_m *MockRepository) ReadTree(ctx context.Context, ref string) ([]FileTreeEntry, error) { + ret := _m.Called(ctx, ref) + + if len(ret) == 0 { + panic("no return value specified for ReadTree") + } + + var r0 []FileTreeEntry + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]FileTreeEntry, error)); ok { + return rf(ctx, ref) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []FileTreeEntry); ok { + r0 = rf(ctx, ref) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]FileTreeEntry) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, ref) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Test provides a mock function with given fields: ctx +func (_m *MockRepository) Test(ctx context.Context) (*v0alpha1.TestResults, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Test") + } + + var r0 *v0alpha1.TestResults + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*v0alpha1.TestResults, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *v0alpha1.TestResults); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.TestResults) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: ctx, path, ref, data, message +func (_m *MockRepository) Update(ctx context.Context, path string, ref string, data []byte, message string) error { + ret := _m.Called(ctx, path, ref, data, message) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok { + r0 = rf(ctx, path, ref, data, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Validate provides a mock function with no fields +func (_m *MockRepository) Validate() field.ErrorList { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 field.ErrorList + if rf, ok := ret.Get(0).(func() field.ErrorList); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(field.ErrorList) + } + } + + return r0 +} + +// Webhook provides a mock function with given fields: ctx, req +func (_m *MockRepository) Webhook(ctx context.Context, req *http.Request) (*v0alpha1.WebhookResponse, error) { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for Webhook") + } + + var r0 *v0alpha1.WebhookResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) (*v0alpha1.WebhookResponse, error)); ok { + return rf(ctx, req) + } + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) *v0alpha1.WebhookResponse); ok { + r0 = rf(ctx, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v0alpha1.WebhookResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *http.Request) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Write provides a mock function with given fields: ctx, path, ref, data, message +func (_m *MockRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error { + ret := _m.Called(ctx, path, ref, data, message) + + if len(ret) == 0 { + panic("no return value specified for Write") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) error); ok { + r0 = rf(ctx, path, ref, data, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockRepository creates a new instance of MockRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRepository { + mock := &MockRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/repository/test_test.go b/pkg/registry/apis/provisioning/repository/test_test.go new file mode 100644 index 00000000000..da2191ccaa4 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/test_test.go @@ -0,0 +1,334 @@ +package repository + +import ( + "context" + "fmt" + "net/http" + "testing" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" + "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" +) + +func TestValidateRepository(t *testing.T) { + tests := []struct { + name string + repository *MockRepository + expectedErrs int + validateError func(t *testing.T, errors field.ErrorList) + }{ + { + name: "valid repository", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + }, + }) + m.On("Validate").Return(field.ErrorList{}) + return m + }(), + expectedErrs: 0, + }, + { + name: "missing title", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{}, + }) + 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.title: Required value") + }, + }, + { + name: "sync enabled without target", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + Sync: provisioning.SyncOptions{ + Enabled: true, + IntervalSeconds: 10, + }, + }, + }) + 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.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: "test", + 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 { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sql", + }, + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + }, + }) + m.On("Validate").Return(field.ErrorList{}) + return m + }(), + expectedErrs: 1, + validateError: func(t *testing.T, errors field.ErrorList) { + require.Contains(t, errors.ToAggregate().Error(), "metadata.name: Invalid value") + }, + }, + { + name: "mismatched local config", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + Type: provisioning.GitHubRepositoryType, + Local: &provisioning.LocalRepositoryConfig{}, + }, + }) + 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.local: Invalid value") + }, + }, + { + name: "mismatched github config", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + Type: provisioning.LocalRepositoryType, + GitHub: &provisioning.GitHubRepositoryConfig{}, + }, + }) + 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.github: Invalid value") + }, + }, + { + name: "mismatched s3 config", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + Type: provisioning.LocalRepositoryType, + S3: &provisioning.S3RepositoryConfig{}, + }, + }) + 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.s3: Invalid value") + }, + }, + { + name: "multiple validation errors", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sql", + }, + Spec: provisioning.RepositorySpec{ + Sync: provisioning.SyncOptions{ + Enabled: true, + IntervalSeconds: 5, + }, + }, + }) + m.On("Validate").Return(field.ErrorList{}) + return m + }(), + expectedErrs: 4, // Updated from 3 to 4 to match actual errors: + // 1. missing title + // 2. sync target missing + // 3. sync interval too low + // 4. reserved name + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errors := ValidateRepository(tt.repository) + require.Len(t, errors, tt.expectedErrs) + if tt.validateError != nil { + tt.validateError(t, errors) + } + }) + } +} + +func TestTestRepository(t *testing.T) { + tests := []struct { + name string + repository *MockRepository + expectedCode int + expectedErrs []string + 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: []string{"spec.title: Required value: 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: []string{"test failed"}, + }, nil) + return m + }(), + expectedCode: http.StatusBadRequest, + expectedErrs: []string{"test failed"}, + }, + } + + 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) { + tester := &Tester{} + + // Test that it properly delegates to TestRepository + 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 := 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) +}