web_options_test.go RawHistoryFolder

1package app
2
3import (
4	"context"
5	"net/http/httptest"
6	"os"
7	"path/filepath"
8	"strings"
9	"testing"
10
11	"github.com/bnema/vev/internal/adapters/webterm"
12	"github.com/bnema/vev/pkg/safedir"
13	"github.com/stretchr/testify/require"
14)
15
16func TestWebFlags(t *testing.T) {
17	for _, tt := range []struct {
18		args []string
19		want webOptions
20		fail bool
21	}{
22		{[]string{"--web-daemon"}, webOptions{}, false},
23		{[]string{"--web-daemon", "--web-listen", "127.0.0.1:9000", "--web-origin=https://terminal.example.internal"}, webOptions{"127.0.0.1:9000", "https://terminal.example.internal"}, false},
24		{[]string{"--web-daemon", "--web-listen"}, webOptions{}, true},
25		{[]string{"--web-daemon", "--web-origin="}, webOptions{}, true},
26		{[]string{"--web-daemon", "--web-listen", "--web-origin=x"}, webOptions{}, true},
27		{[]string{"--web-daemon", "extra"}, webOptions{}, true},
28		{[]string{"--web-renew-token", "--web-origin=x"}, webOptions{}, true},
29	} {
30		t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
31			got, err := parseArgs(tt.args)
32			if tt.fail {
33				require.Error(t, err)
34				return
35			}
36			require.NoError(t, err)
37			require.Equal(t, kindWebDaemon, got.kind)
38			require.Equal(t, tt.want, got.web)
39		})
40	}
41}
42
43func TestWebConfigPrecedence(t *testing.T) {
44	root := t.TempDir()
45	t.Setenv("XDG_CONFIG_HOME", root)
46	dir := filepath.Join(root, "vev")
47	require.NoError(t, safedir.EnsurePrivate(dir))
48	path := filepath.Join(dir, "config")
49	got, _, err := loadWebSettings(webOptions{})
50	require.NoError(t, err)
51	require.Equal(t, webterm.Settings{Listen: webterm.Address, Origin: webterm.Origin}, got)
52	for _, tt := range []struct {
53		name, config   string
54		flags          webOptions
55		listen, origin string
56		fail           bool
57	}{
58		{"persisted", "web.listen = 127.0.0.1:9000\nweb.origin = https://terminal.example.internal\n", webOptions{}, "127.0.0.1:9000", "https://terminal.example.internal", false},
59		{"flags", "web.listen = invalid\nweb.origin = invalid\n", webOptions{"127.0.0.1:9001", "http://localhost:9001"}, "127.0.0.1:9001", "http://localhost:9001", false},
60		{"invalid config", "web.listen = invalid\n", webOptions{}, "", "", true},
61		{"explicit exposure", "web.listen = 0.0.0.0:9000\n", webOptions{}, "", "", true},
62		{"config warnings propagate", "web.listen = 127.0.0.1:9000\nweb.origin = https://terminal.example.internal\nbadline\n", webOptions{}, "127.0.0.1:9000", "https://terminal.example.internal", false},
63	} {
64		t.Run(tt.name, func(t *testing.T) {
65			require.NoError(t, os.WriteFile(path, []byte(tt.config), 0600))
66			got, warnings, err := loadWebSettings(tt.flags)
67			if tt.fail {
68				require.Error(t, err)
69				return
70			}
71			require.NoError(t, err)
72			require.Equal(t, webterm.Settings{Listen: tt.listen, Origin: tt.origin}, got)
73			if tt.name == "config warnings propagate" {
74				require.NotEmpty(t, warnings, "config warnings must reach the web launcher")
75			}
76		})
77	}
78}
79
80func TestWebReadinessUsesLocalListener(t *testing.T) {
81	token, err := webterm.NewToken()
82	require.NoError(t, err)
83	settings, err := webterm.ParseSettings("", "https://unresolvable.example.invalid")
84	require.NoError(t, err)
85	handler, err := webterm.NewServer(t.Context(), settings, token, func(context.Context, *webterm.Terminal) error { return nil })
86	require.NoError(t, err)
87	server := httptest.NewServer(handler)
88	defer server.Close()
89	settings.Listen = server.Listener.Addr().String()
90	access := webAccess{Token: token, Settings: settings}
91	client := server.Client()
92	t.Setenv("HTTP_PROXY", "http://unresolvable.example.invalid")
93	require.True(t, webReachable(t.Context(), client, access, settings))
94	other := settings
95	other.Origin = "https://other.example.invalid"
96	require.False(t, webReachable(t.Context(), client, access, other), "a healthy different gateway must not satisfy readiness")
97	other = settings
98	other.Listen = "127.0.0.1:1"
99	require.False(t, webReachable(t.Context(), client, access, other), "the requested listener must match too")
100	access.Token = "wrong"
101	require.False(t, webReachable(t.Context(), client, access, settings))
102}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close