Files
grafana/apps/provisioning/pkg/repository/factory_test.go
Roberto Jiménez Sánchez acbc2cf01a Provisioning: Configurable Repository Types in monolith and operators (#110822)
* Configurable repository types in monolith and operator

* Default to Github in operators

* Regenerate wire

* Fix and implement unit tests

* Same types for enterprise tests

* Remove unnecessary conversion

* Remove the issue with import cycles
2025-09-09 19:13:22 +02:00

628 lines
19 KiB
Go

package repository
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
func TestNewFactory(t *testing.T) {
t.Run("creates factory with empty extras", func(t *testing.T) {
enabled := map[provisioning.RepositoryType]struct{}{}
factory, err := ProvideFactory(enabled, []Extra{})
require.NoError(t, err)
require.NotNil(t, factory)
types := factory.Types()
assert.Empty(t, types)
})
t.Run("creates factory with multiple extras", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
githubExtra := &MockExtra{}
githubExtra.On("Type").Return(provisioning.GitHubRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
provisioning.GitHubRepositoryType: {},
}
extras := []Extra{localExtra, gitExtra, githubExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
require.NotNil(t, factory)
types := factory.Types()
assert.Len(t, types, 3)
// Verify stable ordering - types should be sorted alphabetically
expectedTypes := []provisioning.RepositoryType{
provisioning.GitRepositoryType,
provisioning.GitHubRepositoryType,
provisioning.LocalRepositoryType,
}
assert.Equal(t, expectedTypes, types)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
githubExtra.AssertExpectations(t)
})
t.Run("returns error for duplicate repository types", func(t *testing.T) {
firstExtra := &MockExtra{}
firstExtra.On("Type").Return(provisioning.LocalRepositoryType)
secondExtra := &MockExtra{}
secondExtra.On("Type").Return(provisioning.LocalRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
extras := []Extra{firstExtra, secondExtra}
factory, err := ProvideFactory(enabled, extras)
assert.Error(t, err)
assert.Nil(t, factory)
assert.Contains(t, err.Error(), "repository type \"local\" is already registered")
firstExtra.AssertExpectations(t)
secondExtra.AssertExpectations(t)
})
t.Run("returns error for duplicate among multiple different types", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
duplicateGitExtra := &MockExtra{}
duplicateGitExtra.On("Type").Return(provisioning.GitRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
}
extras := []Extra{localExtra, gitExtra, duplicateGitExtra}
factory, err := ProvideFactory(enabled, extras)
assert.Error(t, err)
assert.Nil(t, factory)
assert.Contains(t, err.Error(), "repository type \"git\" is already registered")
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
duplicateGitExtra.AssertExpectations(t)
})
t.Run("handles nil extras slice", func(t *testing.T) {
enabled := map[provisioning.RepositoryType]struct{}{}
factory, err := ProvideFactory(enabled, nil)
require.NoError(t, err)
require.NotNil(t, factory)
types := factory.Types()
assert.Empty(t, types)
})
}
func TestFactory_Types(t *testing.T) {
t.Run("returns empty slice for factory with no extras", func(t *testing.T) {
enabled := map[provisioning.RepositoryType]struct{}{}
factory, err := ProvideFactory(enabled, []Extra{})
require.NoError(t, err)
types := factory.Types()
assert.Empty(t, types)
})
t.Run("returns all registered repository types in stable order", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
githubExtra := &MockExtra{}
githubExtra.On("Type").Return(provisioning.GitHubRepositoryType)
bitbucketExtra := &MockExtra{}
bitbucketExtra.On("Type").Return(provisioning.BitbucketRepositoryType)
gitlabExtra := &MockExtra{}
gitlabExtra.On("Type").Return(provisioning.GitLabRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
provisioning.GitHubRepositoryType: {},
provisioning.BitbucketRepositoryType: {},
provisioning.GitLabRepositoryType: {},
}
extras := []Extra{localExtra, gitExtra, githubExtra, bitbucketExtra, gitlabExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
types := factory.Types()
assert.Len(t, types, 5)
// Verify stable ordering - types should be sorted alphabetically
expectedTypes := []provisioning.RepositoryType{
provisioning.BitbucketRepositoryType,
provisioning.GitRepositoryType,
provisioning.GitHubRepositoryType,
provisioning.GitLabRepositoryType,
provisioning.LocalRepositoryType,
}
assert.Equal(t, expectedTypes, types)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
githubExtra.AssertExpectations(t)
bitbucketExtra.AssertExpectations(t)
gitlabExtra.AssertExpectations(t)
})
t.Run("returns consistent order across multiple calls", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
githubExtra := &MockExtra{}
githubExtra.On("Type").Return(provisioning.GitHubRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
provisioning.GitHubRepositoryType: {},
}
extras := []Extra{githubExtra, localExtra, gitExtra} // Intentionally unordered
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
types1 := factory.Types()
types2 := factory.Types()
types3 := factory.Types()
// All calls should return the same order
assert.Equal(t, types1, types2)
assert.Equal(t, types2, types3)
// Verify the order is alphabetical
expectedTypes := []provisioning.RepositoryType{
provisioning.GitRepositoryType,
provisioning.GitHubRepositoryType,
provisioning.LocalRepositoryType,
}
assert.Equal(t, expectedTypes, types1)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
githubExtra.AssertExpectations(t)
})
}
func TestFactory_Build(t *testing.T) {
t.Run("successfully builds repository with matching type", func(t *testing.T) {
expectedRepo := &MockConfigRepository{}
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
localExtra.On("Build", mock.Anything, mock.Anything).Return(expectedRepo, nil)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{localExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.LocalRepositoryType,
},
}
result, err := factory.Build(ctx, repoConfig)
require.NoError(t, err)
assert.Equal(t, expectedRepo, result)
localExtra.AssertExpectations(t)
})
t.Run("returns error for unsupported repository type", func(t *testing.T) {
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.GitRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{gitExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.LocalRepositoryType, // Different from registered type
},
}
result, err := factory.Build(ctx, repoConfig)
assert.Error(t, err)
assert.Nil(t, result)
assert.Contains(t, err.Error(), "repository type \"local\" is not supported")
gitExtra.AssertNotCalled(t, "Build")
gitExtra.AssertExpectations(t)
})
t.Run("propagates error from extra.Build", func(t *testing.T) {
expectedError := errors.New("build failed")
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
localExtra.On("Build", mock.Anything, mock.Anything).Return(nil, expectedError)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{localExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.LocalRepositoryType,
},
}
result, err := factory.Build(ctx, repoConfig)
assert.Error(t, err)
assert.Equal(t, expectedError, err)
assert.Nil(t, result)
localExtra.AssertExpectations(t)
})
t.Run("finds correct extra among multiple", func(t *testing.T) {
gitRepo := &MockConfigRepository{}
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
gitExtra.On("Build", mock.Anything, mock.Anything).Return(gitRepo, nil)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{localExtra, gitExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.GitRepositoryType,
},
}
result, err := factory.Build(ctx, repoConfig)
require.NoError(t, err)
assert.Equal(t, gitRepo, result)
localExtra.AssertNotCalled(t, "Build") // Should not be called
gitExtra.AssertExpectations(t) // Should be called
localExtra.AssertExpectations(t)
})
t.Run("handles empty repository type", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{localExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: "", // Empty type
},
}
result, err := factory.Build(ctx, repoConfig)
assert.Error(t, err)
assert.Nil(t, result)
assert.Contains(t, err.Error(), "repository type \"\" is not supported")
localExtra.AssertNotCalled(t, "Build")
localExtra.AssertExpectations(t)
})
t.Run("passes context correctly to extra.Build", func(t *testing.T) {
localRepo := &MockConfigRepository{}
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
// Create context with value to verify it's passed through
type testKey string
ctx := context.WithValue(context.Background(), testKey("test"), "value")
// Use a custom matcher to verify the context is passed correctly
localExtra.On("Build", mock.MatchedBy(func(c context.Context) bool {
return c.Value(testKey("test")) == "value"
}), mock.Anything).Return(localRepo, nil)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{localExtra})
require.NoError(t, err)
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.LocalRepositoryType,
},
}
_, err = factory.Build(ctx, repoConfig)
require.NoError(t, err)
localExtra.AssertExpectations(t)
})
}
func TestFactory_Types_EnabledFiltering(t *testing.T) {
t.Run("returns only enabled types", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
githubExtra := &MockExtra{}
githubExtra.On("Type").Return(provisioning.GitHubRepositoryType)
// Only enable local and github, not git
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitHubRepositoryType: {},
}
extras := []Extra{localExtra, gitExtra, githubExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
types := factory.Types()
assert.Len(t, types, 2)
expectedTypes := []provisioning.RepositoryType{
provisioning.GitHubRepositoryType,
provisioning.LocalRepositoryType,
}
assert.Equal(t, expectedTypes, types)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
githubExtra.AssertExpectations(t)
})
t.Run("returns empty when no types enabled", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
// Nothing enabled
enabled := map[provisioning.RepositoryType]struct{}{}
extras := []Extra{localExtra, gitExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
types := factory.Types()
assert.Empty(t, types)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
})
t.Run("ignores enabled types without corresponding extras", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
// Enable local and git, but only have local extra
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
}
extras := []Extra{localExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
types := factory.Types()
// Should only return local since git extra doesn't exist
assert.Len(t, types, 1)
assert.Equal(t, []provisioning.RepositoryType{provisioning.LocalRepositoryType}, types)
localExtra.AssertExpectations(t)
})
}
func TestProvideFactory_EnabledParameter(t *testing.T) {
t.Run("creates factory with specific enabled types", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
githubExtra := &MockExtra{}
githubExtra.On("Type").Return(provisioning.GitHubRepositoryType)
// Only enable local and git, not github
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
}
extras := []Extra{localExtra, gitExtra, githubExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
require.NotNil(t, factory)
types := factory.Types()
// Should only return enabled types
assert.Len(t, types, 2)
expectedTypes := []provisioning.RepositoryType{
provisioning.GitRepositoryType,
provisioning.LocalRepositoryType,
}
assert.Equal(t, expectedTypes, types)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
githubExtra.AssertExpectations(t)
})
t.Run("creates factory with no enabled types", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
gitExtra := &MockExtra{}
gitExtra.On("Type").Return(provisioning.GitRepositoryType)
// Enable nothing
enabled := map[provisioning.RepositoryType]struct{}{}
extras := []Extra{localExtra, gitExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
require.NotNil(t, factory)
types := factory.Types()
assert.Empty(t, types)
localExtra.AssertExpectations(t)
gitExtra.AssertExpectations(t)
})
t.Run("enabled types without extras are ignored", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
// Enable both local and git, but only provide local extra
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
provisioning.GitRepositoryType: {},
}
extras := []Extra{localExtra}
factory, err := ProvideFactory(enabled, extras)
require.NoError(t, err)
require.NotNil(t, factory)
types := factory.Types()
// Should only return local since git extra doesn't exist
assert.Len(t, types, 1)
assert.Equal(t, []provisioning.RepositoryType{provisioning.LocalRepositoryType}, types)
localExtra.AssertExpectations(t)
})
t.Run("still errors for duplicate repository types", func(t *testing.T) {
// Create duplicate extras to trigger error
firstExtra := &MockExtra{}
firstExtra.On("Type").Return(provisioning.LocalRepositoryType)
secondExtra := &MockExtra{}
secondExtra.On("Type").Return(provisioning.LocalRepositoryType)
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
extras := []Extra{firstExtra, secondExtra}
factory, err := ProvideFactory(enabled, extras)
assert.Error(t, err)
assert.Nil(t, factory)
assert.Contains(t, err.Error(), "repository type \"local\" is already registered")
firstExtra.AssertExpectations(t)
secondExtra.AssertExpectations(t)
})
t.Run("build fails for disabled repository type", func(t *testing.T) {
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
// Create factory with local extra but don't enable it
enabled := map[provisioning.RepositoryType]struct{}{}
factory, err := ProvideFactory(enabled, []Extra{localExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.LocalRepositoryType,
},
}
result, err := factory.Build(ctx, repoConfig)
assert.Error(t, err)
assert.Nil(t, result)
assert.Contains(t, err.Error(), "repository type \"local\" is not enabled")
localExtra.AssertNotCalled(t, "Build")
localExtra.AssertExpectations(t)
})
t.Run("build succeeds for enabled repository type", func(t *testing.T) {
expectedRepo := &MockConfigRepository{}
localExtra := &MockExtra{}
localExtra.On("Type").Return(provisioning.LocalRepositoryType)
localExtra.On("Build", mock.Anything, mock.Anything).Return(expectedRepo, nil)
// Create factory with local extra and enable it
enabled := map[provisioning.RepositoryType]struct{}{
provisioning.LocalRepositoryType: {},
}
factory, err := ProvideFactory(enabled, []Extra{localExtra})
require.NoError(t, err)
ctx := context.Background()
repoConfig := &provisioning.Repository{
Spec: provisioning.RepositorySpec{
Type: provisioning.LocalRepositoryType,
},
}
result, err := factory.Build(ctx, repoConfig)
require.NoError(t, err)
assert.Equal(t, expectedRepo, result)
localExtra.AssertExpectations(t)
})
}