mirror of https://github.com/grafana/grafana
prometheushacktoberfestmetricsmonitoringalertinggrafanagoinfluxdbmysqlpostgresanalyticsdata-visualizationdashboardbusiness-intelligenceelasticsearch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2004 lines
60 KiB
2004 lines
60 KiB
package webhooks
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
|
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
func TestParseWebhooks(t *testing.T) {
|
|
tests := []struct {
|
|
messageType string
|
|
name string
|
|
expected provisioning.WebhookResponse
|
|
}{
|
|
{"ping", "check", provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
}},
|
|
{"pull_request", "opened", provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted, // 202
|
|
Job: &provisioning.JobSpec{
|
|
Repository: "unit-test-repo",
|
|
Action: provisioning.JobActionPullRequest,
|
|
PullRequest: &provisioning.PullRequestJobOptions{
|
|
Ref: "dashboard/1733653266690",
|
|
Hash: "ab5446a53df9e5f8bdeed52250f51fad08e822bc",
|
|
PR: 12,
|
|
URL: "https://github.com/grafana/git-ui-sync-demo/pull/12",
|
|
},
|
|
},
|
|
}},
|
|
{"push", "different_branch", provisioning.WebhookResponse{
|
|
Code: http.StatusOK, // we don't care about a branch that isn't the one we configured
|
|
}},
|
|
{"push", "nothing_relevant", provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted,
|
|
Job: &provisioning.JobSpec{ // we want to always push a sync job
|
|
Repository: "unit-test-repo",
|
|
Action: provisioning.JobActionPull,
|
|
Pull: &provisioning.SyncJobOptions{
|
|
Incremental: true,
|
|
},
|
|
},
|
|
}},
|
|
{"push", "nested", provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted,
|
|
Job: &provisioning.JobSpec{
|
|
Repository: "unit-test-repo",
|
|
Action: provisioning.JobActionPull,
|
|
Pull: &provisioning.SyncJobOptions{
|
|
Incremental: true,
|
|
},
|
|
},
|
|
}},
|
|
{"issue_comment", "created", provisioning.WebhookResponse{
|
|
Code: http.StatusNotImplemented,
|
|
}},
|
|
}
|
|
|
|
gh := &githubWebhookRepository{
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "unit-test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
Sync: provisioning.SyncOptions{
|
|
Enabled: true, // required to accept sync job
|
|
},
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
URL: "https://github.com/grafana/git-ui-sync-demo",
|
|
Branch: "main",
|
|
|
|
GenerateDashboardPreviews: true,
|
|
},
|
|
},
|
|
},
|
|
owner: "grafana",
|
|
repo: "git-ui-sync-demo",
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
name := fmt.Sprintf("webhook-%s-%s.json", tt.messageType, tt.name)
|
|
t.Run(name, func(t *testing.T) {
|
|
// nolint:gosec
|
|
payload, err := os.ReadFile(path.Join("testdata", name))
|
|
require.NoError(t, err)
|
|
|
|
rsp, err := gh.parseWebhook(tt.messageType, payload)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tt.expected.Code, rsp.Code)
|
|
require.Equal(t, tt.expected.Job, rsp.Job)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubRepository_Webhook(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *provisioning.Repository
|
|
webhookSecret string
|
|
setupRequest func() *http.Request
|
|
mockSetup func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets)
|
|
expected *provisioning.WebhookResponse
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "missing webhook configuration",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
// No webhook configuration
|
|
},
|
|
},
|
|
setupRequest: func() *http.Request {
|
|
req, _ := http.NewRequest("POST", "/webhook", nil)
|
|
return req
|
|
},
|
|
expectedError: fmt.Errorf("unexpected webhook request"),
|
|
},
|
|
{
|
|
name: "secret decryption error",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
setupRequest: func() *http.Request {
|
|
req, _ := http.NewRequest("POST", "/webhook", nil)
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return(nil, errors.New("decryption failed"))
|
|
},
|
|
expectedError: fmt.Errorf("failed to decrypt secret: decryption failed"),
|
|
},
|
|
{
|
|
name: "invalid signature",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader("invalid payload"))
|
|
req.Header.Set("X-Hub-Signature-256", "invalid")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: apierrors.NewUnauthorized("invalid signature"),
|
|
},
|
|
{
|
|
name: "ping event",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "ping")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
Message: "ping received",
|
|
},
|
|
},
|
|
{
|
|
name: "push event for different branch",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
Sync: provisioning.SyncOptions{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"ref": "refs/heads/feature",
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "push")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
},
|
|
},
|
|
{
|
|
name: "push event for main branch",
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
Sync: provisioning.SyncOptions{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"ref": "refs/heads/main",
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "push")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted,
|
|
Job: &provisioning.JobSpec{
|
|
Repository: "test-repo",
|
|
Action: provisioning.JobActionPull,
|
|
Pull: &provisioning.SyncJobOptions{
|
|
Incremental: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "push event with missing repository",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"ref": "refs/heads/main"
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "push")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: fmt.Errorf("missing repository in push event"),
|
|
},
|
|
{
|
|
name: "push event with repository mismatch",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"ref": "refs/heads/main",
|
|
"repository": {
|
|
"full_name": "different-owner/different-repo"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "push")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: fmt.Errorf("repository mismatch"),
|
|
},
|
|
{
|
|
name: "push event when sync is disabled",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
Sync: provisioning.SyncOptions{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"ref": "refs/heads/main",
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "push")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
},
|
|
},
|
|
{
|
|
name: "pull request event - opened",
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "opened",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/grafana/grafana/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "main"
|
|
}
|
|
},
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted,
|
|
Message: "pull request: opened",
|
|
Job: &provisioning.JobSpec{
|
|
Repository: "test-repo",
|
|
Action: provisioning.JobActionPullRequest,
|
|
PullRequest: &provisioning.PullRequestJobOptions{
|
|
URL: "https://github.com/grafana/grafana/pull/123",
|
|
PR: 123,
|
|
Ref: "feature-branch",
|
|
Hash: "abcdef1234567890",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pull request event - synchronize",
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "synchronize",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/grafana/grafana/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "main"
|
|
}
|
|
},
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted,
|
|
Message: "pull request: synchronize",
|
|
Job: &provisioning.JobSpec{
|
|
Repository: "test-repo",
|
|
Action: provisioning.JobActionPullRequest,
|
|
PullRequest: &provisioning.PullRequestJobOptions{
|
|
URL: "https://github.com/grafana/grafana/pull/123",
|
|
PR: 123,
|
|
Ref: "feature-branch",
|
|
Hash: "abcdef1234567890",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pull request event - wrong base branch",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "opened",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/grafana/grafana/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "develop"
|
|
}
|
|
},
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
Message: "ignoring pull request event as develop is not the configured branch",
|
|
},
|
|
},
|
|
{
|
|
name: "pull request event - ignored action",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "closed",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/grafana/grafana/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "main"
|
|
}
|
|
},
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
Message: "ignore pull request event: closed",
|
|
},
|
|
},
|
|
{
|
|
name: "pull request event missing repository",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "opened",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/grafana/grafana/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "main"
|
|
}
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: fmt.Errorf("missing repository in pull request event"),
|
|
},
|
|
{
|
|
name: "pull request event with missing GitHub config",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
// GitHub config is intentionally missing
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "opened",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/grafana/grafana/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "main"
|
|
}
|
|
},
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: fmt.Errorf("missing GitHub config"),
|
|
},
|
|
{
|
|
name: "pull request event with repository mismatch",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "opened",
|
|
"pull_request": {
|
|
"html_url": "https://github.com/different-owner/different-repo/pull/123",
|
|
"number": 123,
|
|
"head": {
|
|
"ref": "feature-branch",
|
|
"sha": "abcdef1234567890"
|
|
},
|
|
"base": {
|
|
"ref": "main"
|
|
}
|
|
},
|
|
"repository": {
|
|
"full_name": "different-owner/different-repo"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: fmt.Errorf("repository mismatch"),
|
|
},
|
|
{
|
|
name: "pull request event missing pull request info",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"action": "opened",
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "pull_request")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expectedError: fmt.Errorf("expected PR in event"),
|
|
},
|
|
{
|
|
name: "secret decryption error with new secrets store",
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("test-secret"),
|
|
},
|
|
},
|
|
},
|
|
setupRequest: func() *http.Request {
|
|
req, _ := http.NewRequest("POST", "/webhook", nil)
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "test-secret").
|
|
Return(nil, errors.New("decryption failed"))
|
|
},
|
|
expectedError: fmt.Errorf("failed to decrypt secret: decryption failed"),
|
|
},
|
|
{
|
|
name: "ping event with new secrets store",
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("test-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "ping")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "test-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusOK,
|
|
Message: "ping received",
|
|
},
|
|
},
|
|
{
|
|
name: "push event for main branch with new secrets store",
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
Sync: provisioning.SyncOptions{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("test-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{
|
|
"ref": "refs/heads/main",
|
|
"repository": {
|
|
"full_name": "grafana/grafana"
|
|
}
|
|
}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "push")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "test-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusAccepted,
|
|
Job: &provisioning.JobSpec{
|
|
Repository: "test-repo",
|
|
Action: provisioning.JobActionPull,
|
|
Pull: &provisioning.SyncJobOptions{
|
|
Incremental: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "unsupported event type",
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
EncryptedSecret: []byte("encrypted-secret"),
|
|
},
|
|
},
|
|
},
|
|
webhookSecret: "webhook-secret",
|
|
setupRequest: func() *http.Request {
|
|
payload := `{}`
|
|
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
|
req.Header.Set("X-GitHub-Event", "team")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create a valid signature
|
|
mac := hmac.New(sha256.New, []byte("webhook-secret"))
|
|
mac.Write([]byte(payload))
|
|
signature := hex.EncodeToString(mac.Sum(nil))
|
|
req.Header.Set("X-Hub-Signature-256", "sha256="+signature)
|
|
|
|
return req
|
|
},
|
|
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
|
Return([]byte("webhook-secret"), nil)
|
|
},
|
|
expected: &provisioning.WebhookResponse{
|
|
Code: http.StatusNotImplemented,
|
|
Message: "unsupported messageType: team",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock secrets service
|
|
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
|
|
|
// Set up the mock expectations
|
|
if tt.mockSetup != nil {
|
|
tt.mockSetup(t, mockSecrets)
|
|
}
|
|
|
|
// Create a GitHub repository with the test config
|
|
repo := &githubWebhookRepository{
|
|
config: tt.config,
|
|
owner: "grafana",
|
|
repo: "grafana",
|
|
secrets: mockSecrets,
|
|
}
|
|
|
|
// Call the Webhook method
|
|
response, err := repo.Webhook(context.Background(), tt.setupRequest())
|
|
|
|
// Check the error
|
|
if tt.expectedError != nil {
|
|
require.Error(t, err)
|
|
var statusErr *apierrors.StatusError
|
|
if errors.As(tt.expectedError, &statusErr) {
|
|
var actualStatusErr *apierrors.StatusError
|
|
require.True(t, errors.As(err, &actualStatusErr), "Expected StatusError but got different error type")
|
|
require.Equal(t, statusErr.Status().Message, actualStatusErr.Status().Message)
|
|
require.Equal(t, statusErr.Status().Code, actualStatusErr.Status().Code)
|
|
} else {
|
|
require.Equal(t, tt.expectedError.Error(), err.Error())
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected.Code, response.Code)
|
|
require.Equal(t, tt.expected.Message, response.Message)
|
|
|
|
if tt.expected.Job != nil {
|
|
require.NotNil(t, response.Job)
|
|
require.Equal(t, tt.expected.Job.Action, response.Job.Action)
|
|
if tt.expected.Job.Pull != nil {
|
|
require.Equal(t, tt.expected.Job.Pull.Incremental, response.Job.Pull.Incremental)
|
|
}
|
|
if tt.expected.Job.PullRequest != nil {
|
|
require.Equal(t, tt.expected.Job.PullRequest.URL, response.Job.PullRequest.URL)
|
|
require.Equal(t, tt.expected.Job.PullRequest.PR, response.Job.PullRequest.PR)
|
|
require.Equal(t, tt.expected.Job.PullRequest.Ref, response.Job.PullRequest.Ref)
|
|
require.Equal(t, tt.expected.Job.PullRequest.Hash, response.Job.PullRequest.Hash)
|
|
}
|
|
} else {
|
|
require.Nil(t, response.Job)
|
|
}
|
|
}
|
|
|
|
// Verify all mock expectations were met
|
|
mockSecrets.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubRepository_CommentPullRequest(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(m *github.MockClient)
|
|
prNumber int
|
|
comment string
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "successfully comment on pull request",
|
|
setupMock: func(m *github.MockClient) {
|
|
m.On("CreatePullRequestComment", mock.Anything, "grafana", "grafana", 123, "Test comment").
|
|
Return(nil)
|
|
},
|
|
prNumber: 123,
|
|
comment: "Test comment",
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "error commenting on pull request",
|
|
setupMock: func(m *github.MockClient) {
|
|
m.On("CreatePullRequestComment", mock.Anything, "grafana", "grafana", 456, "Error comment").
|
|
Return(fmt.Errorf("failed to create comment"))
|
|
},
|
|
prNumber: 456,
|
|
comment: "Error comment",
|
|
expectedError: fmt.Errorf("failed to create comment"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Setup mock GitHub client
|
|
mockGH := github.NewMockClient(t)
|
|
tt.setupMock(mockGH)
|
|
|
|
// Create repository with mock
|
|
repo := &githubWebhookRepository{
|
|
gh: mockGH,
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
},
|
|
owner: "grafana",
|
|
repo: "grafana",
|
|
}
|
|
|
|
// Call the CommentPullRequest method
|
|
err := repo.CommentPullRequest(context.Background(), tt.prNumber, tt.comment)
|
|
|
|
// Check results
|
|
if tt.expectedError != nil {
|
|
require.Error(t, err)
|
|
require.Equal(t, tt.expectedError.Error(), err.Error())
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Verify all mock expectations were met
|
|
mockGH.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubRepository_OnCreate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(m *github.MockClient)
|
|
config *provisioning.Repository
|
|
webhookURL string
|
|
expectedHook *provisioning.WebhookStatus
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "successfully create webhook",
|
|
setupMock: func(m *github.MockClient) {
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(cfg github.WebhookConfig) bool {
|
|
return cfg.URL == "https://example.com/webhook" &&
|
|
cfg.ContentType == "json" &&
|
|
cfg.Active == true
|
|
})).Return(github.WebhookConfig{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
Secret: "test-secret",
|
|
}, nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
Secret: "test-secret",
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "no webhook URL",
|
|
setupMock: func(m *github.MockClient) {
|
|
// No webhook creation expected
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "",
|
|
expectedHook: nil,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "error creating webhook",
|
|
setupMock: func(m *github.MockClient) {
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
|
Return(github.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: nil,
|
|
expectedError: fmt.Errorf("failed to create webhook"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Setup mock GitHub client
|
|
mockGH := github.NewMockClient(t)
|
|
tt.setupMock(mockGH)
|
|
|
|
// Create repository with mock
|
|
repo := &githubWebhookRepository{
|
|
gh: mockGH,
|
|
config: tt.config,
|
|
owner: "grafana",
|
|
repo: "grafana",
|
|
webhookURL: tt.webhookURL,
|
|
}
|
|
|
|
// Call the OnCreate method
|
|
hookOps, err := repo.OnCreate(context.Background())
|
|
|
|
// Check results
|
|
if tt.expectedError != nil {
|
|
require.Error(t, err)
|
|
require.Equal(t, tt.expectedError.Error(), err.Error())
|
|
require.Nil(t, hookOps)
|
|
} else {
|
|
require.NoError(t, err)
|
|
if tt.expectedHook != nil {
|
|
require.NotNil(t, hookOps)
|
|
require.Len(t, hookOps, 1)
|
|
require.Equal(t, "replace", hookOps[0]["op"])
|
|
require.Equal(t, "/status/webhook", hookOps[0]["path"])
|
|
require.Equal(t, tt.expectedHook.ID, hookOps[0]["value"].(*provisioning.WebhookStatus).ID)
|
|
require.Equal(t, tt.expectedHook.URL, hookOps[0]["value"].(*provisioning.WebhookStatus).URL)
|
|
require.NotEmpty(t, hookOps[0]["value"].(*provisioning.WebhookStatus).Secret) // Secret is randomly generated, so just check it's not empty
|
|
} else {
|
|
require.Nil(t, hookOps)
|
|
}
|
|
}
|
|
|
|
// Verify all mock expectations were met
|
|
mockGH.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubRepository_OnUpdate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(m *github.MockClient)
|
|
config *provisioning.Repository
|
|
webhookURL string
|
|
expectedHook *provisioning.WebhookStatus
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "successfully update webhook when webhook exists",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock getting the existing webhook
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
Events: []string{"push"},
|
|
}, nil)
|
|
|
|
// Mock editing the webhook
|
|
m.On("EditWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
|
return hook.ID == 123 && hook.URL == "https://example.com/webhook-updated" &&
|
|
slices.Equal(hook.Events, subscribedEvents)
|
|
})).Return(nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook-updated",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook-updated",
|
|
SubscribedEvents: subscribedEvents,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "create webhook when it doesn't exist",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock webhook not found
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{}, github.ErrResourceNotFound)
|
|
|
|
// Mock creating a new webhook
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
|
return hook.URL == "https://example.com/webhook" &&
|
|
hook.ContentType == "json" &&
|
|
slices.Equal(hook.Events, subscribedEvents) &&
|
|
hook.Active == true
|
|
})).Return(github.WebhookConfig{
|
|
ID: 456,
|
|
URL: "https://example.com/webhook",
|
|
Events: subscribedEvents,
|
|
}, nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/old-webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 456,
|
|
URL: "https://example.com/webhook",
|
|
SubscribedEvents: subscribedEvents,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "no webhook URL provided",
|
|
setupMock: func(m *github.MockClient) {
|
|
// No mocks needed
|
|
},
|
|
config: &provisioning.Repository{},
|
|
webhookURL: "",
|
|
expectedHook: nil,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "error getting webhook",
|
|
setupMock: func(m *github.MockClient) {
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{}, fmt.Errorf("failed to get webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: nil,
|
|
expectedError: fmt.Errorf("get webhook: failed to get webhook"),
|
|
},
|
|
{
|
|
name: "error editing webhook",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock getting the existing webhook
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
Events: []string{"push"},
|
|
}, nil)
|
|
|
|
// Mock editing the webhook with error
|
|
m.On("EditWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
|
Return(fmt.Errorf("failed to edit webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook-updated",
|
|
expectedHook: nil,
|
|
expectedError: fmt.Errorf("edit webhook: failed to edit webhook"),
|
|
},
|
|
{
|
|
name: "create webhook when webhook status is nil",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock creating a new webhook
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
|
Return(github.WebhookConfig{
|
|
ID: 456,
|
|
URL: "https://example.com/webhook",
|
|
Events: subscribedEvents,
|
|
Active: true,
|
|
ContentType: "json",
|
|
}, nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: nil, // Webhook status is nil
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 456,
|
|
URL: "https://example.com/webhook",
|
|
SubscribedEvents: subscribedEvents,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "create webhook when webhook ID is zero",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock creating a new webhook
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
|
Return(github.WebhookConfig{
|
|
ID: 789,
|
|
URL: "https://example.com/webhook",
|
|
Events: subscribedEvents,
|
|
Active: true,
|
|
ContentType: "json",
|
|
}, nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 0, // Webhook ID is zero
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 789,
|
|
URL: "https://example.com/webhook",
|
|
SubscribedEvents: subscribedEvents,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "error when creating webhook fails",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock webhook creation failure
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.Anything).
|
|
Return(github.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: nil, // Webhook status is nil
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: nil,
|
|
expectedError: fmt.Errorf("failed to create webhook"),
|
|
},
|
|
{
|
|
name: "creates webhook when ErrResourceNotFound",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock webhook not found
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{}, github.ErrResourceNotFound)
|
|
|
|
// Mock creating a new webhook
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
|
return hook.URL == "https://example.com/webhook" &&
|
|
hook.ContentType == "json" &&
|
|
slices.Equal(hook.Events, subscribedEvents) &&
|
|
hook.Active == true
|
|
})).Return(github.WebhookConfig{
|
|
ID: 456,
|
|
URL: "https://example.com/webhook",
|
|
Events: subscribedEvents,
|
|
}, nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/old-webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 456,
|
|
URL: "https://example.com/webhook",
|
|
SubscribedEvents: subscribedEvents,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "error on create when not found",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock webhook not found
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{}, github.ErrResourceNotFound)
|
|
|
|
// Mock error when creating a new webhook
|
|
m.On("CreateWebhook", mock.Anything, "grafana", "grafana", mock.MatchedBy(func(hook github.WebhookConfig) bool {
|
|
return hook.URL == "https://example.com/webhook" &&
|
|
hook.ContentType == "json" &&
|
|
slices.Equal(hook.Events, subscribedEvents) &&
|
|
hook.Active == true
|
|
})).Return(github.WebhookConfig{}, fmt.Errorf("failed to create webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/old-webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: nil,
|
|
expectedError: fmt.Errorf("failed to create webhook"),
|
|
},
|
|
{
|
|
name: "no update needed when URL and events match",
|
|
setupMock: func(m *github.MockClient) {
|
|
// Mock getting the existing webhook with matching URL and events
|
|
m.On("GetWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(github.WebhookConfig{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
Events: subscribedEvents,
|
|
}, nil)
|
|
|
|
// No EditWebhook call expected since no changes needed
|
|
},
|
|
config: &provisioning.Repository{
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
Secret: "secret",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedHook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
SubscribedEvents: subscribedEvents,
|
|
Secret: "secret",
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Setup mock GitHub client
|
|
mockGH := github.NewMockClient(t)
|
|
tt.setupMock(mockGH)
|
|
|
|
// Create repository with mock
|
|
repo := &githubWebhookRepository{
|
|
gh: mockGH,
|
|
config: tt.config,
|
|
owner: "grafana",
|
|
repo: "grafana",
|
|
webhookURL: tt.webhookURL,
|
|
}
|
|
|
|
// Call the OnUpdate method
|
|
hookOps, err := repo.OnUpdate(context.Background())
|
|
|
|
// Check results
|
|
if tt.expectedError != nil {
|
|
require.Error(t, err)
|
|
require.Equal(t, tt.expectedError.Error(), err.Error())
|
|
require.Nil(t, hookOps)
|
|
} else {
|
|
require.NoError(t, err)
|
|
if tt.expectedHook != nil {
|
|
require.NotNil(t, hookOps)
|
|
require.Len(t, hookOps, 1)
|
|
require.Equal(t, "replace", hookOps[0]["op"])
|
|
require.Equal(t, "/status/webhook", hookOps[0]["path"])
|
|
require.Equal(t, tt.expectedHook.ID, hookOps[0]["value"].(*provisioning.WebhookStatus).ID)
|
|
require.Equal(t, tt.expectedHook.URL, hookOps[0]["value"].(*provisioning.WebhookStatus).URL)
|
|
if tt.expectedHook.Secret != "" {
|
|
require.Equal(t, tt.expectedHook.Secret, hookOps[0]["value"].(*provisioning.WebhookStatus).Secret)
|
|
} else {
|
|
require.NotEmpty(t, hookOps[0]["value"].(*provisioning.WebhookStatus).Secret) // Secret is randomly generated, so just check it's not empty
|
|
}
|
|
require.ElementsMatch(t, tt.expectedHook.SubscribedEvents, hookOps[0]["value"].(*provisioning.WebhookStatus).SubscribedEvents)
|
|
} else {
|
|
require.Nil(t, hookOps)
|
|
}
|
|
}
|
|
|
|
// Verify all mock expectations were met
|
|
mockGH.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubRepository_OnDelete(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets)
|
|
config *provisioning.Repository
|
|
webhookURL string
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "successfully delete webhook",
|
|
setupMock: func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
mockSecrets.EXPECT().Delete(mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
// Mock deleting the webhook
|
|
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "no webhook URL provided",
|
|
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "",
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "webhook not found in status",
|
|
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
// No secrets deletion or webhook deletion mocks needed - method returns early when webhook is nil
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: nil, // Webhook status is nil
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedError: nil, // No error expected - method returns early when webhook is nil
|
|
},
|
|
{
|
|
name: "error on delete from basic github repository",
|
|
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(fmt.Errorf("failed to delete webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedError: fmt.Errorf("on delete from basic github repository: failed to delete webhook"),
|
|
},
|
|
{
|
|
name: "error deleting webhook",
|
|
setupMock: func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
mockSecrets.EXPECT().Delete(mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
// Mock webhook deletion failure
|
|
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
|
Return(fmt.Errorf("failed to delete webhook"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedError: fmt.Errorf("delete webhook: failed to delete webhook"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Setup mock GitHub client
|
|
mockGH := github.NewMockClient(t)
|
|
mockRepo := github.NewMockGithubRepository(t)
|
|
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
|
tt.setupMock(mockGH, mockRepo, mockSecrets)
|
|
|
|
// Create repository with mock
|
|
repo := &githubWebhookRepository{
|
|
GithubRepository: mockRepo,
|
|
gh: mockGH,
|
|
config: tt.config,
|
|
secrets: mockSecrets,
|
|
owner: "grafana",
|
|
repo: "grafana",
|
|
webhookURL: tt.webhookURL,
|
|
}
|
|
|
|
// Call the OnDelete method
|
|
err := repo.OnDelete(context.Background())
|
|
|
|
// Check results
|
|
if tt.expectedError != nil {
|
|
require.Error(t, err)
|
|
require.Equal(t, tt.expectedError.Error(), err.Error())
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Verify all mock expectations were met
|
|
mockGH.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitHubRepository_OnDelete_WithSecrets(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets)
|
|
config *provisioning.Repository
|
|
webhookURL string
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "successful deletion with secrets",
|
|
setupMock: func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
mockSecrets.EXPECT().Delete(
|
|
mock.Anything,
|
|
&provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
"test-repo"+webhookSecretSuffix,
|
|
).Return(nil)
|
|
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).Return(nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
},
|
|
{
|
|
name: "secret deletion error",
|
|
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
mockSecrets.EXPECT().Delete(
|
|
mock.Anything,
|
|
&provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
"test-repo"+webhookSecretSuffix,
|
|
).Return(errors.New("failed to delete webhook secret"))
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
Status: provisioning.RepositoryStatus{
|
|
Webhook: &provisioning.WebhookStatus{
|
|
ID: 123,
|
|
URL: "https://example.com/webhook",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "https://example.com/webhook",
|
|
expectedError: "delete webhook secret: failed to delete webhook secret",
|
|
},
|
|
{
|
|
name: "no webhook URL - no secrets deletion",
|
|
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
|
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
|
},
|
|
config: &provisioning.Repository{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-repo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: provisioning.RepositorySpec{
|
|
GitHub: &provisioning.GitHubRepositoryConfig{
|
|
Branch: "main",
|
|
},
|
|
},
|
|
},
|
|
webhookURL: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
mockClient := github.NewMockClient(t)
|
|
mockRepo := github.NewMockGithubRepository(t)
|
|
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
|
tt.setupMock(mockClient, mockRepo, mockSecrets)
|
|
|
|
repo := &githubWebhookRepository{
|
|
GithubRepository: mockRepo,
|
|
gh: mockClient,
|
|
config: tt.config,
|
|
secrets: mockSecrets,
|
|
owner: "grafana",
|
|
repo: "grafana",
|
|
webhookURL: tt.webhookURL,
|
|
}
|
|
|
|
err := repo.OnDelete(context.Background())
|
|
|
|
if tt.expectedError != "" {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tt.expectedError)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
mockClient.AssertExpectations(t)
|
|
mockRepo.AssertExpectations(t)
|
|
mockSecrets.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|