mirror of https://github.com/grafana/grafana.git
1722 lines
50 KiB
Go
1722 lines
50 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-github/v70/github"
|
|
mockhub "github.com/migueleliasweb/go-github-mock/src/mock"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestGithubClient_GetCommits(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
branch string
|
|
path string
|
|
since time.Time
|
|
until time.Time
|
|
wantCommits []Commit
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "get commits successfully",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposCommitsByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
commits := []*github.RepositoryCommit{
|
|
{
|
|
SHA: github.Ptr("abc123"),
|
|
Commit: &github.Commit{
|
|
Message: github.Ptr("First commit"),
|
|
Author: &github.CommitAuthor{
|
|
Name: github.Ptr("Test User"),
|
|
Email: github.Ptr("test@example.com"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)},
|
|
},
|
|
Committer: &github.CommitAuthor{
|
|
Name: github.Ptr("Test User"),
|
|
Email: github.Ptr("test@example.com"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)},
|
|
},
|
|
},
|
|
Author: &github.User{
|
|
Login: github.Ptr("test-user"),
|
|
AvatarURL: github.Ptr("https://avatar.url"),
|
|
},
|
|
Committer: &github.User{
|
|
Login: github.Ptr("test-user"),
|
|
AvatarURL: github.Ptr("https://avatar.url"),
|
|
},
|
|
},
|
|
{
|
|
SHA: github.Ptr("def456"),
|
|
Commit: &github.Commit{
|
|
Message: github.Ptr("Second commit"),
|
|
Author: &github.CommitAuthor{
|
|
Name: github.Ptr("Another User"),
|
|
Email: github.Ptr("another@example.com"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC)},
|
|
},
|
|
Committer: &github.CommitAuthor{
|
|
Name: github.Ptr("Another User"),
|
|
Email: github.Ptr("another@example.com"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC)},
|
|
},
|
|
},
|
|
Author: &github.User{
|
|
Login: github.Ptr("another-user"),
|
|
AvatarURL: github.Ptr("https://another.avatar.url"),
|
|
},
|
|
Committer: &github.User{
|
|
Login: github.Ptr("another-user"),
|
|
AvatarURL: github.Ptr("https://another.avatar.url"),
|
|
},
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(commits))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
branch: "main",
|
|
path: "test.txt",
|
|
since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
|
|
wantCommits: []Commit{
|
|
{
|
|
Ref: "abc123",
|
|
Message: "First commit",
|
|
Author: &CommitAuthor{
|
|
Name: "Test User",
|
|
Username: "test-user",
|
|
AvatarURL: "https://avatar.url",
|
|
},
|
|
Committer: &CommitAuthor{
|
|
Name: "Test User",
|
|
Username: "test-user",
|
|
AvatarURL: "https://avatar.url",
|
|
},
|
|
CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
|
|
},
|
|
{
|
|
Ref: "def456",
|
|
Message: "Second commit",
|
|
Author: &CommitAuthor{
|
|
Name: "Another User",
|
|
Username: "another-user",
|
|
AvatarURL: "https://another.avatar.url",
|
|
},
|
|
Committer: &CommitAuthor{
|
|
Name: "Another User",
|
|
Username: "another-user",
|
|
AvatarURL: "https://another.avatar.url",
|
|
},
|
|
CreatedAt: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC),
|
|
},
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "repository not found",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposCommitsByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
Message: "Not Found",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "nonexistent-repo",
|
|
branch: "main",
|
|
path: "test.txt",
|
|
since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
|
|
wantCommits: nil,
|
|
wantErr: ErrResourceNotFound,
|
|
},
|
|
{
|
|
name: "commits missing author",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposCommitsByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
commits := []*github.RepositoryCommit{
|
|
{
|
|
SHA: github.Ptr("abc123"),
|
|
Commit: &github.Commit{
|
|
Message: github.Ptr("First commit"),
|
|
Author: &github.CommitAuthor{
|
|
Name: github.Ptr("Test User"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)},
|
|
Email: github.Ptr("test@example.com"),
|
|
},
|
|
Committer: &github.CommitAuthor{
|
|
Name: github.Ptr("Test User"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)},
|
|
Email: github.Ptr("test@example.com"),
|
|
},
|
|
},
|
|
// Author is nil
|
|
Committer: &github.User{
|
|
Login: github.Ptr("test-user"),
|
|
AvatarURL: github.Ptr("https://avatar.url"),
|
|
},
|
|
},
|
|
{
|
|
SHA: github.Ptr("def456"),
|
|
Commit: &github.Commit{
|
|
Message: github.Ptr("Second commit"),
|
|
// Missing Author in Commit
|
|
Committer: &github.CommitAuthor{
|
|
Name: github.Ptr("Another User"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 2, 12, 0, 0, 0, time.UTC)},
|
|
Email: github.Ptr("another@example.com"),
|
|
},
|
|
},
|
|
Author: &github.User{
|
|
Login: github.Ptr("another-user"),
|
|
AvatarURL: github.Ptr("https://another.avatar.url"),
|
|
},
|
|
Committer: &github.User{
|
|
Login: github.Ptr("another-user"),
|
|
AvatarURL: github.Ptr("https://another.avatar.url"),
|
|
},
|
|
},
|
|
}
|
|
require.NoError(t, json.NewEncoder(w).Encode(commits))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
branch: "main",
|
|
path: "test.txt",
|
|
since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
|
|
wantCommits: []Commit{
|
|
{
|
|
Ref: "abc123",
|
|
Message: "First commit",
|
|
Author: &CommitAuthor{
|
|
Name: "Test User",
|
|
// Username and AvatarURL are empty because Author is nil in the response
|
|
},
|
|
Committer: &CommitAuthor{
|
|
Name: "Test User",
|
|
Username: "test-user",
|
|
AvatarURL: "https://avatar.url",
|
|
},
|
|
CreatedAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
|
|
},
|
|
{
|
|
Ref: "def456",
|
|
Message: "Second commit",
|
|
Author: nil, // Author is nil because Commit.Author is nil in the response
|
|
Committer: &CommitAuthor{
|
|
Name: "Another User",
|
|
Username: "another-user",
|
|
AvatarURL: "https://another.avatar.url",
|
|
},
|
|
},
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "too many commits",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposCommitsByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
// Return a large number of commits that would exceed the maxCommits limit
|
|
commits := make([]*github.RepositoryCommit, maxCommits+1)
|
|
for i := 0; i < maxCommits+1; i++ {
|
|
commits[i] = &github.RepositoryCommit{
|
|
SHA: github.Ptr(fmt.Sprintf("commit%d", i)),
|
|
Commit: &github.Commit{
|
|
Message: github.Ptr(fmt.Sprintf("Commit %d", i)),
|
|
Author: &github.CommitAuthor{
|
|
Name: github.Ptr("Test User"),
|
|
Date: &github.Timestamp{Time: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)},
|
|
Email: github.Ptr("test@example.com"),
|
|
},
|
|
},
|
|
Author: &github.User{
|
|
Login: github.Ptr("test-user"),
|
|
AvatarURL: github.Ptr("https://avatar.url"),
|
|
},
|
|
}
|
|
}
|
|
require.NoError(t, json.NewEncoder(w).Encode(commits))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
branch: "main",
|
|
path: "test.txt",
|
|
since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
|
|
wantCommits: nil,
|
|
wantErr: fmt.Errorf("too many commits to fetch (more than %d)", maxCommits),
|
|
},
|
|
{
|
|
name: "service unavailable",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposCommitsByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
branch: "main",
|
|
path: "test.txt",
|
|
since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
|
|
wantCommits: nil,
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposCommitsByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
branch: "main",
|
|
path: "test.txt",
|
|
since: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
until: time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC),
|
|
wantCommits: nil,
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
commits, err := client.Commits(context.Background(), tt.owner, tt.repository, tt.branch, tt.path)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
assert.Nil(t, commits)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wantCommits, commits)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_ListWebhooks(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
wantWebhooks []WebhookConfig
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful webhooks listing",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
hooks := []*github.Hook{
|
|
{
|
|
ID: github.Ptr(int64(1)),
|
|
Events: []string{"push", "pull_request"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook1"),
|
|
ContentType: github.Ptr("json"),
|
|
},
|
|
},
|
|
{
|
|
ID: github.Ptr(int64(2)),
|
|
Events: []string{"issues"},
|
|
Active: github.Ptr(false),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook2"),
|
|
ContentType: github.Ptr(""),
|
|
},
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(hooks))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
wantWebhooks: []WebhookConfig{
|
|
{
|
|
ID: 1,
|
|
Events: []string{"push", "pull_request"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook1",
|
|
ContentType: "json",
|
|
},
|
|
{
|
|
ID: 2,
|
|
Events: []string{"issues"},
|
|
Active: false,
|
|
URL: "https://example.com/webhook2",
|
|
ContentType: "form", // Default value when empty
|
|
},
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "empty webhooks list",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
hooks := []*github.Hook{}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(hooks))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
wantWebhooks: []WebhookConfig{},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "too many webhooks",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
// Create more webhooks than the maxWebhooks limit
|
|
hooks := make([]*github.Hook, maxWebhooks+1)
|
|
for i := 0; i < maxWebhooks+1; i++ {
|
|
hooks[i] = &github.Hook{
|
|
ID: github.Ptr(int64(i + 1)),
|
|
Events: []string{"push"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr(fmt.Sprintf("https://example.com/webhook%d", i+1)),
|
|
ContentType: github.Ptr("json"),
|
|
},
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(hooks))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
wantWebhooks: nil,
|
|
wantErr: fmt.Errorf("too many webhooks configured (more than %d)", maxWebhooks),
|
|
},
|
|
{
|
|
name: "service unavailable error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
wantWebhooks: nil,
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
wantWebhooks: nil,
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
webhooks, err := client.ListWebhooks(context.Background(), tt.owner, tt.repository)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Check the result
|
|
assert.Equal(t, tt.wantWebhooks, webhooks)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_CreateWebhook(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
config WebhookConfig
|
|
want WebhookConfig
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful webhook creation",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify the request body contains the correct webhook config
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
|
|
hook := &github.Hook{}
|
|
require.NoError(t, json.Unmarshal(body, hook))
|
|
|
|
assert.Equal(t, "https://example.com/webhook", hook.Config.GetURL())
|
|
assert.Equal(t, "json", hook.Config.GetContentType())
|
|
assert.Equal(t, "secret123", hook.Config.GetSecret())
|
|
assert.Equal(t, []string{"push", "pull_request"}, hook.Events)
|
|
assert.True(t, hook.GetActive())
|
|
|
|
// Return a created hook
|
|
createdHook := &github.Hook{
|
|
ID: github.Ptr(int64(123)),
|
|
Events: []string{"push", "pull_request"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook"),
|
|
ContentType: github.Ptr("json"),
|
|
// Secret is not returned by GitHub API
|
|
},
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
require.NoError(t, json.NewEncoder(w).Encode(createdHook))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
Events: []string{"push", "pull_request"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
Secret: "secret123",
|
|
},
|
|
want: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push", "pull_request"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
Secret: "secret123",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "default content type to form",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
|
|
hook := &github.Hook{}
|
|
require.NoError(t, json.Unmarshal(body, hook))
|
|
|
|
// Verify content type was defaulted to "form"
|
|
assert.Equal(t, "form", hook.Config.GetContentType())
|
|
|
|
createdHook := &github.Hook{
|
|
ID: github.Ptr(int64(123)),
|
|
Events: []string{"push"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook"),
|
|
ContentType: github.Ptr("form"),
|
|
},
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
require.NoError(t, json.NewEncoder(w).Encode(createdHook))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
Secret: "secret123",
|
|
// ContentType intentionally omitted
|
|
},
|
|
want: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "form",
|
|
Secret: "secret123",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "service unavailable error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
Secret: "secret123",
|
|
},
|
|
want: WebhookConfig{},
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposHooksByOwnerByRepo,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
Secret: "secret123",
|
|
},
|
|
want: WebhookConfig{},
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
got, err := client.CreateWebhook(context.Background(), tt.owner, tt.repository, tt.config)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Check the result
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_GetWebhook(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
webhookID int64
|
|
want WebhookConfig
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful webhook retrieval",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
hook := &github.Hook{
|
|
ID: github.Ptr(int64(123)),
|
|
Events: []string{"push", "pull_request"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook"),
|
|
ContentType: github.Ptr("json"),
|
|
// Secret is not returned by GitHub API
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(hook))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 123,
|
|
want: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push", "pull_request"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
|
|
{
|
|
name: "empty content type defaults to json",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
hook := &github.Hook{
|
|
ID: github.Ptr(int64(456)),
|
|
Events: []string{"push"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook-empty-content"),
|
|
ContentType: github.Ptr(""), // Empty content type
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(hook))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 456,
|
|
want: WebhookConfig{
|
|
ID: 456,
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook-empty-content",
|
|
ContentType: "json", // Should default to "json"
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "webhook not found",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
Message: "Not Found",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 999,
|
|
want: WebhookConfig{},
|
|
wantErr: ErrResourceNotFound,
|
|
},
|
|
{
|
|
name: "service unavailable",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service Unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 123,
|
|
want: WebhookConfig{},
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 123,
|
|
want: WebhookConfig{},
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
got, err := client.GetWebhook(context.Background(), tt.owner, tt.repository, tt.webhookID)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Check the result
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_DeleteWebhook(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
webhookID int64
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful webhook deletion",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.DeleteReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 123,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "webhook not found",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.DeleteReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
Message: "Not found",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 456,
|
|
wantErr: ErrResourceNotFound,
|
|
},
|
|
{
|
|
name: "service unavailable",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.DeleteReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 789,
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "unauthorized to delete the webhook",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.DeleteReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusUnauthorized,
|
|
},
|
|
Message: "401 bad credentials",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 789,
|
|
wantErr: ErrUnauthorized,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.DeleteReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
webhookID: 101,
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
err := client.DeleteWebhook(context.Background(), tt.owner, tt.repository, tt.webhookID)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_EditWebhook(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
config WebhookConfig
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful webhook edit",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PatchReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify the request body contains the correct webhook config
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
|
|
hook := &github.Hook{}
|
|
require.NoError(t, json.Unmarshal(body, hook))
|
|
|
|
assert.Equal(t, "https://example.com/webhook-updated", hook.Config.GetURL())
|
|
assert.Equal(t, "json", hook.Config.GetContentType())
|
|
assert.Equal(t, "updated-secret", hook.Config.GetSecret())
|
|
assert.Equal(t, []string{"push", "pull_request", "issues"}, hook.Events)
|
|
assert.True(t, hook.GetActive())
|
|
|
|
// Return the updated hook
|
|
updatedHook := &github.Hook{
|
|
ID: github.Ptr(int64(123)),
|
|
Events: []string{"push", "pull_request", "issues"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook-updated"),
|
|
ContentType: github.Ptr("json"),
|
|
// Secret is not returned by GitHub API
|
|
},
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(updatedHook))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push", "pull_request", "issues"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook-updated",
|
|
ContentType: "json",
|
|
Secret: "updated-secret",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "default content type to form",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PatchReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify the request body contains the correct webhook config
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
|
|
hook := &github.Hook{}
|
|
require.NoError(t, json.Unmarshal(body, hook))
|
|
|
|
// Verify content type was defaulted to "form"
|
|
assert.Equal(t, "form", hook.Config.GetContentType())
|
|
assert.Equal(t, "https://example.com/webhook", hook.Config.GetURL())
|
|
assert.Equal(t, "secret123", hook.Config.GetSecret())
|
|
assert.Equal(t, []string{"push"}, hook.Events)
|
|
assert.True(t, hook.GetActive())
|
|
|
|
// Return the updated hook
|
|
updatedHook := &github.Hook{
|
|
ID: github.Ptr(int64(123)),
|
|
Events: []string{"push"},
|
|
Active: github.Ptr(true),
|
|
Config: &github.HookConfig{
|
|
URL: github.Ptr("https://example.com/webhook"),
|
|
ContentType: github.Ptr("form"),
|
|
// Secret is not returned by GitHub API
|
|
},
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(updatedHook))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "", // Empty content type should default to "form"
|
|
Secret: "secret123",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "service unavailable error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PatchReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
Secret: "secret123",
|
|
},
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PatchReposHooksByOwnerByRepoByHookId,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
config: WebhookConfig{
|
|
ID: 123,
|
|
Events: []string{"push"},
|
|
Active: true,
|
|
URL: "https://example.com/webhook",
|
|
ContentType: "json",
|
|
Secret: "secret123",
|
|
},
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
err := client.EditWebhook(context.Background(), tt.owner, tt.repository, tt.config)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_ListPullRequestFiles(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
number int
|
|
wantFiles []CommitFile
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful pull request files listing",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
files := []*github.CommitFile{
|
|
{
|
|
Filename: github.Ptr("file1.txt"),
|
|
Additions: github.Ptr(10),
|
|
Deletions: github.Ptr(5),
|
|
Changes: github.Ptr(15),
|
|
Status: github.Ptr("modified"),
|
|
Patch: github.Ptr("@@ -1,5 +1,10 @@"),
|
|
},
|
|
{
|
|
Filename: github.Ptr("file2.txt"),
|
|
Additions: github.Ptr(20),
|
|
Deletions: github.Ptr(0),
|
|
Changes: github.Ptr(20),
|
|
Status: github.Ptr("added"),
|
|
Patch: github.Ptr("@@ -0,0 +1,20 @@"),
|
|
},
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(files))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 123,
|
|
wantFiles: []CommitFile{
|
|
&github.CommitFile{
|
|
Filename: github.Ptr("file1.txt"),
|
|
Additions: github.Ptr(10),
|
|
Deletions: github.Ptr(5),
|
|
Changes: github.Ptr(15),
|
|
Status: github.Ptr("modified"),
|
|
Patch: github.Ptr("@@ -1,5 +1,10 @@"),
|
|
},
|
|
&github.CommitFile{
|
|
Filename: github.Ptr("file2.txt"),
|
|
Additions: github.Ptr(20),
|
|
Deletions: github.Ptr(0),
|
|
Changes: github.Ptr(20),
|
|
Status: github.Ptr("added"),
|
|
Patch: github.Ptr("@@ -0,0 +1,20 @@"),
|
|
},
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "empty files list",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
files := []*github.CommitFile{}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(files))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 456,
|
|
wantFiles: []CommitFile{},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "too many files",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
// Create more files than the maxPRFiles limit
|
|
files := make([]*github.CommitFile, maxPRFiles+1)
|
|
for i := 0; i < maxPRFiles+1; i++ {
|
|
files[i] = &github.CommitFile{
|
|
Filename: github.Ptr(fmt.Sprintf("file%d.txt", i+1)),
|
|
Additions: github.Ptr(i + 1),
|
|
Deletions: github.Ptr(0),
|
|
Changes: github.Ptr(i + 1),
|
|
Status: github.Ptr("added"),
|
|
}
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
require.NoError(t, json.NewEncoder(w).Encode(files))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 789,
|
|
wantFiles: nil,
|
|
wantErr: fmt.Errorf("pull request contains too many files (more than %d)", maxPRFiles),
|
|
},
|
|
{
|
|
name: "service unavailable error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 101,
|
|
wantFiles: nil,
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.GetReposPullsFilesByOwnerByRepoByPullNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 202,
|
|
wantFiles: nil,
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
files, err := client.ListPullRequestFiles(context.Background(), tt.owner, tt.repository, tt.number)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// Check the result
|
|
assert.Equal(t, tt.wantFiles, files)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreatePullRequestComment(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockHandler *http.Client
|
|
owner string
|
|
repository string
|
|
number int
|
|
body string
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "successful comment creation",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposIssuesCommentsByOwnerByRepoByIssueNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify the request body contains the correct comment
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
|
|
comment := &github.IssueComment{}
|
|
require.NoError(t, json.Unmarshal(body, comment))
|
|
assert.Equal(t, "Test comment", comment.GetBody())
|
|
|
|
// Return the created comment
|
|
createdComment := &github.IssueComment{
|
|
ID: github.Ptr(int64(123)),
|
|
Body: github.Ptr("Test comment"),
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
require.NoError(t, json.NewEncoder(w).Encode(createdComment))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 101,
|
|
body: "Test comment",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "service unavailable error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposIssuesCommentsByOwnerByRepoByIssueNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
Message: "Service unavailable",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 101,
|
|
body: "Test comment",
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "other error",
|
|
mockHandler: mockhub.NewMockedHTTPClient(
|
|
mockhub.WithRequestMatchHandler(
|
|
mockhub.PostReposIssuesCommentsByOwnerByRepoByIssueNumber,
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusInternalServerError,
|
|
},
|
|
Message: "Internal server error",
|
|
}))
|
|
}),
|
|
),
|
|
),
|
|
owner: "test-owner",
|
|
repository: "test-repo",
|
|
number: 101,
|
|
body: "Test comment",
|
|
wantErr: errors.New("Internal server error"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a mock client
|
|
factory := ProvideFactory()
|
|
factory.Client = tt.mockHandler
|
|
client := factory.New(context.Background(), "")
|
|
|
|
// Call the method being tested
|
|
err := client.CreatePullRequestComment(context.Background(), tt.owner, tt.repository, tt.number, tt.body)
|
|
|
|
// Check the error
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPaginatedList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mockSetup func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions)
|
|
want []string
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "single page",
|
|
mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) {
|
|
items := []string{"item1", "item2", "item3"}
|
|
listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) {
|
|
return items, &github.Response{
|
|
NextPage: 0,
|
|
}, nil
|
|
}
|
|
return listFn, defaultListOptions(100)
|
|
},
|
|
want: []string{"item1", "item2", "item3"},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "multiple pages",
|
|
mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) {
|
|
page1 := []string{"item1", "item2"}
|
|
page2 := []string{"item3", "item4"}
|
|
page3 := []string{"item5"}
|
|
|
|
var callCount int
|
|
listFn := func(_ context.Context, opts *github.ListOptions) ([]string, *github.Response, error) {
|
|
callCount++
|
|
switch callCount {
|
|
case 1:
|
|
return page1, &github.Response{
|
|
NextPage: 2,
|
|
}, nil
|
|
case 2:
|
|
assert.Equal(t, 2, opts.Page)
|
|
return page2, &github.Response{
|
|
NextPage: 3,
|
|
}, nil
|
|
case 3:
|
|
assert.Equal(t, 3, opts.Page)
|
|
return page3, &github.Response{
|
|
NextPage: 0,
|
|
}, nil
|
|
default:
|
|
return nil, nil, errors.New("unexpected call")
|
|
}
|
|
}
|
|
return listFn, defaultListOptions(100)
|
|
},
|
|
want: []string{"item1", "item2", "item3", "item4", "item5"},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "error on first page",
|
|
mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) {
|
|
listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) {
|
|
return nil, &github.Response{}, errors.New("API error")
|
|
}
|
|
return listFn, defaultListOptions(100)
|
|
},
|
|
want: nil,
|
|
wantErr: errors.New("API error"),
|
|
},
|
|
{
|
|
name: "service unavailable error",
|
|
mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) {
|
|
listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) {
|
|
return nil, &github.Response{}, &github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
},
|
|
}
|
|
}
|
|
return listFn, defaultListOptions(100)
|
|
},
|
|
want: nil,
|
|
wantErr: ErrServiceUnavailable,
|
|
},
|
|
{
|
|
name: "resource not found error",
|
|
mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) {
|
|
listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) {
|
|
return nil, &github.Response{}, &github.ErrorResponse{
|
|
Response: &http.Response{
|
|
StatusCode: http.StatusNotFound,
|
|
},
|
|
}
|
|
}
|
|
return listFn, defaultListOptions(100)
|
|
},
|
|
want: nil,
|
|
wantErr: ErrResourceNotFound,
|
|
},
|
|
{
|
|
name: "too many items error",
|
|
mockSetup: func() (func(context.Context, *github.ListOptions) ([]string, *github.Response, error), listOptions) {
|
|
listFn := func(_ context.Context, _ *github.ListOptions) ([]string, *github.Response, error) {
|
|
// Return more items than the max allowed
|
|
items := make([]string, 10)
|
|
for i := range items {
|
|
items[i] = fmt.Sprintf("item%d", i+1)
|
|
}
|
|
return items, &github.Response{
|
|
NextPage: 2,
|
|
}, nil
|
|
}
|
|
return listFn, listOptions{
|
|
ListOptions: github.ListOptions{
|
|
Page: 1,
|
|
PerPage: 100,
|
|
},
|
|
MaxItems: 5, // Set max items to less than what we'll return
|
|
}
|
|
},
|
|
want: nil,
|
|
wantErr: ErrTooManyItems,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
listFn, opts := tt.mockSetup()
|
|
|
|
got, err := paginatedList(context.Background(), listFn, opts)
|
|
|
|
if tt.wantErr != nil {
|
|
assert.Error(t, err)
|
|
if errors.Is(err, tt.wantErr) {
|
|
assert.Equal(t, tt.wantErr, err)
|
|
} else {
|
|
assert.Contains(t, err.Error(), tt.wantErr.Error())
|
|
}
|
|
assert.Nil(t, got)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultListOptions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
maxItems int
|
|
want listOptions
|
|
}{
|
|
{
|
|
name: "with zero max items",
|
|
maxItems: 0,
|
|
want: listOptions{
|
|
ListOptions: github.ListOptions{
|
|
Page: 1,
|
|
PerPage: 100,
|
|
},
|
|
MaxItems: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "with positive max items",
|
|
maxItems: 50,
|
|
want: listOptions{
|
|
ListOptions: github.ListOptions{
|
|
Page: 1,
|
|
PerPage: 100,
|
|
},
|
|
MaxItems: 50,
|
|
},
|
|
},
|
|
{
|
|
name: "with large max items",
|
|
maxItems: 1000,
|
|
want: listOptions{
|
|
ListOptions: github.ListOptions{
|
|
Page: 1,
|
|
PerPage: 100,
|
|
},
|
|
MaxItems: 1000,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := defaultListOptions(tt.maxItems)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|