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}