Compare commits
1
Commits
next
...
f1304da780
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1304da780 |
@@ -275,3 +275,99 @@ func TestHandleSourceDetail_FitsWideAndNarrowWindows(t *testing.T) {
|
|||||||
`<div class="flex flex-wrap justify-between items-center gap-2 mt-2">`,
|
`<div class="flex flex-wrap justify-between items-center gap-2 mt-2">`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestHandleSourceDetail_DeletePromptsNameWhatIsLost checks that each
|
||||||
|
// delete prompt on the webhook page names the webhook, entrypoint or
|
||||||
|
// target and says what deleting it loses, that the webhook's gives its
|
||||||
|
// number of stored events (5 received, 2 removed by retention, so 3,
|
||||||
|
// the statistics pane's "Within retention" figure), and that an
|
||||||
|
// entrypoint with no description is named by its URL. The template
|
||||||
|
// writes the slashes after http: as \/, which the browser reads as /.
|
||||||
|
func TestHandleSourceDetail_DeletePromptsNameWhatIsLost(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
h *handlers.Handlers
|
||||||
|
sess *session.Session
|
||||||
|
db *database.Database
|
||||||
|
dbMgr *database.WebhookDBManager
|
||||||
|
)
|
||||||
|
|
||||||
|
app := newTestApp(t, &h, &sess, &db, &dbMgr)
|
||||||
|
app.RequireStart()
|
||||||
|
|
||||||
|
t.Cleanup(app.RequireStop)
|
||||||
|
|
||||||
|
wh := seedWebhook(t, db)
|
||||||
|
|
||||||
|
webhookDB, err := dbMgr.GetDB(wh.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, database.AddEventTotals(
|
||||||
|
webhookDB, database.EventTotals{Events: 5, EventsRemoved: 2},
|
||||||
|
))
|
||||||
|
|
||||||
|
unnamed := seedEntrypoint(t, db, wh.ID)
|
||||||
|
require.NoError(t, db.DB().Omit(clause.Associations).Create(
|
||||||
|
&database.Entrypoint{
|
||||||
|
WebhookID: wh.ID,
|
||||||
|
Path: "described-" + wh.ID,
|
||||||
|
Description: "Stripe",
|
||||||
|
Active: true,
|
||||||
|
},
|
||||||
|
).Error)
|
||||||
|
seedTarget(t, db, wh.ID, database.TargetTypeLog)
|
||||||
|
|
||||||
|
body := renderSourceDetailPage(t, h, sess, wh.ID)
|
||||||
|
|
||||||
|
assert.Contains(t, body,
|
||||||
|
`Delete webhook "delete-me"?\n\n`+
|
||||||
|
`This deletes its stored events (3) and their deliveries. `+
|
||||||
|
`Any archive files it wrote are kept.`)
|
||||||
|
assert.Contains(t, body,
|
||||||
|
`Delete entrypoint "Stripe"?\n\n`+
|
||||||
|
`Senders using its URL get an error from now on, `+
|
||||||
|
`and the URL cannot be restored.`)
|
||||||
|
assert.Contains(t, body,
|
||||||
|
`Delete entrypoint "http:\/\/example.com/h/`+
|
||||||
|
unnamed.Path+`"?`)
|
||||||
|
assert.Contains(t, body,
|
||||||
|
`Delete target "t-log"?\n\n`+
|
||||||
|
`Nothing more is delivered to it. `+
|
||||||
|
`Its past deliveries stay in the event log.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHandleSourceDetail_DeletePromptKeepsQuotesInName checks that a
|
||||||
|
// webhook name with quotes, a backslash, a closing script tag and a
|
||||||
|
// newline reaches its delete prompt escaped for the script, which the
|
||||||
|
// browser reads back as the name typed: each quote and angle bracket
|
||||||
|
// as a \u escape, the slash as \/, the newline as \n and the backslash
|
||||||
|
// doubled. An unescaped newline would break the prompt's script, and
|
||||||
|
// the form would then submit without asking.
|
||||||
|
func TestHandleSourceDetail_DeletePromptKeepsQuotesInName(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
h *handlers.Handlers
|
||||||
|
sess *session.Session
|
||||||
|
db *database.Database
|
||||||
|
)
|
||||||
|
|
||||||
|
app := newTestApp(t, &h, &sess, &db)
|
||||||
|
app.RequireStart()
|
||||||
|
|
||||||
|
t.Cleanup(app.RequireStop)
|
||||||
|
|
||||||
|
wh := &database.Webhook{
|
||||||
|
UserID: deleteTestUserID,
|
||||||
|
Name: "Bob's \"best\" \\ hook</script>\nline two",
|
||||||
|
}
|
||||||
|
require.NoError(
|
||||||
|
t, db.DB().Omit(clause.Associations).Create(wh).Error,
|
||||||
|
)
|
||||||
|
|
||||||
|
body := renderSourceDetailPage(t, h, sess, wh.ID)
|
||||||
|
|
||||||
|
assert.Contains(t, body,
|
||||||
|
"Delete webhook "Bob\\u0027s \\u0022best\\u0022 \\\\ hook"+
|
||||||
|
"\\u003c\\/script\\u003e\\nline two"?")
|
||||||
|
}
|
||||||
|
|||||||
@@ -612,8 +612,9 @@ func (h *Handlers) renderSourceDetail(
|
|||||||
|
|
||||||
// The host is the client's Host header, unvalidated. It is
|
// The host is the client's Host header, unvalidated. It is
|
||||||
// inert only because source_detail.html renders BaseURL as
|
// inert only because source_detail.html renders BaseURL as
|
||||||
// text inside a <code> element; putting it in an href or any
|
// text, inside a <code> element and in an entrypoint's delete
|
||||||
// other URL context needs it constrained first.
|
// prompt; putting it in an href or any other URL context
|
||||||
|
// needs it constrained first.
|
||||||
baseURL := scheme + "://" + r.Host
|
baseURL := scheme + "://" + r.Host
|
||||||
|
|
||||||
// The template calls Webhook methods, which take pointer
|
// The template calls Webhook methods, which take pointer
|
||||||
|
|||||||
@@ -20,7 +20,11 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<a href="/hook/{{.Webhook.ID}}/events" class="btn-secondary">Full Event Log</a>
|
<a href="/hook/{{.Webhook.ID}}/events" class="btn-secondary">Full Event Log</a>
|
||||||
<a href="/hook/{{.Webhook.ID}}/edit" class="btn-secondary">Edit</a>
|
<a href="/hook/{{.Webhook.ID}}/edit" class="btn-secondary">Edit</a>
|
||||||
<form method="POST" action="/hook/{{.Webhook.ID}}/delete" onsubmit="return confirm('Delete this webhook and all its data?')">
|
<!-- The delete prompts are the browser's own, so they
|
||||||
|
work without the page's scripts. The template
|
||||||
|
escapes each name for the script, so a quote or a
|
||||||
|
backslash in it shows as typed. -->
|
||||||
|
<form method="POST" action="/hook/{{.Webhook.ID}}/delete" onsubmit="return confirm('Delete webhook "{{.Webhook.Name}}"?\n\nThis deletes its stored events{{with .Stats}} ({{.WithinRetention.Events}}){{end}} and their deliveries. Any archive files it wrote are kept.')">
|
||||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
<button type="submit" class="btn-danger">Delete</button>
|
<button type="submit" class="btn-danger">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -83,7 +87,7 @@
|
|||||||
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/entrypoints/{{.ID}}/delete" onsubmit="return confirm('Delete this entrypoint?')" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/entrypoints/{{.ID}}/delete" onsubmit="return confirm('Delete entrypoint "{{if .Description}}{{.Description}}{{else}}{{$.BaseURL}}/h/{{.Path}}{{end}}"?\n\nSenders using its URL get an error from now on, and the URL cannot be restored.')" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<button type="submit" class="btn-small text-red-600" title="Delete">Delete</button>
|
<button type="submit" class="btn-small text-red-600" title="Delete">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -249,7 +253,7 @@
|
|||||||
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/delete" onsubmit="return confirm('Delete this target?')" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/delete" onsubmit="return confirm('Delete target "{{.Name}}"?\n\nNothing more is delivered to it. Its past deliveries stay in the event log.')" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<button type="submit" class="btn-small text-red-600" title="Delete">Delete</button>
|
<button type="submit" class="btn-small text-red-600" title="Delete">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user