Files
grafana/pkg/registry/apis/provisioning/repository/github_test.go
T
2025-04-10 20:57:42 +03:00

251 lines
8.0 KiB
Go

package repository
import (
context "context"
"fmt"
"net/http"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
)
func TestIsValidGitBranchName(t *testing.T) {
tests := []struct {
name string
branch string
expected bool
}{
{"Valid branch name", "feature/add-tests", true},
{"Valid branch name with numbers", "feature/123-add-tests", true},
{"Valid branch name with dots", "feature.add.tests", true},
{"Valid branch name with hyphens", "feature-add-tests", true},
{"Valid branch name with underscores", "feature_add_tests", true},
{"Valid branch name with mixed characters", "feature/add_tests-123", true},
{"Starts with /", "/feature", false},
{"Ends with /", "feature/", false},
{"Ends with .", "feature.", false},
{"Ends with space", "feature ", false},
{"Contains consecutive slashes", "feature//branch", false},
{"Contains consecutive dots", "feature..branch", false},
{"Contains @{", "feature@{branch", false},
{"Contains invalid character ~", "feature~branch", false},
{"Contains invalid character ^", "feature^branch", false},
{"Contains invalid character :", "feature:branch", false},
{"Contains invalid character ?", "feature?branch", false},
{"Contains invalid character *", "feature*branch", false},
{"Contains invalid character [", "feature[branch", false},
{"Contains invalid character ]", "feature]branch", false},
{"Contains invalid character \\", "feature\\branch", false},
{"Empty branch name", "", false},
{"Only whitespace", " ", false},
{"Single valid character", "a", true},
{"Ends with .lock", "feature.lock", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, isValidGitBranchName(tt.branch))
})
}
}
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 := &githubRepository{
config: &provisioning.Repository{
ObjectMeta: v1.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,
},
},
},
}
var err error
gh.owner, gh.repo, err = parseOwnerRepo(gh.config.Spec.GitHub.URL)
require.NoError(t, err)
// Support parsing from a ".git" extension
owner, repo, err := parseOwnerRepo(gh.config.Spec.GitHub.URL + ".git")
require.NoError(t, err)
require.Equal(t, gh.owner, owner)
require.Equal(t, gh.repo, repo)
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("github", "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 TestReadTree(t *testing.T) {
tests := []struct {
name string
path string
tree []pgh.RepositoryContent
expected []FileTreeEntry
}{
{name: "empty tree", tree: []pgh.RepositoryContent{}, expected: []FileTreeEntry{}},
{name: "single file", tree: func() []pgh.RepositoryContent {
content := pgh.NewMockRepositoryContent(t)
content.EXPECT().GetPath().Return("file.txt")
content.EXPECT().GetSize().Return(int64(100))
content.EXPECT().GetSHA().Return("abc123")
content.EXPECT().IsDirectory().Return(false)
return []pgh.RepositoryContent{content}
}(), expected: []FileTreeEntry{
{Path: "file.txt", Size: 100, Hash: "abc123", Blob: true},
}},
{name: "single directory", tree: func() []pgh.RepositoryContent {
content := pgh.NewMockRepositoryContent(t)
content.EXPECT().GetPath().Return("dir")
content.EXPECT().IsDirectory().Return(true)
content.EXPECT().GetSize().Return(int64(0))
content.EXPECT().GetSHA().Return("")
return []pgh.RepositoryContent{content}
}(), expected: []FileTreeEntry{
{Path: "dir/", Blob: false},
}},
{name: "mixed content", tree: func() []pgh.RepositoryContent {
file1 := pgh.NewMockRepositoryContent(t)
file1.EXPECT().GetPath().Return("file1.txt")
file1.EXPECT().GetSize().Return(int64(100))
file1.EXPECT().GetSHA().Return("abc123")
file1.EXPECT().IsDirectory().Return(false)
dir := pgh.NewMockRepositoryContent(t)
dir.EXPECT().GetPath().Return("dir")
dir.EXPECT().IsDirectory().Return(true)
dir.EXPECT().GetSize().Return(int64(0))
dir.EXPECT().GetSHA().Return("")
file2 := pgh.NewMockRepositoryContent(t)
file2.EXPECT().GetPath().Return("file2.txt")
file2.EXPECT().GetSize().Return(int64(200))
file2.EXPECT().GetSHA().Return("def456")
file2.EXPECT().IsDirectory().Return(false)
return []pgh.RepositoryContent{file1, dir, file2}
}(), expected: []FileTreeEntry{
{Path: "file1.txt", Size: 100, Hash: "abc123", Blob: true},
{Path: "dir/", Blob: false},
{Path: "file2.txt", Size: 200, Hash: "def456", Blob: true},
}},
{name: "with path prefix", path: "prefix", tree: func() []pgh.RepositoryContent {
file := pgh.NewMockRepositoryContent(t)
file.EXPECT().GetPath().Return("file.txt")
file.EXPECT().GetSize().Return(int64(100))
file.EXPECT().GetSHA().Return("abc123")
file.EXPECT().IsDirectory().Return(false)
dir := pgh.NewMockRepositoryContent(t)
dir.EXPECT().GetPath().Return("dir")
dir.EXPECT().GetSize().Return(int64(0))
dir.EXPECT().GetSHA().Return("")
dir.EXPECT().IsDirectory().Return(true)
return []pgh.RepositoryContent{file, dir}
}(), expected: []FileTreeEntry{
{Path: "file.txt", Size: 100, Hash: "abc123", Blob: true},
{Path: "dir/", Blob: false},
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ghMock := pgh.NewMockClient(t)
gh := &githubRepository{
owner: "owner",
repo: "repo",
config: &provisioning.Repository{
Spec: provisioning.RepositorySpec{
GitHub: &provisioning.GitHubRepositoryConfig{
Path: tt.path,
},
},
},
gh: ghMock,
}
ghMock.On("GetTree", mock.Anything, "owner", "repo", tt.path, "some-ref", true).Return(tt.tree, false, nil)
tree, err := gh.ReadTree(context.Background(), "some-ref")
require.NoError(t, err)
require.Equal(t, tt.expected, tree)
})
}
}