Files
gitea-tea/internal/terminal/view_test.go
T
bircni ab3e91acd1 fix(webhook): validate events and types, sort events and show delivery URLs
webhook create --events pushh created a webhook that fired on nothing,
since Gitea drops unknown events, and unknown types or a Slack webhook
without a channel failed with raw HTTP 422 errors. --events and --type
now accept only what Gitea knows, list it in help and complete it, and a
Slack webhook needs --config channel=.

Events came back in a different order on every call; they are sorted
now. Webhooks can be told apart again: list, view and JSON show the
delivery URL without user information and query, and without the path
for chat services whose paths carry tokens; view --reveal-url shows it
whole. Tables collapse complete event groups such as pull_request (8).

On a terminal, webhook edit no longer needs an authorization flag: since
Gitea drops the stored header on every edit and never returns it, it asks
for the header again and keeps it, and an empty answer suits webhooks
without one. Without a terminal the edit still requires the flags. Edit
flags lose the create defaults, the unsupported secret flags are hidden
on edit, deletions name the webhook and its scope, and mutations print ✓
lines. terminal.View gains Bodyless for resources without a body.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:42:49 +02:00

116 lines
4.3 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package terminal
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func issueView() View {
return View{
Title: "Login fails\x1b[2J", ID: "#1",
Badges: []Cell{{Text: "Open", Color: "green"}},
Meta: "alice opened about 1 hour ago • 2 comments",
Fields: []Field{
{Label: "State", Value: "open", PipedOnly: true},
{Label: "Labels", Cells: []Cell{{Text: "bug", Color: "ee0701"}, {Text: "priority/high"}}},
{Label: "Milestone", Value: "v1.0"},
{Label: "Blocked by"},
{Label: "Comments", Value: "2", TTYOnly: true},
},
Body: "Steps:\r\n1. Use `p@ss!`\x1b]52;c;aGk=\x07\n",
Kind: "issue", URL: "http://localhost:3001/alice/project/issues/1",
}
}
func TestViewRender(t *testing.T) {
for _, tc := range []struct {
name string
view View
d Display
want string
}{
{
"key and value lines when piped",
issueView(),
Display{Color: true},
"title:\tLogin fails[2J\nstate:\topen\nlabels:\tbug, priority/high\nmilestone:\tv1.0\nblocked by:\t\n" +
"url:\thttp://localhost:3001/alice/project/issues/1\n--\nSteps:\n1. Use `p@ss!`]52;c;aGk=\n",
},
{
"summary for people",
issueView(),
Display{TTY: true},
"Login fails[2J #1\nOpen • alice opened about 1 hour ago • 2 comments\n" +
"Labels: bug, priority/high\nMilestone: v1.0\nComments: 2\n\n" +
"Steps:\n1. Use `p@ss!`]52;c;aGk=\n\nView this issue on Gitea: http://localhost:3001/alice/project/issues/1\n",
},
{
"styled for color terminals",
View{Title: "v1.0", Badges: []Cell{{Text: "Latest", Color: "green"}}, Fields: []Field{{Label: "Labels", Cells: []Cell{{Text: "bug", Color: "ee0701"}}}}, URL: "https://example.org/r"},
Display{TTY: true, Color: true, TrueColor: true},
"\x1b[1mv1.0\x1b[0m\n\x1b[32mLatest\x1b[0m\n\x1b[1mLabels:\x1b[0m \x1b[38;2;238;7;1mbug\x1b[0m\n\n" +
"\x1b[90mNo description provided\x1b[0m\n\n\x1b[90mView on Gitea: https://example.org/r\x1b[0m\n",
},
{
"minimal",
View{Title: "Home"},
Display{TTY: true},
"Home\n\nNo description provided\n",
},
{
"bodyless for people",
View{Title: "laptop", Fields: []Field{{Label: "Type", Value: "ssh-ed25519"}}, Bodyless: true, URL: "https://example.org/keys"},
Display{TTY: true},
"laptop\nType: ssh-ed25519\n\nView on Gitea: https://example.org/keys\n",
},
{
"bodyless when piped",
View{Title: "laptop", Fields: []Field{{Label: "Type", Value: "ssh-ed25519"}}, Bodyless: true, URL: "https://example.org/keys"},
Display{},
"title:\tlaptop\ntype:\tssh-ed25519\nurl:\thttps://example.org/keys\n",
},
} {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, tc.view.Render(tc.d))
})
}
}
func TestViewRendersMarkdownOnlyWhenEnabled(t *testing.T) {
view := View{Title: "Docs", Body: "# Setup\n\nRun **tea** [now](https://example.org).\n\n- one\n- two"}
require.Contains(t, view.Render(Display{TTY: true}), "\n# Setup\n\nRun **tea** [now](https://example.org).\n")
require.Contains(t, view.Render(Display{Markdown: true}), "--\n# Setup\n\nRun **tea** [now](https://example.org).\n")
plain := view.Render(Display{TTY: true, Markdown: true, Width: 40})
require.Contains(t, plain, "\n # Setup\n\n Run **tea** now https://example.org.\n\n • one\n • two\n")
require.NotContains(t, plain, "\x1b")
colored := view.Render(Display{TTY: true, Markdown: true, Color: true})
require.Contains(t, colored, "Run \x1b[1mtea\x1b[m")
require.NotContains(t, colored, "\x1b]8;")
require.Contains(t, view.Render(Display{TTY: true, Markdown: true, Color: true, Links: true}), "\x1b]8;id=")
}
func TestMarkdownSanitizesServerText(t *testing.T) {
rendered := Markdown("Hello \x1b]0;PWNED\x07world\n\n`\x1b[2Jcode`", Display{Color: true, Links: true})
require.NotContains(t, rendered, "\x1b]0;")
require.NotContains(t, rendered, "\x1b[2J")
require.Contains(t, rendered, "]0;PWNEDworld")
for _, line := range strings.Split(Markdown(strings.Repeat("word ", 50), Display{Width: 40}), "\n") {
require.LessOrEqual(t, len(line), 40, line)
require.Equal(t, strings.TrimRight(line, " "), line)
}
}
func TestMarkdownEnabled(t *testing.T) {
require.True(t, MarkdownEnabled("auto", true))
require.True(t, MarkdownEnabled("", true))
require.False(t, MarkdownEnabled("raw", true))
require.False(t, MarkdownEnabled("auto", false))
}