The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
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.
grafana/pkg/registry/apis/provisioning/webhooks/repository_test.go

1677 lines
49 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.MockService)
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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("encrypted-secret")).
Return([]byte("webhook-secret"), nil)
},
expectedError: fmt.Errorf("expected PR in event"),
},
{
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.MockService) {
mockSecrets.EXPECT().Decrypt(mock.Anything, []byte("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.NewMockService(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)
config *provisioning.Repository
webhookURL string
expectedError error
}{
{
name: "successfully delete webhook",
setupMock: func(m *github.MockClient) {
// Mock deleting the webhook
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
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",
expectedError: nil,
},
{
name: "no webhook URL provided",
setupMock: func(m *github.MockClient) {
// No mocks needed
},
config: &provisioning.Repository{},
webhookURL: "",
expectedError: nil,
},
{
name: "webhook not found in status",
setupMock: func(m *github.MockClient) {
// No mocks needed
},
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",
expectedError: fmt.Errorf("webhook not found"),
},
{
name: "error deleting webhook",
setupMock: func(m *github.MockClient) {
// Mock webhook deletion failure
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
Return(fmt.Errorf("failed to delete 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",
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)
tt.setupMock(mockGH)
// Create repository with mock
repo := &githubWebhookRepository{
gh: mockGH,
config: tt.config,
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)
})
}
}