mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-02 23:41:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			472 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			472 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package integration
 | 
						|
 | 
						|
import (
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"net/url"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	gitea_context "code.gitea.io/gitea/services/context"
 | 
						|
	"code.gitea.io/gitea/services/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")
 | 
						|
 | 
						|
	webhooksLen := len(webhook.List())
 | 
						|
	baseurl := "/user2/repo1/settings/hooks"
 | 
						|
	tests := []string{
 | 
						|
		// webhook list page
 | 
						|
		baseurl,
 | 
						|
		// new webhook page
 | 
						|
		baseurl + "/gitea/new",
 | 
						|
		// edit webhook page
 | 
						|
		baseurl + "/1",
 | 
						|
	}
 | 
						|
 | 
						|
	var csrfToken string
 | 
						|
	for _, url := range tests {
 | 
						|
		resp := session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK)
 | 
						|
		htmlDoc := NewHTMLParser(t, resp.Body)
 | 
						|
		assert.Equal(t,
 | 
						|
			webhooksLen,
 | 
						|
			htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(),
 | 
						|
			"not all webhooks are listed in the 'new' dropdown")
 | 
						|
 | 
						|
		csrfToken = htmlDoc.GetCSRF()
 | 
						|
	}
 | 
						|
 | 
						|
	// ensure that the "failure" pages has the full dropdown as well
 | 
						|
	resp := session.MakeRequest(t, NewRequestWithValues(t, "POST", baseurl+"/gitea/new", map[string]string{"_csrf": csrfToken}), http.StatusUnprocessableEntity)
 | 
						|
	htmlDoc := NewHTMLParser(t, resp.Body)
 | 
						|
	assert.Equal(t,
 | 
						|
		webhooksLen,
 | 
						|
		htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(),
 | 
						|
		"not all webhooks are listed in the 'new' dropdown on failure")
 | 
						|
 | 
						|
	resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", baseurl+"/1", map[string]string{"_csrf": csrfToken}), http.StatusUnprocessableEntity)
 | 
						|
	htmlDoc = NewHTMLParser(t, resp.Body)
 | 
						|
	assert.Equal(t,
 | 
						|
		webhooksLen,
 | 
						|
		htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(),
 | 
						|
		"not all webhooks are listed in the 'new' dropdown on failure")
 | 
						|
 | 
						|
	adminSession := loginUser(t, "user1")
 | 
						|
	t.Run("org3", func(t *testing.T) {
 | 
						|
		baseurl := "/org/org3/settings/hooks"
 | 
						|
		resp := adminSession.MakeRequest(t, NewRequest(t, "GET", baseurl), http.StatusOK)
 | 
						|
		htmlDoc := NewHTMLParser(t, resp.Body)
 | 
						|
		assert.Equal(t,
 | 
						|
			webhooksLen,
 | 
						|
			htmlDoc.Find(`a[href^="`+baseurl+`/"][href$="/new"]`).Length(),
 | 
						|
			"not all webhooks are listed in the 'new' dropdown")
 | 
						|
	})
 | 
						|
	t.Run("admin", func(t *testing.T) {
 | 
						|
		baseurl := "/admin/hooks"
 | 
						|
		resp := adminSession.MakeRequest(t, NewRequest(t, "GET", baseurl), http.StatusOK)
 | 
						|
		htmlDoc := NewHTMLParser(t, resp.Body)
 | 
						|
		assert.Equal(t,
 | 
						|
			webhooksLen,
 | 
						|
			htmlDoc.Find(`a[href^="/admin/default-hooks/"][href$="/new"]`).Length(),
 | 
						|
			"not all webhooks are listed in the 'new' dropdown for default-hooks")
 | 
						|
		assert.Equal(t,
 | 
						|
			webhooksLen,
 | 
						|
			htmlDoc.Find(`a[href^="/admin/system-hooks/"][href$="/new"]`).Length(),
 | 
						|
			"not all webhooks are listed in the 'new' dropdown for system-hooks")
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestWebhookForms(t *testing.T) {
 | 
						|
	defer tests.PrepareTestEnv(t)()
 | 
						|
 | 
						|
	session := loginUser(t, "user1")
 | 
						|
 | 
						|
	t.Run("forgejo/required", testWebhookForms("forgejo", session, map[string]string{
 | 
						|
		"payload_url":  "https://forgejo.example.com",
 | 
						|
		"http_method":  "POST",
 | 
						|
		"content_type": "1", // json
 | 
						|
	}, map[string]string{
 | 
						|
		"payload_url": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"http_method": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"content_type": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"payload_url": "invalid_url",
 | 
						|
	}, map[string]string{
 | 
						|
		"http_method": "INVALID",
 | 
						|
	}))
 | 
						|
	t.Run("forgejo/optional", testWebhookForms("forgejo", session, map[string]string{
 | 
						|
		"payload_url":  "https://forgejo.example.com",
 | 
						|
		"http_method":  "POST",
 | 
						|
		"content_type": "1", // json
 | 
						|
		"secret":       "s3cr3t",
 | 
						|
 | 
						|
		"branch_filter":        "forgejo/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("gitea/required", testWebhookForms("gitea", session, map[string]string{
 | 
						|
		"payload_url":  "https://gitea.example.com",
 | 
						|
		"http_method":  "POST",
 | 
						|
		"content_type": "1", // json
 | 
						|
	}, map[string]string{
 | 
						|
		"payload_url": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"http_method": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"content_type": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"payload_url": "invalid_url",
 | 
						|
	}, map[string]string{
 | 
						|
		"http_method": "INVALID",
 | 
						|
	}))
 | 
						|
	t.Run("gitea/optional", testWebhookForms("gitea", session, map[string]string{
 | 
						|
		"payload_url":  "https://gitea.example.com",
 | 
						|
		"http_method":  "POST",
 | 
						|
		"content_type": "1", // json
 | 
						|
		"secret":       "s3cr3t",
 | 
						|
 | 
						|
		"branch_filter":        "gitea/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("gogs/required", testWebhookForms("gogs", session, map[string]string{
 | 
						|
		"payload_url":  "https://gogs.example.com",
 | 
						|
		"content_type": "1", // json
 | 
						|
	}))
 | 
						|
	t.Run("gogs/optional", testWebhookForms("gogs", session, map[string]string{
 | 
						|
		"payload_url":  "https://gogs.example.com",
 | 
						|
		"content_type": "1", // json
 | 
						|
		"secret":       "s3cr3t",
 | 
						|
 | 
						|
		"branch_filter":        "gogs/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("slack/required", testWebhookForms("slack", session, map[string]string{
 | 
						|
		"payload_url": "https://slack.example.com",
 | 
						|
		"channel":     "general",
 | 
						|
	}, map[string]string{
 | 
						|
		"channel": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"channel": "invalid channel name",
 | 
						|
	}))
 | 
						|
	t.Run("slack/optional", testWebhookForms("slack", session, map[string]string{
 | 
						|
		"payload_url": "https://slack.example.com",
 | 
						|
		"channel":     "#general",
 | 
						|
		"username":    "john",
 | 
						|
		"icon_url":    "https://slack.example.com/icon.png",
 | 
						|
		"color":       "#dd4b39",
 | 
						|
 | 
						|
		"branch_filter":        "slack/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("discord/required", testWebhookForms("discord", session, map[string]string{
 | 
						|
		"payload_url": "https://discord.example.com",
 | 
						|
	}))
 | 
						|
	t.Run("discord/optional", testWebhookForms("discord", session, map[string]string{
 | 
						|
		"payload_url": "https://discord.example.com",
 | 
						|
		"username":    "john",
 | 
						|
		"icon_url":    "https://discord.example.com/icon.png",
 | 
						|
 | 
						|
		"branch_filter":        "discord/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("dingtalk/required", testWebhookForms("dingtalk", session, map[string]string{
 | 
						|
		"payload_url": "https://dingtalk.example.com",
 | 
						|
	}))
 | 
						|
	t.Run("dingtalk/optional", testWebhookForms("dingtalk", session, map[string]string{
 | 
						|
		"payload_url": "https://dingtalk.example.com",
 | 
						|
 | 
						|
		"branch_filter":        "discord/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("telegram/required", testWebhookForms("telegram", session, map[string]string{
 | 
						|
		"bot_token": "123456",
 | 
						|
		"chat_id":   "789",
 | 
						|
	}))
 | 
						|
	t.Run("telegram/optional", testWebhookForms("telegram", session, map[string]string{
 | 
						|
		"bot_token": "123456",
 | 
						|
		"chat_id":   "789",
 | 
						|
		"thread_id": "abc",
 | 
						|
 | 
						|
		"branch_filter":        "telegram/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("msteams/required", testWebhookForms("msteams", session, map[string]string{
 | 
						|
		"payload_url": "https://msteams.example.com",
 | 
						|
	}))
 | 
						|
	t.Run("msteams/optional", testWebhookForms("msteams", session, map[string]string{
 | 
						|
		"payload_url": "https://msteams.example.com",
 | 
						|
 | 
						|
		"branch_filter":        "msteams/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("feishu/required", testWebhookForms("feishu", session, map[string]string{
 | 
						|
		"payload_url": "https://feishu.example.com",
 | 
						|
	}))
 | 
						|
	t.Run("feishu/optional", testWebhookForms("feishu", session, map[string]string{
 | 
						|
		"payload_url": "https://feishu.example.com",
 | 
						|
 | 
						|
		"branch_filter":        "feishu/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("matrix/required", testWebhookForms("matrix", session, map[string]string{
 | 
						|
		"homeserver_url": "https://matrix.example.com",
 | 
						|
		"access_token":   "123456",
 | 
						|
		"room_id":        "123",
 | 
						|
	}, map[string]string{
 | 
						|
		"access_token": "",
 | 
						|
	}))
 | 
						|
	t.Run("matrix/optional", testWebhookForms("matrix", session, map[string]string{
 | 
						|
		"homeserver_url": "https://matrix.example.com",
 | 
						|
		"access_token":   "123456",
 | 
						|
		"room_id":        "123",
 | 
						|
		"message_type":   "1", // m.text
 | 
						|
 | 
						|
		"branch_filter": "matrix/*",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("wechatwork/required", testWebhookForms("wechatwork", session, map[string]string{
 | 
						|
		"payload_url": "https://wechatwork.example.com",
 | 
						|
	}))
 | 
						|
	t.Run("wechatwork/optional", testWebhookForms("wechatwork", session, map[string]string{
 | 
						|
		"payload_url": "https://wechatwork.example.com",
 | 
						|
 | 
						|
		"branch_filter":        "wechatwork/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("packagist/required", testWebhookForms("packagist", session, map[string]string{
 | 
						|
		"username":    "john",
 | 
						|
		"api_token":   "secret",
 | 
						|
		"package_url": "https://packagist.org/packages/example/framework",
 | 
						|
	}))
 | 
						|
	t.Run("packagist/optional", testWebhookForms("packagist", session, map[string]string{
 | 
						|
		"username":    "john",
 | 
						|
		"api_token":   "secret",
 | 
						|
		"package_url": "https://packagist.org/packages/example/framework",
 | 
						|
 | 
						|
		"branch_filter":        "packagist/*",
 | 
						|
		"authorization_header": "Bearer 123456",
 | 
						|
	}))
 | 
						|
 | 
						|
	t.Run("sourcehut_builds/required", testWebhookForms("sourcehut_builds", session, map[string]string{
 | 
						|
		"payload_url":   "https://sourcehut_builds.example.com",
 | 
						|
		"manifest_path": ".build.yml",
 | 
						|
		"visibility":    "PRIVATE",
 | 
						|
		"access_token":  "123456",
 | 
						|
	}, map[string]string{
 | 
						|
		"access_token": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"manifest_path": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"manifest_path": "/absolute",
 | 
						|
	}, map[string]string{
 | 
						|
		"visibility": "",
 | 
						|
	}, map[string]string{
 | 
						|
		"visibility": "INVALID",
 | 
						|
	}))
 | 
						|
	t.Run("sourcehut_builds/optional", testWebhookForms("sourcehut_builds", session, map[string]string{
 | 
						|
		"payload_url":   "https://sourcehut_builds.example.com",
 | 
						|
		"manifest_path": ".build.yml",
 | 
						|
		"visibility":    "PRIVATE",
 | 
						|
		"secrets":       "on",
 | 
						|
		"access_token":  "123456",
 | 
						|
 | 
						|
		"branch_filter": "srht/*",
 | 
						|
	}))
 | 
						|
}
 | 
						|
 | 
						|
func assertInput(t testing.TB, form *goquery.Selection, name string) string {
 | 
						|
	t.Helper()
 | 
						|
	input := form.Find(`input[name="` + name + `"]`)
 | 
						|
	if input.Length() != 1 {
 | 
						|
		form.Find("input").Each(func(i int, s *goquery.Selection) {
 | 
						|
			t.Logf("found <input name=%q />", s.AttrOr("name", ""))
 | 
						|
		})
 | 
						|
		t.Errorf("field <input name=%q /> found %d times, expected once", name, input.Length())
 | 
						|
	}
 | 
						|
	switch input.AttrOr("type", "") {
 | 
						|
	case "checkbox":
 | 
						|
		if _, checked := input.Attr("checked"); checked {
 | 
						|
			return "on"
 | 
						|
		}
 | 
						|
		return ""
 | 
						|
	default:
 | 
						|
		return input.AttrOr("value", "")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testWebhookForms(name string, session *TestSession, validFields map[string]string, invalidPatches ...map[string]string) func(t *testing.T) {
 | 
						|
	return func(t *testing.T) {
 | 
						|
		t.Run("repo1", func(t *testing.T) {
 | 
						|
			testWebhookFormsShared(t, "/user2/repo1/settings/hooks", name, session, validFields, invalidPatches...)
 | 
						|
		})
 | 
						|
		t.Run("org3", func(t *testing.T) {
 | 
						|
			testWebhookFormsShared(t, "/org/org3/settings/hooks", name, session, validFields, invalidPatches...)
 | 
						|
		})
 | 
						|
		t.Run("system", func(t *testing.T) {
 | 
						|
			testWebhookFormsShared(t, "/admin/system-hooks", name, session, validFields, invalidPatches...)
 | 
						|
		})
 | 
						|
		t.Run("default", func(t *testing.T) {
 | 
						|
			testWebhookFormsShared(t, "/admin/default-hooks", name, session, validFields, invalidPatches...)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func testWebhookFormsShared(t *testing.T, endpoint, name string, session *TestSession, validFields map[string]string, invalidPatches ...map[string]string) {
 | 
						|
	// new webhook form
 | 
						|
	resp := session.MakeRequest(t, NewRequest(t, "GET", endpoint+"/"+name+"/new"), http.StatusOK)
 | 
						|
	htmlForm := NewHTMLParser(t, resp.Body).Find(`form[action^="` + endpoint + `/"]`)
 | 
						|
 | 
						|
	// fill the form
 | 
						|
	payload := map[string]string{
 | 
						|
		"_csrf":  htmlForm.Find(`input[name="_csrf"]`).AttrOr("value", ""),
 | 
						|
		"events": "send_everything",
 | 
						|
	}
 | 
						|
	for k, v := range validFields {
 | 
						|
		assertInput(t, htmlForm, k)
 | 
						|
		payload[k] = v
 | 
						|
	}
 | 
						|
	if t.Failed() {
 | 
						|
		t.FailNow() // prevent further execution if the form could not be filled properly
 | 
						|
	}
 | 
						|
 | 
						|
	// create the webhook (this redirects back to the hook list)
 | 
						|
	resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", endpoint+"/"+name+"/new", payload), http.StatusSeeOther)
 | 
						|
	assertHasFlashMessages(t, resp, "success")
 | 
						|
	listEndpoint := resp.Header().Get("Location")
 | 
						|
	updateEndpoint := endpoint + "/"
 | 
						|
	if endpoint == "/admin/system-hooks" || endpoint == "/admin/default-hooks" {
 | 
						|
		updateEndpoint = "/admin/hooks/"
 | 
						|
	}
 | 
						|
 | 
						|
	// find last created hook in the hook list
 | 
						|
	// (a bit hacky, but the list should be sorted)
 | 
						|
	resp = session.MakeRequest(t, NewRequest(t, "GET", listEndpoint), http.StatusOK)
 | 
						|
	htmlDoc := NewHTMLParser(t, resp.Body)
 | 
						|
	selector := `a[href^="` + updateEndpoint + `"]`
 | 
						|
	if endpoint == "/admin/system-hooks" {
 | 
						|
		// system-hooks and default-hooks are listed on the same page
 | 
						|
		// add a specifier to select the latest system-hooks
 | 
						|
		// (the default-hooks are at the end, so no further specifier needed)
 | 
						|
		selector = `.admin-setting-content > div:first-of-type ` + selector
 | 
						|
	}
 | 
						|
	editFormURL := htmlDoc.Find(selector).Last().AttrOr("href", "")
 | 
						|
	assert.NotEmpty(t, editFormURL)
 | 
						|
 | 
						|
	// edit webhook form
 | 
						|
	resp = session.MakeRequest(t, NewRequest(t, "GET", editFormURL), http.StatusOK)
 | 
						|
	htmlForm = NewHTMLParser(t, resp.Body).Find(`form[action^="` + updateEndpoint + `"]`)
 | 
						|
	editPostURL := htmlForm.AttrOr("action", "")
 | 
						|
	assert.NotEmpty(t, editPostURL)
 | 
						|
 | 
						|
	// fill the form
 | 
						|
	payload = map[string]string{
 | 
						|
		"_csrf":  htmlForm.Find(`input[name="_csrf"]`).AttrOr("value", ""),
 | 
						|
		"events": "push_only",
 | 
						|
	}
 | 
						|
	for k, v := range validFields {
 | 
						|
		assert.Equal(t, v, assertInput(t, htmlForm, k), "input %q did not contain value %q", k, v)
 | 
						|
		payload[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	// update the webhook
 | 
						|
	resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", editPostURL, payload), http.StatusSeeOther)
 | 
						|
	assertHasFlashMessages(t, resp, "success")
 | 
						|
 | 
						|
	// check the updated webhook
 | 
						|
	resp = session.MakeRequest(t, NewRequest(t, "GET", editFormURL), http.StatusOK)
 | 
						|
	htmlForm = NewHTMLParser(t, resp.Body).Find(`form[action^="` + updateEndpoint + `"]`)
 | 
						|
	for k, v := range validFields {
 | 
						|
		assert.Equal(t, v, assertInput(t, htmlForm, k), "input %q did not contain value %q", k, v)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(invalidPatches) > 0 {
 | 
						|
		// check that invalid fields are rejected
 | 
						|
		resp := session.MakeRequest(t, NewRequest(t, "GET", endpoint+"/"+name+"/new"), http.StatusOK)
 | 
						|
		htmlForm := NewHTMLParser(t, resp.Body).Find(`form[action^="` + endpoint + `/"]`)
 | 
						|
 | 
						|
		for _, invalidPatch := range invalidPatches {
 | 
						|
			t.Run("invalid", func(t *testing.T) {
 | 
						|
				// fill the form
 | 
						|
				payload := map[string]string{
 | 
						|
					"_csrf":  htmlForm.Find(`input[name="_csrf"]`).AttrOr("value", ""),
 | 
						|
					"events": "send_everything",
 | 
						|
				}
 | 
						|
				for k, v := range validFields {
 | 
						|
					payload[k] = v
 | 
						|
				}
 | 
						|
				for k, v := range invalidPatch {
 | 
						|
					if v == "" {
 | 
						|
						delete(payload, k)
 | 
						|
					} else {
 | 
						|
						payload[k] = v
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				resp := session.MakeRequest(t, NewRequestWithValues(t, "POST", endpoint+"/"+name+"/new", payload), http.StatusUnprocessableEntity)
 | 
						|
				// check that the invalid form is pre-filled
 | 
						|
				htmlForm = NewHTMLParser(t, resp.Body).Find(`form[action^="` + endpoint + `/"]`)
 | 
						|
				for k, v := range payload {
 | 
						|
					if k == "_csrf" || k == "events" || v == "" {
 | 
						|
						// the 'events' is a radio input, which is buggy below
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					assert.Equal(t, v, assertInput(t, htmlForm, k), "input %q did not contain value %q", k, v)
 | 
						|
				}
 | 
						|
				if t.Failed() {
 | 
						|
					t.Log(invalidPatch)
 | 
						|
				}
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func assertHasFlashMessages(t *testing.T, resp *httptest.ResponseRecorder, expectedKeys ...string) {
 | 
						|
	seenKeys := make(map[string][]string, len(expectedKeys))
 | 
						|
 | 
						|
	for _, cookie := range resp.Result().Cookies() {
 | 
						|
		if cookie.Name != gitea_context.CookieNameFlash {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		flash, _ := url.ParseQuery(cookie.Value)
 | 
						|
		for key, value := range flash {
 | 
						|
			// the key is itself url-encoded
 | 
						|
			if flash, err := url.ParseQuery(key); err == nil {
 | 
						|
				for key, value := range flash {
 | 
						|
					seenKeys[key] = value
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				seenKeys[key] = value
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, k := range expectedKeys {
 | 
						|
		if len(seenKeys[k]) == 0 {
 | 
						|
			t.Errorf("missing expected flash message %q", k)
 | 
						|
		}
 | 
						|
		delete(seenKeys, k)
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range seenKeys {
 | 
						|
		t.Errorf("unexpected flash message %q: %q", k, v)
 | 
						|
	}
 | 
						|
}
 |