From 635230ca5debd9f230fcf216add83fa5658bc3c2 Mon Sep 17 00:00:00 2001
From: oliverpool <git@olivier.pfad.fr>
Date: Sat, 23 Mar 2024 22:43:44 +0100
Subject: [PATCH] [TESTS] webhook forms keep submitted data when invalid

---
 routers/web/repo/setting/webhook.go           | 44 +++++++++++++------
 templates/repo/settings/webhook/settings.tmpl |  2 +-
 tests/integration/repo_webhook_test.go        | 11 ++++-
 3 files changed, 42 insertions(+), 15 deletions(-)

diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index e6e869d8f6..29d2573451 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -219,6 +219,22 @@ func WebhookCreate(ctx *context.Context) {
 	ctx.Data["BaseLinkNew"] = orCtx.LinkNew
 
 	if ctx.HasError() {
+		// pre-fill the form with the submitted data
+		var w webhook.Webhook
+		w.URL = fields.URL
+		w.ContentType = fields.ContentType
+		w.Secret = fields.Secret
+		w.HookEvent = ParseHookEvent(fields.WebhookForm)
+		w.IsActive = fields.WebhookForm.Active
+		w.HTTPMethod = fields.HTTPMethod
+		err := w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
+		if err != nil {
+			ctx.ServerError("SetHeaderAuthorization", err)
+			return
+		}
+		ctx.Data["Webhook"] = w
+		ctx.Data["HookMetadata"] = fields.Metadata
+
 		ctx.HTML(http.StatusUnprocessableEntity, orCtx.NewTemplate)
 		return
 	}
@@ -284,13 +300,27 @@ func WebhookUpdate(ctx *context.Context) {
 		middleware.Validate(errs, ctx.Data, form, ctx.Locale) // error checked below in ctx.HasError
 	})
 
+	// pre-fill the form with the submitted data
+	w.URL = fields.URL
+	w.ContentType = fields.ContentType
+	w.Secret = fields.Secret
+	w.HookEvent = ParseHookEvent(fields.WebhookForm)
+	w.IsActive = fields.WebhookForm.Active
+	w.HTTPMethod = fields.HTTPMethod
+
+	err := w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
+	if err != nil {
+		ctx.ServerError("SetHeaderAuthorization", err)
+		return
+	}
+
 	if ctx.HasError() {
+		ctx.Data["HookMetadata"] = fields.Metadata
 		ctx.HTML(http.StatusUnprocessableEntity, orCtx.NewTemplate)
 		return
 	}
 
 	var meta []byte
-	var err error
 	if fields.Metadata != nil {
 		meta, err = json.Marshal(fields.Metadata)
 		if err != nil {
@@ -299,20 +329,8 @@ func WebhookUpdate(ctx *context.Context) {
 		}
 	}
 
-	w.URL = fields.URL
-	w.ContentType = fields.ContentType
-	w.Secret = fields.Secret
-	w.HookEvent = ParseHookEvent(fields.WebhookForm)
-	w.IsActive = fields.WebhookForm.Active
-	w.HTTPMethod = fields.HTTPMethod
 	w.Meta = string(meta)
 
-	err = w.SetHeaderAuthorization(fields.WebhookForm.AuthorizationHeader)
-	if err != nil {
-		ctx.ServerError("SetHeaderAuthorization", err)
-		return
-	}
-
 	if err := w.UpdateEvent(); err != nil {
 		ctx.ServerError("UpdateEvent", err)
 		return
diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl
index 3ef8894444..2cbbef3e40 100644
--- a/templates/repo/settings/webhook/settings.tmpl
+++ b/templates/repo/settings/webhook/settings.tmpl
@@ -259,7 +259,7 @@
 </div>
 
 <!-- Authorization Header -->
-<div class="field{{if eq .HookType "matrix"}} required{{end}}">
+<div class="field{{if eq .HookType "matrix"}} required{{end}} {{if .Err_AuthorizationHeader}}error{{end}}">
 	<label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label>
 	<input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}>
 	{{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}
diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go
index 47c7a1e436..3264f3894e 100644
--- a/tests/integration/repo_webhook_test.go
+++ b/tests/integration/repo_webhook_test.go
@@ -330,7 +330,16 @@ func testWebhookForms(name string, session *TestSession, validFields map[string]
 						}
 					}
 
-					session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user2/repo1/settings/hooks/"+name+"/new", payload), http.StatusUnprocessableEntity)
+					resp := session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user2/repo1/settings/hooks/"+name+"/new", payload), http.StatusUnprocessableEntity)
+					// check that the invalid form is pre-filled
+					htmlForm = NewHTMLParser(t, resp.Body).Find(`form[action^="/user2/repo1/settings/hooks/"]`)
+					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)
 					}