diff --git a/modules/structs/hook.go b/modules/structs/hook.go index ce5742e5c7..cef2dbd712 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -116,14 +116,7 @@ var ( _ Payloader = &PackagePayload{} ) -// _________ __ -// \_ ___ \_______ ____ _____ _/ |_ ____ -// / \ \/\_ __ \_/ __ \\__ \\ __\/ __ \ -// \ \____| | \/\ ___/ / __ \| | \ ___/ -// \______ /|__| \___ >____ /__| \___ > -// \/ \/ \/ \/ - -// CreatePayload FIXME +// CreatePayload represents a payload information of create event. type CreatePayload struct { Sha string `json:"sha"` Ref string `json:"ref"` @@ -157,13 +150,6 @@ func ParseCreateHook(raw []byte) (*CreatePayload, error) { return hook, nil } -// ________ .__ __ -// \______ \ ____ | | _____/ |_ ____ -// | | \_/ __ \| | _/ __ \ __\/ __ \ -// | ` \ ___/| |_\ ___/| | \ ___/ -// /_______ /\___ >____/\___ >__| \___ > -// \/ \/ \/ \/ - // PusherType define the type to push type PusherType string @@ -186,13 +172,6 @@ func (p *DeletePayload) JSONPayload() ([]byte, error) { return json.MarshalIndent(p, "", " ") } -// ___________ __ -// \_ _____/__________| | __ -// | __)/ _ \_ __ \ |/ / -// | \( <_> ) | \/ < -// \___ / \____/|__| |__|_ \ -// \/ \/ - // ForkPayload represents fork payload type ForkPayload struct { Forkee *Repository `json:"forkee"` @@ -232,13 +211,6 @@ func (p *IssueCommentPayload) JSONPayload() ([]byte, error) { return json.MarshalIndent(p, "", " ") } -// __________ .__ -// \______ \ ____ | | ____ _____ ______ ____ -// | _// __ \| | _/ __ \\__ \ / ___// __ \ -// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/ -// |____|_ /\___ >____/\___ >____ /____ >\___ > -// \/ \/ \/ \/ \/ \/ - // HookReleaseAction defines hook release action type type HookReleaseAction string @@ -302,13 +274,6 @@ func (p *PushPayload) Branch() string { return strings.ReplaceAll(p.Ref, "refs/heads/", "") } -// .___ -// | | ______ ________ __ ____ -// | |/ ___// ___/ | \_/ __ \ -// | |\___ \ \___ \| | /\ ___/ -// |___/____ >____ >____/ \___ > -// \/ \/ \/ - // HookIssueAction FIXME type HookIssueAction string @@ -371,13 +336,6 @@ type ChangesPayload struct { Ref *ChangesFromPayload `json:"ref,omitempty"` } -// __________ .__ .__ __________ __ -// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ -// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ -// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | -// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| -// \/ \/ |__| \/ \/ - // PullRequestPayload represents a payload information of pull request event. type PullRequestPayload struct { Action HookIssueAction `json:"action"` @@ -402,13 +360,6 @@ type ReviewPayload struct { Content string `json:"content"` } -// __ __.__ __ .__ -// / \ / \__| | _|__| -// \ \/\/ / | |/ / | -// \ /| | <| | -// \__/\ / |__|__|_ \__| -// \/ \/ - // HookWikiAction an action that happens to a wiki page type HookWikiAction string @@ -435,13 +386,6 @@ func (p *WikiPayload) JSONPayload() ([]byte, error) { return json.MarshalIndent(p, "", " ") } -//__________ .__ __ -//\______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. -// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | -// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ | -// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____| -// \/ \/|__| \/ \/ - // HookRepoAction an action that happens to a repo type HookRepoAction string @@ -480,7 +424,7 @@ type PackagePayload struct { Action HookPackageAction `json:"action"` Repository *Repository `json:"repository"` Package *Package `json:"package"` - Organization *User `json:"organization"` + Organization *Organization `json:"organization"` Sender *User `json:"sender"` } diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 148fe64992..2ad9eeb0ec 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -205,6 +205,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI webhook_module.HookEventWiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true), webhook_module.HookEventRepository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true), webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true), + webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true), webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true), }, BranchFilter: form.BranchFilter, @@ -384,6 +385,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh w.HookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)) w.HookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)) w.HookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)) + w.HookEvents[webhook_module.HookEventPullRequestComment] = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)) w.HookEvents[webhook_module.HookEventPullRequestReview] = pullHook(form.Events, "pull_request_review") w.HookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)) w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)) diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go index e382f5a9df..992b8c566f 100644 --- a/services/webhook/dingtalk.go +++ b/services/webhook/dingtalk.go @@ -190,3 +190,7 @@ func newDingtalkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_ var pc payloadConvertor[DingtalkPayload] = dingtalkConvertor{} return newJSONRequest(pc, w, t, true) } + +func init() { + RegisterWebhookRequester(webhook_module.DINGTALK, newDingtalkRequest) +} diff --git a/services/webhook/discord.go b/services/webhook/discord.go index c562d98168..30d930062e 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -277,6 +277,10 @@ func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_m return newJSONRequest(pc, w, t, true) } +func init() { + RegisterWebhookRequester(webhook_module.DISCORD, newDiscordRequest) +} + func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) { switch event { case webhook_module.HookEventPullRequestReviewApproved: diff --git a/services/webhook/feishu.go b/services/webhook/feishu.go index 7ca7d1cf5f..4e6aebc39d 100644 --- a/services/webhook/feishu.go +++ b/services/webhook/feishu.go @@ -170,3 +170,7 @@ func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mo var pc payloadConvertor[FeishuPayload] = feishuConvertor{} return newJSONRequest(pc, w, t, true) } + +func init() { + RegisterWebhookRequester(webhook_module.FEISHU, newFeishuRequest) +} diff --git a/services/webhook/general_test.go b/services/webhook/general_test.go index ef1ec7f324..ec735d785a 100644 --- a/services/webhook/general_test.go +++ b/services/webhook/general_test.go @@ -319,8 +319,8 @@ func packageTestPayload() *api.PackagePayload { AvatarURL: "http://localhost:3000/user1/avatar", }, Repository: nil, - Organization: &api.User{ - UserName: "org1", + Organization: &api.Organization{ + Name: "org1", AvatarURL: "http://localhost:3000/org1/avatar", }, Package: &api.Package{ diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 5e9f808d8b..96dfa139ac 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -24,6 +24,10 @@ import ( webhook_module "code.gitea.io/gitea/modules/webhook" ) +func init() { + RegisterWebhookRequester(webhook_module.MATRIX, newMatrixRequest) +} + func newMatrixRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) { meta := &MatrixMeta{} if err := json.Unmarshal([]byte(w.Meta), meta); err != nil { diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go index 7ef96ffa27..1ae7c4f931 100644 --- a/services/webhook/msteams.go +++ b/services/webhook/msteams.go @@ -349,3 +349,7 @@ func newMSTeamsRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_m var pc payloadConvertor[MSTeamsPayload] = msteamsConvertor{} return newJSONRequest(pc, w, t, true) } + +func init() { + RegisterWebhookRequester(webhook_module.MSTEAMS, newMSTeamsRequest) +} diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index 2fce4b351e..6c691c21f4 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -8,6 +8,7 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -920,10 +921,16 @@ func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_mo return } + var org *api.Organization + if pd.Owner.IsOrganization() { + org = convert.ToOrganization(ctx, organization.OrgFromUser(pd.Owner)) + } + if err := PrepareWebhooks(ctx, source, webhook_module.HookEventPackage, &api.PackagePayload{ - Action: action, - Package: apiPackage, - Sender: convert.ToUser(ctx, sender, nil), + Action: action, + Package: apiPackage, + Organization: org, + Sender: convert.ToUser(ctx, sender, nil), }); err != nil { log.Error("PrepareWebhooks: %v", err) } diff --git a/services/webhook/packagist.go b/services/webhook/packagist.go index 4d809ab3a6..e66895832b 100644 --- a/services/webhook/packagist.go +++ b/services/webhook/packagist.go @@ -120,3 +120,7 @@ func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook } return newJSONRequest(pc, w, t, true) } + +func init() { + RegisterWebhookRequester(webhook_module.PACKAGIST, newPackagistRequest) +} diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 2a49df2453..0371ee23e6 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -295,6 +295,10 @@ func newSlackRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mod return newJSONRequest(pc, w, t, true) } +func init() { + RegisterWebhookRequester(webhook_module.SLACK, newSlackRequest) +} + var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`) // IsValidSlackChannel validates a channel name conforms to what slack expects: diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go index e54d6f2947..6fbf995801 100644 --- a/services/webhook/telegram.go +++ b/services/webhook/telegram.go @@ -187,3 +187,7 @@ func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_ var pc payloadConvertor[TelegramPayload] = telegramConvertor{} return newJSONRequest(pc, w, t, true) } + +func init() { + RegisterWebhookRequester(webhook_module.TELEGRAM, newTelegramRequest) +} diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index b4609e8a51..182078b39d 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -27,16 +27,12 @@ import ( "github.com/gobwas/glob" ) -var webhookRequesters = map[webhook_module.HookType]func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error){ - webhook_module.SLACK: newSlackRequest, - webhook_module.DISCORD: newDiscordRequest, - webhook_module.DINGTALK: newDingtalkRequest, - webhook_module.TELEGRAM: newTelegramRequest, - webhook_module.MSTEAMS: newMSTeamsRequest, - webhook_module.FEISHU: newFeishuRequest, - webhook_module.MATRIX: newMatrixRequest, - webhook_module.WECHATWORK: newWechatworkRequest, - webhook_module.PACKAGIST: newPackagistRequest, +type Requester func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error) + +var webhookRequesters = map[webhook_module.HookType]Requester{} + +func RegisterWebhookRequester(hookType webhook_module.HookType, requester Requester) { + webhookRequesters[hookType] = requester } // IsValidHookTaskType returns true if a webhook registered diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go index 1d8c1d7dac..44e0ff7de5 100644 --- a/services/webhook/wechatwork.go +++ b/services/webhook/wechatwork.go @@ -179,3 +179,7 @@ func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhoo var pc payloadConvertor[WechatworkPayload] = wechatworkConvertor{} return newJSONRequest(pc, w, t, true) } + +func init() { + RegisterWebhookRequester(webhook_module.WECHATWORK, newWechatworkRequest) +} diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 13dc90f8a7..22f26d87d4 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -471,6 +471,15 @@ func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) } +func testAPIOrgCreateRepo(t *testing.T, session *TestSession, orgName, repoName string, status int) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", orgName), &api.CreateRepoOption{ + Name: repoName, + }).AddTokenAuth(token) + MakeRequest(t, req, status) +} + func TestAPIOrgRepoCreate(t *testing.T) { testCases := []struct { ctxUserID int64 @@ -488,11 +497,7 @@ func TestAPIOrgRepoCreate(t *testing.T) { for _, testCase := range testCases { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{ - Name: testCase.repoName, - }).AddTokenAuth(token) - MakeRequest(t, req, testCase.expectedStatus) + testAPIOrgCreateRepo(t, session, testCase.orgName, testCase.repoName, testCase.expectedStatus) } } diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go index 05d90fc4e3..8e5f67e282 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -172,6 +172,19 @@ func TestAPIListWikiPages(t *testing.T) { assert.Equal(t, dummymeta, meta) } +func testAPICreateWikiPage(t *testing.T, session *TestSession, userName, repoName, title string, status int) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", userName, repoName) + + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ + Title: title, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), + Message: "", + }).AddTokenAuth(token) + MakeRequest(t, req, status) +} + func TestAPINewWikiPage(t *testing.T) { for _, title := range []string{ "New page", @@ -180,16 +193,7 @@ func TestAPINewWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() username := "user2" session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", username, "repo1") - - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{ - Title: title, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")), - Message: "", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + testAPICreateWikiPage(t, session, username, "repo1", title, http.StatusCreated) } } diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 4617c5f89a..bd0cedd300 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -174,7 +174,7 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, htmlDoc = NewHTMLParser(t, resp.Body) - val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text() + val := strings.TrimSpace(htmlDoc.doc.Find(".comment-list .comment .render-content").Eq(commentCount).Text()) assert.Equal(t, content, val) idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id") diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index ef44a9e2d0..17905513c3 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -4,10 +4,22 @@ 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/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" @@ -39,3 +51,514 @@ func TestNewWebHookLink(t *testing.T) { }) } } + +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 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) + }) +} diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl index b50816b2cd..ffba516ed3 100644 --- a/tests/mssql.ini.tmpl +++ b/tests/mssql.ini.tmpl @@ -26,6 +26,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/gitea-repositories @@ -111,3 +114,6 @@ ENABLED = true [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index ec8307acc3..e2f2e1390a 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -28,6 +28,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/gitea-repositories @@ -118,3 +121,6 @@ REPLY_TO_ADDRESS = incoming+%{token}@localhost [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index 139ea9c2b7..483b9ed0cd 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -27,6 +27,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/gitea-repositories @@ -127,3 +130,6 @@ ENABLED = true [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1 diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 2f7a3e8182..e837860c26 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -22,6 +22,9 @@ TYPE = immediate [queue.push_update] TYPE = immediate +[queue.webhook_sender] +TYPE = immediate + [repository] ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea-repositories @@ -116,3 +119,6 @@ RENDER_CONTENT_MODE=sanitized [actions] ENABLED = true + +[webhook] +ALLOWED_HOST_LIST = 127.0.0.1