mirror of
https://github.com/grafana/grafana.git
synced 2026-06-13 10:30:42 -04:00
374 lines
10 KiB
Go
374 lines
10 KiB
Go
package github_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"testing"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
|
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
|
|
"github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
|
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
type mockSecureValues struct {
|
|
privateKey common.RawSecureValue
|
|
privateKeyErr error
|
|
clientSecret common.RawSecureValue
|
|
clientSecErr error
|
|
token common.RawSecureValue
|
|
tokenErr error
|
|
}
|
|
|
|
func (m *mockSecureValues) PrivateKey(_ context.Context) (common.RawSecureValue, error) {
|
|
return m.privateKey, m.privateKeyErr
|
|
}
|
|
|
|
func (m *mockSecureValues) ClientSecret(_ context.Context) (common.RawSecureValue, error) {
|
|
return m.clientSecret, m.clientSecErr
|
|
}
|
|
|
|
func (m *mockSecureValues) Token(_ context.Context) (common.RawSecureValue, error) {
|
|
return m.token, m.tokenErr
|
|
}
|
|
|
|
func TestExtra_Type(t *testing.T) {
|
|
mockFactory := github.NewMockGithubFactory(t)
|
|
decrypter := func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{}
|
|
}
|
|
|
|
e := github.Extra(decrypter, mockFactory)
|
|
|
|
result := e.Type()
|
|
|
|
assert.Equal(t, provisioning.GithubConnectionType, result)
|
|
}
|
|
|
|
func TestExtra_Build(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
conn *provisioning.Connection
|
|
setupDecrypter func() connection.Decrypter
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "success with valid connection",
|
|
conn: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-connection",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "123456",
|
|
InstallationID: "789012",
|
|
},
|
|
},
|
|
},
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{
|
|
privateKey: common.RawSecureValue("test-private-key"),
|
|
token: common.RawSecureValue("test-token"),
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "nil connection",
|
|
conn: nil,
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{}
|
|
}
|
|
},
|
|
expectedError: "invalid github connection",
|
|
},
|
|
{
|
|
name: "connection without github config",
|
|
conn: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-connection",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: nil,
|
|
},
|
|
},
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{}
|
|
}
|
|
},
|
|
expectedError: "invalid github connection",
|
|
},
|
|
{
|
|
name: "error decrypting private key",
|
|
conn: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-connection",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "123456",
|
|
InstallationID: "789012",
|
|
},
|
|
},
|
|
},
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{
|
|
privateKeyErr: errors.New("failed to decrypt private key"),
|
|
}
|
|
}
|
|
},
|
|
expectedError: "failed to decrypt private key",
|
|
},
|
|
{
|
|
name: "error decrypting token",
|
|
conn: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-connection",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "123456",
|
|
InstallationID: "789012",
|
|
},
|
|
},
|
|
},
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{
|
|
privateKey: common.RawSecureValue("test-private-key"),
|
|
tokenErr: errors.New("failed to decrypt token"),
|
|
}
|
|
}
|
|
},
|
|
expectedError: "failed to decrypt token",
|
|
},
|
|
{
|
|
name: "success with empty secure values",
|
|
conn: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-connection",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "123456",
|
|
InstallationID: "789012",
|
|
},
|
|
},
|
|
},
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{
|
|
privateKey: common.RawSecureValue(""),
|
|
token: common.RawSecureValue(""),
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "success with different app and installation IDs",
|
|
conn: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "another-connection",
|
|
Namespace: "prod",
|
|
},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "999888",
|
|
InstallationID: "777666",
|
|
},
|
|
},
|
|
},
|
|
setupDecrypter: func() connection.Decrypter {
|
|
return func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{
|
|
privateKey: common.RawSecureValue("another-private-key"),
|
|
token: common.RawSecureValue("another-token"),
|
|
}
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
mockFactory := github.NewMockGithubFactory(t)
|
|
decrypter := tt.setupDecrypter()
|
|
|
|
e := github.Extra(decrypter, mockFactory)
|
|
|
|
result, err := e.Build(ctx, tt.conn)
|
|
|
|
if tt.expectedError != "" {
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.expectedError)
|
|
assert.Nil(t, result)
|
|
} else {
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtra_Mutate(t *testing.T) {
|
|
privateKeyBase64 := base64.StdEncoding.EncodeToString([]byte(testPrivateKeyPEM))
|
|
|
|
tests := []struct {
|
|
name string
|
|
obj runtime.Object
|
|
wantErr bool
|
|
validateError func(t *testing.T, err error)
|
|
validateResult func(t *testing.T, obj runtime.Object)
|
|
}{
|
|
{
|
|
name: "should successfully mutate GitHub connection without generating token",
|
|
obj: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "123",
|
|
InstallationID: "456",
|
|
},
|
|
},
|
|
Secure: provisioning.ConnectionSecure{
|
|
PrivateKey: common.InlineSecureValue{
|
|
Create: common.NewSecretValue(privateKeyBase64),
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
validateResult: func(t *testing.T, obj runtime.Object) {
|
|
conn := obj.(*provisioning.Connection)
|
|
assert.Equal(t, "https://github.com/settings/installations/456", conn.Spec.URL)
|
|
assert.True(t, conn.Secure.Token.Create.IsZero(), "Token should not be generated in mutator")
|
|
},
|
|
},
|
|
{
|
|
name: "should be no-op for non-Connection objects",
|
|
obj: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-repo"},
|
|
},
|
|
wantErr: false,
|
|
validateResult: func(t *testing.T, obj runtime.Object) {
|
|
// Object should remain unchanged
|
|
repo := obj.(*provisioning.Repository)
|
|
assert.Equal(t, "test-repo", repo.Name)
|
|
},
|
|
},
|
|
{
|
|
name: "should be no-op for Connection without GitHub config",
|
|
obj: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GitlabConnectionType,
|
|
Gitlab: &provisioning.GitlabConnectionConfig{
|
|
ClientID: "clientID",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
validateResult: func(t *testing.T, obj runtime.Object) {
|
|
conn := obj.(*provisioning.Connection)
|
|
assert.Empty(t, conn.Spec.URL, "URL should not be set")
|
|
assert.True(t, conn.Secure.Token.Create.IsZero(), "Token should not be generated")
|
|
},
|
|
},
|
|
{
|
|
name: "should generate URL from installation ID",
|
|
obj: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "789",
|
|
InstallationID: "012",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
validateResult: func(t *testing.T, obj runtime.Object) {
|
|
conn := obj.(*provisioning.Connection)
|
|
assert.Equal(t, "https://github.com/settings/installations/012", conn.Spec.URL)
|
|
},
|
|
},
|
|
{
|
|
name: "should preserve token when no new key provided",
|
|
obj: &provisioning.Connection{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
|
Spec: provisioning.ConnectionSpec{
|
|
Type: provisioning.GithubConnectionType,
|
|
GitHub: &provisioning.GitHubConnectionConfig{
|
|
AppID: "123",
|
|
InstallationID: "456",
|
|
},
|
|
},
|
|
Secure: provisioning.ConnectionSecure{
|
|
PrivateKey: common.InlineSecureValue{
|
|
Name: "existing-key",
|
|
},
|
|
Token: common.InlineSecureValue{
|
|
Create: common.NewSecretValue("existing-token"),
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
validateResult: func(t *testing.T, obj runtime.Object) {
|
|
conn := obj.(*provisioning.Connection)
|
|
assert.Equal(t, "https://github.com/settings/installations/456", conn.Spec.URL)
|
|
// Token should remain unchanged
|
|
assert.Equal(t, "existing-token", conn.Secure.Token.Create.DangerouslyExposeAndConsumeValue())
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
mockFactory := github.NewMockGithubFactory(t)
|
|
decrypter := func(c *provisioning.Connection) connection.SecureValues {
|
|
return &mockSecureValues{}
|
|
}
|
|
|
|
e := github.Extra(decrypter, mockFactory)
|
|
|
|
err := e.Mutate(ctx, tt.obj)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
if tt.validateError != nil {
|
|
tt.validateError(t, err)
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
if tt.validateResult != nil {
|
|
tt.validateResult(t, tt.obj)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|