You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gitea/tests/integration/repo_webhook_test.go

604 lines
21 KiB
Go

// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/tests"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
)
func TestNewWebHookLink(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
baseurl := "/user2/repo1/settings/hooks"
tests := []string{
// webhook list page
baseurl,
// new webhook page
baseurl + "/gitea/new",
// edit webhook page
baseurl + "/1",
}
for _, url := range tests {
resp := session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
menus := htmlDoc.doc.Find(".ui.top.attached.header .ui.dropdown .menu a")
menus.Each(func(i int, menu *goquery.Selection) {
url, exist := menu.Attr("href")
assert.True(t, exist)
assert.True(t, strings.HasPrefix(url, baseurl))
})
}
}
func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{
Type: "gitea",
Config: api.CreateHookOptionConfig{
"content_type": "json",
"url": url,
},
Events: []string{event},
Active: true,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
}
func testCreateWebhookForRepo(t *testing.T, session *TestSession, webhookType, userName, repoName, url, eventKind string) {
csrf := GetUserCSRFToken(t, session)
req := NewRequestWithValues(t, "POST", "/"+userName+"/"+repoName+"/settings/hooks/"+webhookType+"/new", map[string]string{
"_csrf": csrf,
"payload_url": url,
"events": eventKind,
"active": "true",
"content_type": fmt.Sprintf("%d", webhook.ContentTypeJSON),
"http_method": "POST",
})
session.MakeRequest(t, req, http.StatusSeeOther)
}
func testAPICreateWebhookForOrg(t *testing.T, session *TestSession, userName, url, event string) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/"+userName+"/hooks", api.CreateHookOption{
Type: "gitea",
Config: api.CreateHookOptionConfig{
"content_type": "json",
"url": url,
},
Events: []string{event},
Active: true,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
}
type mockWebhookProvider struct {
server *httptest.Server
}
func newMockWebhookProvider(callback func(r *http.Request), status int) *mockWebhookProvider {
m := &mockWebhookProvider{}
m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callback(r)
w.WriteHeader(status)
}))
return m
}
func (m *mockWebhookProvider) URL() string {
if m.server == nil {
return ""
}
return m.server.URL
}
// Close closes the mock webhook http server
func (m *mockWebhookProvider) Close() {
if m.server != nil {
m.server.Close()
m.server = nil
}
}
func Test_WebhookCreate(t *testing.T) {
var payloads []api.CreatePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.CreatePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = string(webhook_module.HookEventCreate)
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "create")
// 2. trigger the webhook
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
// 3. validate the webhook is triggered
assert.Len(t, payloads, 1)
assert.EqualValues(t, string(webhook_module.HookEventCreate), triggeredEvent)
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
assert.EqualValues(t, "master2", payloads[0].Ref)
assert.EqualValues(t, "branch", payloads[0].RefType)
})
}
func Test_WebhookDelete(t *testing.T) {
var payloads []api.DeletePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.DeletePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "delete"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "delete")
// 2. trigger the webhook
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
testAPIDeleteBranch(t, "master2", http.StatusNoContent)
// 3. validate the webhook is triggered
assert.EqualValues(t, "delete", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
assert.EqualValues(t, "master2", payloads[0].Ref)
assert.EqualValues(t, "branch", payloads[0].RefType)
})
}
func Test_WebhookFork(t *testing.T) {
var payloads []api.ForkPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.ForkPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "fork"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "fork")
// 2. trigger the webhook
testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master")
// 3. validate the webhook is triggered
assert.EqualValues(t, "fork", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "repo1-fork", payloads[0].Repo.Name)
assert.EqualValues(t, "user1/repo1-fork", payloads[0].Repo.FullName)
assert.EqualValues(t, "repo1", payloads[0].Forkee.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Forkee.FullName)
})
}
func Test_WebhookIssueComment(t *testing.T) {
var payloads []api.IssueCommentPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssueCommentPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "issue_comment"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment")
// 2. trigger the webhook
issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
// 3. validate the webhook is triggered
assert.EqualValues(t, "issue_comment", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "created", payloads[0].Action)
assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
assert.EqualValues(t, "Title2", payloads[0].Issue.Title)
assert.EqualValues(t, "Description2", payloads[0].Issue.Body)
assert.EqualValues(t, "issue title2 comment1", payloads[0].Comment.Body)
})
}
func Test_WebhookRelease(t *testing.T) {
var payloads []api.ReleasePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.ReleasePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "release"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "release")
// 2. trigger the webhook
createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false)
// 3. validate the webhook is triggered
assert.EqualValues(t, "release", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "repo1", payloads[0].Repository.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName)
assert.EqualValues(t, "v0.0.99", payloads[0].Release.TagName)
assert.False(t, payloads[0].Release.IsDraft)
assert.False(t, payloads[0].Release.IsPrerelease)
})
}
func Test_WebhookPush(t *testing.T) {
var payloads []api.PushPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PushPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "push"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push")
// 2. trigger the webhook
testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")
// 3. validate the webhook is triggered
assert.EqualValues(t, "push", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
assert.Len(t, payloads[0].Commits, 1)
assert.EqualValues(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
})
}
func Test_WebhookIssue(t *testing.T) {
var payloads []api.IssuePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssuePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "issues"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues")
// 2. trigger the webhook
testNewIssue(t, session, "user2", "repo1", "Title1", "Description1")
// 3. validate the webhook is triggered
assert.EqualValues(t, "issues", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "opened", payloads[0].Action)
assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
assert.EqualValues(t, "Title1", payloads[0].Issue.Title)
assert.EqualValues(t, "Description1", payloads[0].Issue.Body)
})
}
func Test_WebhookPullRequest(t *testing.T) {
var payloads []api.PullRequestPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PullRequestPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request")
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
// 2. trigger the webhook
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
// 3. validate the webhook is triggered
assert.EqualValues(t, "pull_request", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
assert.EqualValues(t, "repo1", payloads[0].PullRequest.Head.Repository.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName)
assert.EqualValues(t, 0, payloads[0].PullRequest.Additions)
})
}
func Test_WebhookPullRequestComment(t *testing.T) {
var payloads []api.IssueCommentPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.IssueCommentPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "pull_request_comment"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_comment")
// 2. trigger the webhook
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
prID := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
testIssueAddComment(t, session, "/user2/repo1/pulls/"+prID, "pull title2 comment1", "")
// 3. validate the webhook is triggered
assert.EqualValues(t, "pull_request_comment", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "created", payloads[0].Action)
assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
assert.EqualValues(t, "first pull request", payloads[0].Issue.Title)
assert.EqualValues(t, "", payloads[0].Issue.Body)
assert.EqualValues(t, "pull title2 comment1", payloads[0].Comment.Body)
})
}
func Test_WebhookWiki(t *testing.T) {
var payloads []api.WikiPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.WikiPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "wiki"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "wiki")
// 2. trigger the webhook
testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated)
// 3. validate the webhook is triggered
assert.EqualValues(t, "wiki", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "created", payloads[0].Action)
assert.EqualValues(t, "repo1", payloads[0].Repository.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName)
assert.EqualValues(t, "Test-Wiki-Page", payloads[0].Page)
})
}
func Test_WebhookRepository(t *testing.T) {
var payloads []api.RepositoryPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.RepositoryPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "repository"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1")
testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "repository")
// 2. trigger the webhook
testAPIOrgCreateRepo(t, session, "org3", "repo_new", http.StatusCreated)
// 3. validate the webhook is triggered
assert.EqualValues(t, "repository", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "created", payloads[0].Action)
assert.EqualValues(t, "org3", payloads[0].Organization.UserName)
assert.EqualValues(t, "repo_new", payloads[0].Repository.Name)
assert.EqualValues(t, "org3/repo_new", payloads[0].Repository.FullName)
})
}
func Test_WebhookPackage(t *testing.T) {
var payloads []api.PackagePayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
content, _ := io.ReadAll(r.Body)
var payload api.PackagePayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "package"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user1")
testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "package")
// 2. trigger the webhook
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", "org3", "gitea", "v1.24.0")
req := NewRequestWithBody(t, "PUT", url+"/gitea", strings.NewReader("This is a dummy file")).
AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
// 3. validate the webhook is triggered
assert.EqualValues(t, "package", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, "created", payloads[0].Action)
assert.EqualValues(t, "gitea", payloads[0].Package.Name)
assert.EqualValues(t, "generic", payloads[0].Package.Type)
assert.EqualValues(t, "org3", payloads[0].Organization.UserName)
assert.EqualValues(t, "v1.24.0", payloads[0].Package.Version)
})
}
func Test_WebhookStatus(t *testing.T) {
var payloads []api.CommitStatusPayload
var triggeredEvent string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
content, _ := io.ReadAll(r.Body)
var payload api.CommitStatusPayload
err := json.Unmarshal(content, &payload)
assert.NoError(t, err)
payloads = append(payloads, payload)
triggeredEvent = "status"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "status")
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
gitRepo1, err := gitrepo.OpenRepository(context.Background(), repo1)
assert.NoError(t, err)
commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
assert.NoError(t, err)
// 2. trigger the webhook
testCtx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeAll)
// update a status for a commit via API
doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{
State: api.CommitStatusSuccess,
TargetURL: "http://test.ci/",
Description: "",
Context: "testci",
})(t)
// 3. validate the webhook is triggered
assert.EqualValues(t, "status", triggeredEvent)
assert.Len(t, payloads, 1)
assert.EqualValues(t, commitID, payloads[0].Commit.ID)
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
assert.EqualValues(t, "testci", payloads[0].Context)
assert.EqualValues(t, commitID, payloads[0].SHA)
})
}
func Test_WebhookStatus_NoWrongTrigger(t *testing.T) {
var trigger string
provider := newMockWebhookProvider(func(r *http.Request) {
assert.NotContains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should not contain status")
assert.NotContains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should not contain status")
trigger = "push"
}, http.StatusOK)
defer provider.Close()
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// 1. create a new webhook with special webhook for repo1
session := loginUser(t, "user2")
// create a push_only webhook from web UI
testCreateWebhookForRepo(t, session, "gitea", "user2", "repo1", provider.URL(), "push_only")
// 2. trigger the webhook with a push action
testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")
// 3. validate the webhook is triggered with right event
assert.EqualValues(t, "push", trigger)
})
}