Files
grafana/pkg/tests/apis/provisioning/connection_repositories_test.go
Roberto Jiménez Sánchez 7be93d9af4 Provisioning: add /connections/{name}/repositories endpoint (#116020)
* feat(provisioning): add /connections/{name}/repositories endpoint

Add a new subresource endpoint to list external repositories from git
providers (GitHub, GitLab, Bitbucket) accessible through a connection.

Changes:
- Add ExternalRepositoryList and ExternalRepository types with Name, Owner, and URL fields
- Create connection_repositories.go connector (returns 'not implemented' for now)
- Register storage and authorization for the repositories subresource
- Update OpenAPI documentation
- Regenerate code (deepcopy, openapi, client)

The endpoint is accessible at /apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/connections/{name}/repositories
and requires admin read access.

Related: #TBD

* test(provisioning): add unit and integration tests for connection repositories endpoint

- Add unit tests for connection_repositories connector
- Add integration tests for authorization and endpoint behavior
- Tests verify not implemented response and proper authorization

* Fix generation

* fix(tests): fix test compilation and assertions

- Remove unused import in unit test
- Fix integration test Raw() usage
- Fix ExternalRepositoryList type verification test

* Format code

* fix(provisioning): fix ineffectual assignment in connection_repositories connector

- Add debug log statement to use logger variable
- Fixes linter error about ineffectual assignment to ctx
2026-01-08 16:14:19 +00:00

173 lines
5.7 KiB
Go

package provisioning
import (
"context"
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/util/testutil"
)
func TestIntegrationProvisioning_ConnectionRepositories(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := runGrafana(t)
ctx := context.Background()
createOptions := metav1.CreateOptions{FieldValidation: "Strict"}
// Create a connection for testing
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection-repositories-test",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "123456",
"installationID": "454545",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.NoError(t, err, "failed to create connection")
t.Run("endpoint returns not implemented", func(t *testing.T) {
var statusCode int
result := helper.AdminREST.Get().
Namespace("default").
Resource("connections").
Name("connection-repositories-test").
SubResource("repositories").
Do(ctx).
StatusCode(&statusCode)
require.Error(t, result.Error(), "should return error for not implemented endpoint")
require.Equal(t, http.StatusMethodNotAllowed, statusCode, "should return 405 Method Not Allowed")
require.True(t, apierrors.IsMethodNotSupported(result.Error()), "error should be MethodNotSupported")
})
t.Run("admin can access endpoint (gets not implemented)", func(t *testing.T) {
var statusCode int
result := helper.AdminREST.Get().
Namespace("default").
Resource("connections").
Name("connection-repositories-test").
SubResource("repositories").
Do(ctx).StatusCode(&statusCode)
// Endpoint exists but returns not implemented
require.Error(t, result.Error(), "should return error")
require.True(t, apierrors.IsMethodNotSupported(result.Error()), "error should be MethodNotSupported")
// Status code should be 405 (Method Not Allowed) for method not supported
require.Equal(t, http.StatusMethodNotAllowed, statusCode)
})
t.Run("editor cannot access endpoint", func(t *testing.T) {
var statusCode int
result := helper.EditorREST.Get().
Namespace("default").
Resource("connections").
Name("connection-repositories-test").
SubResource("repositories").
Do(ctx).StatusCode(&statusCode)
require.Error(t, result.Error(), "editor should not be able to access repositories endpoint")
require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden")
require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden")
})
t.Run("viewer cannot access endpoint", func(t *testing.T) {
var statusCode int
result := helper.ViewerREST.Get().
Namespace("default").
Resource("connections").
Name("connection-repositories-test").
SubResource("repositories").
Do(ctx).StatusCode(&statusCode)
require.Error(t, result.Error(), "viewer should not be able to access repositories endpoint")
require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden")
require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden")
})
t.Run("non-GET methods are rejected", func(t *testing.T) {
configBytes, _ := json.Marshal(map[string]any{})
var statusCode int
result := helper.AdminREST.Post().
Namespace("default").
Resource("connections").
Name("connection-repositories-test").
SubResource("repositories").
Body(configBytes).
SetHeader("Content-Type", "application/json").
Do(ctx).StatusCode(&statusCode)
require.Error(t, result.Error(), "POST should not be allowed")
require.True(t, apierrors.IsMethodNotSupported(result.Error()), "error should be MethodNotSupported")
})
}
func TestIntegrationProvisioning_ConnectionRepositoriesResponseType(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := runGrafana(t)
ctx := context.Background()
createOptions := metav1.CreateOptions{FieldValidation: "Strict"}
// Create a connection for testing
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection-repositories-type-test",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "123456",
"installationID": "454545",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.NoError(t, err, "failed to create connection")
t.Run("verify ExternalRepositoryList type exists in API", func(t *testing.T) {
// Verify the type is registered and can be instantiated
list := &provisioning.ExternalRepositoryList{}
require.NotNil(t, list)
// Verify it has the expected structure (Items is a slice, nil by default is fine)
require.IsType(t, []provisioning.ExternalRepository{}, list.Items)
// Can create items
list.Items = []provisioning.ExternalRepository{
{Name: "test", Owner: "owner", URL: "https://example.com/repo"},
}
require.Len(t, list.Items, 1)
require.Equal(t, "test", list.Items[0].Name)
})
}