diff --git a/pkg/operators/provisioning/config.go b/pkg/operators/provisioning/config.go index 296ba3cb95d..e82bc1ad49d 100644 --- a/pkg/operators/provisioning/config.go +++ b/pkg/operators/provisioning/config.go @@ -57,9 +57,11 @@ type ControllerConfig struct { decryptService decrypt.DecryptService registry prometheus.Registerer repositoryFactory repository.Factory - RepositoryFactoryFunc func() (repository.Factory, error) + repositoryExtras []repository.Extra + RepositoryExtrasFunc func() ([]repository.Extra, error) connectionFactory connection.Factory - ConnectionFactoryFunc func() (connection.Factory, error) + connectionExtras []connection.Extra + ConnectionExtrasFunc func() ([]connection.Extra, error) healthMetricsRecorder controller.HealthMetricsRecorder tracer tracing.Tracer quotaGetter quotas.QuotaGetter @@ -424,23 +426,20 @@ func (c *ControllerConfig) RepositoryFactory() (repository.Factory, error) { return c.repositoryFactory, nil } - if c.RepositoryFactoryFunc != nil { - repositoryFactory, err := c.RepositoryFactoryFunc() - if err != nil { - return nil, fmt.Errorf("failed to get repository factory: %w", err) - } - c.repositoryFactory = repositoryFactory - return repositoryFactory, nil + extras, err := c.RepositoryExtras() + if err != nil { + return nil, err } - decryptSvc, err := c.DecryptService() - if err != nil { - return nil, fmt.Errorf("setup decrypt service: %w", err) + // Build enabled types from the extras + enabledTypes := make(map[provisioning.RepositoryType]struct{}) + for _, extra := range extras { + enabledTypes[extra.Type()] = struct{}{} } - repositoryFactory, err := setupRepoFactory(c.Settings, repository.ProvideDecrypter(decryptSvc), c.provisioningClient, c.Registry()) + repositoryFactory, err := repository.ProvideFactory(enabledTypes, extras) if err != nil { - return nil, fmt.Errorf("setup repository factory: %w", err) + return nil, fmt.Errorf("create repository factory: %w", err) } c.repositoryFactory = repositoryFactory @@ -453,23 +452,20 @@ func (c *ControllerConfig) ConnectionFactory() (connection.Factory, error) { return c.connectionFactory, nil } - if c.ConnectionFactoryFunc != nil { - connectionFactory, err := c.ConnectionFactoryFunc() - if err != nil { - return nil, fmt.Errorf("failed to get connection factory: %w", err) - } - c.connectionFactory = connectionFactory - return connectionFactory, nil + extras, err := c.ConnectionExtras() + if err != nil { + return nil, err } - decryptSvc, err := c.DecryptService() - if err != nil { - return nil, fmt.Errorf("setup decrypt service: %w", err) + // Build enabled types from the extras + enabledTypes := make(map[provisioning.ConnectionType]struct{}) + for _, extra := range extras { + enabledTypes[extra.Type()] = struct{}{} } - connectionFactory, err := setupConnectionFactory(c.Settings, connection.ProvideDecrypter(decryptSvc)) + connectionFactory, err := connection.ProvideFactory(enabledTypes, extras) if err != nil { - return nil, fmt.Errorf("setup connection factory: %w", err) + return nil, fmt.Errorf("create connection factory: %w", err) } c.connectionFactory = connectionFactory @@ -508,30 +504,36 @@ func (c *ControllerConfig) URLProvider() (func(ctx context.Context, namespace st return c.urlProvider, nil } -func setupRepoFactory( - cfg *setting.Cfg, - decrypter repository.Decrypter, - _ *client.Clientset, - registry prometheus.Registerer, -) (repository.Factory, error) { - operatorSec := cfg.SectionWithEnvOverrides("operator") - provisioningSec := cfg.SectionWithEnvOverrides("provisioning") +func (c *ControllerConfig) RepositoryExtras() ([]repository.Extra, error) { + if c.repositoryExtras != nil { + return c.repositoryExtras, nil + } + + if c.RepositoryExtrasFunc != nil { + extras, err := c.RepositoryExtrasFunc() + if err != nil { + return nil, fmt.Errorf("failed to get repository extras: %w", err) + } + c.repositoryExtras = extras + return extras, nil + } + + // Default OSS implementation + decryptSvc, err := c.DecryptService() + if err != nil { + return nil, fmt.Errorf("get decrypt service: %w", err) + } + decrypter := repository.ProvideDecrypter(decryptSvc) + + operatorSec := c.Settings.SectionWithEnvOverrides("operator") + provisioningSec := c.Settings.SectionWithEnvOverrides("provisioning") repoTypes := provisioningSec.Key("repository_types").Strings("|") if len(repoTypes) == 0 { repoTypes = []string{"github"} } - // TODO: This depends on the different flavor of Grafana - // https://github.com/grafana/git-ui-sync-project/issues/495 extras := make([]repository.Extra, 0) - alreadyRegistered := make(map[provisioning.RepositoryType]struct{}) - for _, t := range repoTypes { - if _, ok := alreadyRegistered[provisioning.RepositoryType(t)]; ok { - continue - } - alreadyRegistered[provisioning.RepositoryType(t)] = struct{}{} - switch provisioning.RepositoryType(t) { case provisioning.GitRepositoryType: extras = append(extras, gitrepo.Extra(decrypter)) @@ -539,57 +541,55 @@ func setupRepoFactory( var webhook *webhooks.WebhookExtraBuilder provisioningAppURL := operatorSec.Key("provisioning_server_public_url").String() if provisioningAppURL != "" { - webhook = webhooks.ProvideWebhooks(provisioningAppURL, registry) + webhook = webhooks.ProvideWebhooks(provisioningAppURL, c.Registry()) } - extras = append(extras, githubrepo.Extra(decrypter, githubrepo.ProvideFactory(), webhook)) case provisioning.LocalRepositoryType: homePath := operatorSec.Key("home_path").String() if homePath == "" { return nil, fmt.Errorf("home_path is required in [operator] section for local repository type") } - permittedPrefixes := operatorSec.Key("local_permitted_prefixes").Strings("|") if len(permittedPrefixes) == 0 { return nil, fmt.Errorf("local_permitted_prefixes is required in [operator] section for local repository type") } - - extras = append(extras, local.Extra( - homePath, - permittedPrefixes, - )) + extras = append(extras, local.Extra(homePath, permittedPrefixes)) default: return nil, fmt.Errorf("unsupported repository type: %s", t) } } - repoFactory, err := repository.ProvideFactory(alreadyRegistered, extras) - if err != nil { - return nil, fmt.Errorf("create repository factory: %w", err) - } - - return repoFactory, nil + c.repositoryExtras = extras + return extras, nil } -func setupConnectionFactory( - cfg *setting.Cfg, - decrypter connection.Decrypter, -) (connection.Factory, error) { - // For now, only support GitHub connections - // TODO: Add support for other connection types +func (c *ControllerConfig) ConnectionExtras() ([]connection.Extra, error) { + if c.connectionExtras != nil { + return c.connectionExtras, nil + } + + if c.ConnectionExtrasFunc != nil { + extras, err := c.ConnectionExtrasFunc() + if err != nil { + return nil, fmt.Errorf("failed to get connection extras: %w", err) + } + c.connectionExtras = extras + return extras, nil + } + + // Default OSS implementation + decryptSvc, err := c.DecryptService() + if err != nil { + return nil, fmt.Errorf("get decrypt service: %w", err) + } + decrypter := connection.ProvideDecrypter(decryptSvc) + extras := []connection.Extra{ githubconnection.Extra(decrypter, githubconnection.ProvideFactory()), } - enabledTypes := map[provisioning.ConnectionType]struct{}{ - provisioning.GithubConnectionType: {}, - } - connectionFactory, err := connection.ProvideFactory(enabledTypes, extras) - if err != nil { - return nil, fmt.Errorf("create connection factory: %w", err) - } - - return connectionFactory, nil + c.connectionExtras = extras + return extras, nil } func setupDecryptService(cfg *setting.Cfg, tracer tracing.Tracer, tokenExchangeClient *authn.TokenExchangeClient) (decrypt.DecryptService, error) { diff --git a/pkg/tests/apis/provisioning/connection_test.go b/pkg/tests/apis/provisioning/connection_test.go index 42587d0bc4e..3fe0df57d02 100644 --- a/pkg/tests/apis/provisioning/connection_test.go +++ b/pkg/tests/apis/provisioning/connection_test.go @@ -2274,6 +2274,217 @@ func TestIntegrationConnectionController_GranularConditionReasons(t *testing.T) }) } +func TestIntegrationConnectionController_EnterpriseWiring(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + if !extensions.IsEnterprise { + t.Skip("Skipping integration test when not enterprise") + } + + helper := runGrafana(t) + ctx := context.Background() + + t.Run("GitLab connection can be created and reconciled", func(t *testing.T) { + clientSecret := base64.StdEncoding.EncodeToString([]byte("test-client-secret")) + + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "test-gitlab-connection", + "namespace": "default", + }, + "spec": map[string]any{ + "title": "Test GitLab Connection", + "type": string(provisioning.GitlabConnectionType), + "url": "https://gitlab.com", + "gitlab": map[string]any{ + "clientID": "test-client-id", + }, + }, + "secure": map[string]any{ + "clientSecret": map[string]any{ + "create": clientSecret, + }, + }, + }} + + // CREATE + created, err := helper.Connections.Resource.Create(ctx, connection, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create GitLab connection") + require.NotNil(t, created) + + connectionName := created.GetName() + require.NotEmpty(t, connectionName, "connection name should not be empty") + + // Cleanup + defer func() { + _ = helper.Connections.Resource.Delete(ctx, connectionName, metav1.DeleteOptions{}) + }() + + // READ + output, err := helper.Connections.Resource.Get(ctx, connectionName, metav1.GetOptions{}) + require.NoError(t, err, "failed to read back GitLab connection") + assert.Equal(t, connectionName, output.GetName(), "name should be equal") + + spec := output.Object["spec"].(map[string]any) + assert.Equal(t, string(provisioning.GitlabConnectionType), spec["type"], "type should be gitlab") + + // Get typed client for status checks + restConfig := helper.Org1.Admin.NewRestConfig() + provClient, err := clientset.NewForConfig(restConfig) + require.NoError(t, err, "failed to create provisioning client") + connClient := provClient.ProvisioningV0alpha1().Connections("default") + + // Wait for reconciliation - controller should process the resource + // With fake credentials, health check will fail, but reconciliation should happen + require.Eventually(t, func() bool { + updated, err := connClient.Get(ctx, connectionName, metav1.GetOptions{}) + if err != nil { + return false + } + // Check that controller has reconciled (ObservedGeneration matches Generation) + // and that health check was attempted (Checked > 0) + return updated.Status.ObservedGeneration == updated.Generation && + updated.Status.Health.Checked > 0 + }, 15*time.Second, 500*time.Millisecond, "connection should be reconciled by controller") + + // Verify reconciliation status + reconciled, err := connClient.Get(ctx, connectionName, metav1.GetOptions{}) + require.NoError(t, err) + + // Controller should have set ObservedGeneration - this proves reconciliation happened + assert.Equal(t, reconciled.Generation, reconciled.Status.ObservedGeneration, + "controller should have reconciled the connection") + + // Health check should have been attempted - proves the controller processed it + assert.Greater(t, reconciled.Status.Health.Checked, int64(0), + "health check should have been attempted") + + // Should have a ready condition - proves status was updated + readyCondition := meta.FindStatusCondition(reconciled.Status.Conditions, provisioning.ConditionTypeReady) + assert.NotNil(t, readyCondition, "should have ready condition") + + t.Logf("GitLab connection reconciled successfully. Health: %v, ObservedGen: %d, Checked: %d", + reconciled.Status.Health.Healthy, reconciled.Status.ObservedGeneration, reconciled.Status.Health.Checked) + }) + + t.Run("Bitbucket connection can be created and reconciled", func(t *testing.T) { + clientSecret := base64.StdEncoding.EncodeToString([]byte("test-client-secret")) + + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "test-bitbucket-connection", + "namespace": "default", + }, + "spec": map[string]any{ + "title": "Test Bitbucket Connection", + "type": string(provisioning.BitbucketConnectionType), + "bitbucket": map[string]any{ + "clientID": "test-client-id", + }, + }, + "secure": map[string]any{ + "clientSecret": map[string]any{ + "create": clientSecret, + }, + }, + }} + + // CREATE + created, err := helper.Connections.Resource.Create(ctx, connection, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create Bitbucket connection") + require.NotNil(t, created) + + connectionName := created.GetName() + require.NotEmpty(t, connectionName, "connection name should not be empty") + + // Cleanup + defer func() { + _ = helper.Connections.Resource.Delete(ctx, connectionName, metav1.DeleteOptions{}) + }() + + // READ + output, err := helper.Connections.Resource.Get(ctx, connectionName, metav1.GetOptions{}) + require.NoError(t, err, "failed to read back Bitbucket connection") + assert.Equal(t, connectionName, output.GetName(), "name should be equal") + + spec := output.Object["spec"].(map[string]any) + assert.Equal(t, string(provisioning.BitbucketConnectionType), spec["type"], "type should be bitbucket") + + // Get typed client for status checks + restConfig := helper.Org1.Admin.NewRestConfig() + provClient, err := clientset.NewForConfig(restConfig) + require.NoError(t, err, "failed to create provisioning client") + connClient := provClient.ProvisioningV0alpha1().Connections("default") + + // Wait for reconciliation + require.Eventually(t, func() bool { + updated, err := connClient.Get(ctx, connectionName, metav1.GetOptions{}) + if err != nil { + return false + } + return updated.Status.ObservedGeneration == updated.Generation && + updated.Status.Health.Checked > 0 + }, 15*time.Second, 500*time.Millisecond, "connection should be reconciled by controller") + + // Verify reconciliation status + reconciled, err := connClient.Get(ctx, connectionName, metav1.GetOptions{}) + require.NoError(t, err) + + assert.Equal(t, reconciled.Generation, reconciled.Status.ObservedGeneration, + "controller should have reconciled the connection") + assert.Greater(t, reconciled.Status.Health.Checked, int64(0), + "health check should have been attempted") + + readyCondition := meta.FindStatusCondition(reconciled.Status.Conditions, provisioning.ConditionTypeReady) + assert.NotNil(t, readyCondition, "should have ready condition") + + t.Logf("Bitbucket connection reconciled successfully. Health: %v, ObservedGen: %d, Checked: %d", + reconciled.Status.Health.Healthy, reconciled.Status.ObservedGeneration, reconciled.Status.Health.Checked) + }) + + t.Run("All connection types are supported", func(t *testing.T) { + // List all supported connection types by attempting to create connections + // This validates the factory has all expected types registered + + supportedTypes := []provisioning.ConnectionType{ + provisioning.GithubConnectionType, + provisioning.GitlabConnectionType, + provisioning.BitbucketConnectionType, + } + + for _, connType := range supportedTypes { + t.Run(string(connType), func(t *testing.T) { + // We just check that we can create the object without factory errors + // Validation errors are expected if credentials are missing/invalid + conn := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "generateName": "test-", + "namespace": "default", + }, + "spec": map[string]any{ + "title": "Test Connection", + "type": string(connType), + }, + }} + + // Try to create - we expect validation error, not "type not supported" + _, err := helper.Connections.Resource.Create(ctx, conn, metav1.CreateOptions{}) + if err != nil { + // Should be a validation error, not "type not supported" + assert.NotContains(t, err.Error(), "is not supported", + "type %s should be supported by factory", connType) + } + }) + } + }) +} + func verifyToken(t *testing.T, appID, token string) (bool, error) { t.Helper() diff --git a/pkg/tests/apis/provisioning/repository_test.go b/pkg/tests/apis/provisioning/repository_test.go index 8cab9430c91..4ef2eb7252b 100644 --- a/pkg/tests/apis/provisioning/repository_test.go +++ b/pkg/tests/apis/provisioning/repository_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -1773,3 +1774,218 @@ func TestIntegrationRepositoryController_DefaultBranch(t *testing.T) { }, 30*time.Second, 1*time.Second, "repository should have default branch") }) } + +func TestIntegrationRepositoryController_EnterpriseWiring(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + if !extensions.IsEnterprise { + t.Skip("Skipping integration test when not enterprise") + } + + helper := runGrafana(t) + ctx := context.Background() + + t.Run("GitLab repository can be created and reconciled", func(t *testing.T) { + token := base64.StdEncoding.EncodeToString([]byte("test-gitlab-token")) + + repository := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Repository", + "metadata": map[string]any{ + "name": "test-gitlab-repo", + "namespace": "default", + }, + "spec": map[string]any{ + "title": "Test GitLab Repository", + "type": string(provisioning.GitLabRepositoryType), + "gitlab": map[string]any{ + "url": "https://gitlab.com/test/repo.git", + "branch": "main", + "path": "dashboards", + }, + }, + "secure": map[string]any{ + "token": map[string]any{ + "create": token, + }, + }, + }} + + // CREATE + created, err := helper.Repositories.Resource.Create(ctx, repository, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create GitLab repository") + require.NotNil(t, created) + + repoName := created.GetName() + require.NotEmpty(t, repoName, "repository name should not be empty") + + // Cleanup + defer func() { + _ = helper.Repositories.Resource.Delete(ctx, repoName, metav1.DeleteOptions{}) + }() + + // READ + output, err := helper.Repositories.Resource.Get(ctx, repoName, metav1.GetOptions{}) + require.NoError(t, err, "failed to read back GitLab repository") + assert.Equal(t, repoName, output.GetName(), "name should be equal") + + spec := output.Object["spec"].(map[string]any) + assert.Equal(t, string(provisioning.GitLabRepositoryType), spec["type"], "type should be gitlab") + + // Get typed client for status checks + restConfig := helper.Org1.Admin.NewRestConfig() + provClient, err := clientset.NewForConfig(restConfig) + require.NoError(t, err, "failed to create provisioning client") + repoClient := provClient.ProvisioningV0alpha1().Repositories("default") + + // Wait for reconciliation - controller should process the resource + // With fake credentials, the git operations will fail, but reconciliation should happen + require.Eventually(t, func() bool { + updated, err := repoClient.Get(ctx, repoName, metav1.GetOptions{}) + if err != nil { + return false + } + // Check that controller has reconciled (ObservedGeneration matches Generation) + // and that health check was attempted (Checked > 0) + return updated.Status.ObservedGeneration == updated.Generation && + updated.Status.Health.Checked > 0 + }, 15*time.Second, 500*time.Millisecond, "repository should be reconciled by controller") + + // Verify reconciliation status + reconciled, err := repoClient.Get(ctx, repoName, metav1.GetOptions{}) + require.NoError(t, err) + + // Controller should have set ObservedGeneration - this proves reconciliation happened + assert.Equal(t, reconciled.Generation, reconciled.Status.ObservedGeneration, + "controller should have reconciled the repository") + + // Health check should have been attempted - proves the controller processed it + assert.Greater(t, reconciled.Status.Health.Checked, int64(0), + "health check should have been attempted") + + // Should have a ready condition - proves status was updated + readyCondition := meta.FindStatusCondition(reconciled.Status.Conditions, provisioning.ConditionTypeReady) + assert.NotNil(t, readyCondition, "should have ready condition") + + t.Logf("GitLab repository reconciled successfully. Health: %v, ObservedGen: %d, Checked: %d", + reconciled.Status.Health.Healthy, reconciled.Status.ObservedGeneration, reconciled.Status.Health.Checked) + }) + + t.Run("Bitbucket repository can be and reconciled", func(t *testing.T) { + token := base64.StdEncoding.EncodeToString([]byte("test-bitbucket-token")) + + repository := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Repository", + "metadata": map[string]any{ + "name": "test-bitbucket-repo", + "namespace": "default", + }, + "spec": map[string]any{ + "title": "Test Bitbucket Repository", + "type": string(provisioning.BitbucketRepositoryType), + "bitbucket": map[string]any{ + "url": "https://bitbucket.org/workspace/repo.git", + "branch": "main", + "path": "dashboards", + }, + }, + "secure": map[string]any{ + "token": map[string]any{ + "create": token, + }, + }, + }} + + // CREATE + created, err := helper.Repositories.Resource.Create(ctx, repository, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create Bitbucket repository") + require.NotNil(t, created) + + repoName := created.GetName() + require.NotEmpty(t, repoName, "repository name should not be empty") + + // Cleanup + defer func() { + _ = helper.Repositories.Resource.Delete(ctx, repoName, metav1.DeleteOptions{}) + }() + + // READ + output, err := helper.Repositories.Resource.Get(ctx, repoName, metav1.GetOptions{}) + require.NoError(t, err, "failed to read back Bitbucket repository") + assert.Equal(t, repoName, output.GetName(), "name should be equal") + + spec := output.Object["spec"].(map[string]any) + assert.Equal(t, string(provisioning.BitbucketRepositoryType), spec["type"], "type should be bitbucket") + + // Get typed client for status checks + restConfig := helper.Org1.Admin.NewRestConfig() + provClient, err := clientset.NewForConfig(restConfig) + require.NoError(t, err, "failed to create provisioning client") + repoClient := provClient.ProvisioningV0alpha1().Repositories("default") + + // Wait for reconciliation + require.Eventually(t, func() bool { + updated, err := repoClient.Get(ctx, repoName, metav1.GetOptions{}) + if err != nil { + return false + } + return updated.Status.ObservedGeneration == updated.Generation && + updated.Status.Health.Checked > 0 + }, 15*time.Second, 500*time.Millisecond, "repository should be reconciled by controller") + + // Verify reconciliation status + reconciled, err := repoClient.Get(ctx, repoName, metav1.GetOptions{}) + require.NoError(t, err) + + assert.Equal(t, reconciled.Generation, reconciled.Status.ObservedGeneration, + "controller should have reconciled the repository") + assert.Greater(t, reconciled.Status.Health.Checked, int64(0), + "health check should have been attempted") + + readyCondition := meta.FindStatusCondition(reconciled.Status.Conditions, provisioning.ConditionTypeReady) + assert.NotNil(t, readyCondition, "should have ready condition") + + t.Logf("Bitbucket repository reconciled successfully. Health: %v, ObservedGen: %d, Checked: %d", + reconciled.Status.Health.Healthy, reconciled.Status.ObservedGeneration, reconciled.Status.Health.Checked) + }) + + t.Run("All repository types are supported", func(t *testing.T) { + // List all supported repository types + + supportedTypes := []provisioning.RepositoryType{ + provisioning.GitHubRepositoryType, + provisioning.GitLabRepositoryType, + provisioning.BitbucketRepositoryType, + provisioning.GitRepositoryType, + provisioning.LocalRepositoryType, + } + + for _, repoType := range supportedTypes { + t.Run(string(repoType), func(t *testing.T) { + // We just check that we can create the object without factory errors + // Validation errors are expected if configuration is missing/invalid + repo := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Repository", + "metadata": map[string]any{ + "generateName": "test-", + "namespace": "default", + }, + "spec": map[string]any{ + "title": "Test Repository", + "type": string(repoType), + }, + }} + + // Try to create - we expect validation error, not "type not supported" + _, err := helper.Repositories.Resource.Create(ctx, repo, metav1.CreateOptions{}) + if err != nil { + // Should be a validation error, not "type not supported" + assert.NotContains(t, err.Error(), "is not supported", + "type %s should be supported by factory", repoType) + } + }) + } + }) +}