v2.31.0-dev17 bd898f0

handler_test.go RawHistoryFolder

1package onboarding_test
2
3import (
4	"io"
5	"net/http"
6	"net/http/httptest"
7	"testing"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11
12	"github.com/bnema/gordon/internal/adapters/in/http/onboarding"
13)
14
15const (
16	testFingerprint = "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
17	testHTTPPort    = 8088
18	testTLSPort     = 8443
19)
20
21func newTestHandler() (*onboarding.Handler, []byte, []byte) {
22	rootPEM := []byte("-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----\n")
23	mobileconfig := []byte("<plist>com.apple.security.root</plist>")
24
25	h := onboarding.NewHandler(rootPEM, mobileconfig, testFingerprint, testHTTPPort, testTLSPort)
26	return h, rootPEM, mobileconfig
27}
28
29func newTestServer(t *testing.T) (*httptest.Server, []byte, []byte) {
30	t.Helper()
31	h, rootPEM, mobileconfig := newTestHandler()
32
33	mux := http.NewServeMux()
34	mux.HandleFunc("GET /.well-known/gordon/{$}", h.ServeOnboardingPage)
35	mux.HandleFunc("HEAD /.well-known/gordon/{$}", h.ServeOnboardingPage)
36	mux.HandleFunc("GET /.well-known/gordon/ca", h.ServeOnboardingPage)
37	mux.HandleFunc("HEAD /.well-known/gordon/ca", h.ServeOnboardingPage)
38	mux.HandleFunc("GET /.well-known/gordon/ca.crt", h.ServeCACert)
39	mux.HandleFunc("HEAD /.well-known/gordon/ca.crt", h.ServeCACert)
40	mux.HandleFunc("GET /.well-known/gordon/ca.mobileconfig", h.ServeMobileconfig)
41	mux.HandleFunc("HEAD /.well-known/gordon/ca.mobileconfig", h.ServeMobileconfig)
42
43	srv := httptest.NewServer(mux)
44	t.Cleanup(srv.Close)
45	return srv, rootPEM, mobileconfig
46}
47
48// getOnboardingBody issues a GET /.well-known/gordon/ca with a custom Host header and returns the body.
49func getOnboardingBody(t *testing.T, srv *httptest.Server, host string) string {
50	t.Helper()
51	req, err := http.NewRequest(http.MethodGet, srv.URL+"/.well-known/gordon/ca", nil)
52	require.NoError(t, err)
53	req.Host = host
54
55	resp, err := http.DefaultClient.Do(req)
56	require.NoError(t, err)
57	defer resp.Body.Close()
58
59	require.Equal(t, http.StatusOK, resp.StatusCode)
60	body, err := io.ReadAll(resp.Body)
61	require.NoError(t, err)
62	return string(body)
63}
64
65func TestHandler_CACert(t *testing.T) {
66	srv, rootPEM, _ := newTestServer(t)
67
68	resp, err := http.Get(srv.URL + "/.well-known/gordon/ca.crt")
69	require.NoError(t, err)
70	defer resp.Body.Close()
71
72	body, err := io.ReadAll(resp.Body)
73	require.NoError(t, err)
74
75	assert.Equal(t, http.StatusOK, resp.StatusCode)
76	assert.Equal(t, "application/x-x509-ca-cert", resp.Header.Get("Content-Type"))
77	assert.Equal(t, rootPEM, body)
78}
79
80func TestHandler_Mobileconfig(t *testing.T) {
81	srv, _, _ := newTestServer(t)
82
83	resp, err := http.Get(srv.URL + "/.well-known/gordon/ca.mobileconfig")
84	require.NoError(t, err)
85	defer resp.Body.Close()
86
87	body, err := io.ReadAll(resp.Body)
88	require.NoError(t, err)
89
90	assert.Equal(t, http.StatusOK, resp.StatusCode)
91	assert.Equal(t, "application/x-apple-aspen-config", resp.Header.Get("Content-Type"))
92	assert.Contains(t, string(body), "com.apple.security.root")
93}
94
95func TestHandler_OnboardingPage(t *testing.T) {
96	srv, _, _ := newTestServer(t)
97
98	resp, err := http.Get(srv.URL + "/.well-known/gordon/")
99	require.NoError(t, err)
100	defer resp.Body.Close()
101
102	body, err := io.ReadAll(resp.Body)
103	require.NoError(t, err)
104
105	assert.Equal(t, http.StatusOK, resp.StatusCode)
106	assert.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
107	assert.Contains(t, string(body), "Gordon")
108	assert.Contains(t, string(body), "/.well-known/gordon/ca.crt")
109	assert.Contains(t, string(body), "/.well-known/gordon/ca.mobileconfig")
110}
111
112func TestHandler_OnboardingPage_DefaultHTTPSURLOmitsInternalTLSPort(t *testing.T) {
113	srv, _, _ := newTestServer(t)
114	body := getOnboardingBody(t, srv, "o2.bnema.dev")
115
116	assert.Contains(t, body, "https://o2.bnema.dev/")
117	assert.NotContains(t, body, ":8443")
118}
119
120func TestHandler_OnboardingPage_ExplicitHTTPPortMapsToTLSPort(t *testing.T) {
121	srv, _, _ := newTestServer(t)
122	body := getOnboardingBody(t, srv, "o2.bnema.dev:8088")
123
124	assert.Contains(t, body, "https://o2.bnema.dev:8443/")
125}
126
127func TestHandler_OnboardingPage_ExplicitNonHTTPPortIsPreserved(t *testing.T) {
128	srv, _, _ := newTestServer(t)
129	body := getOnboardingBody(t, srv, "o2.bnema.dev:9999")
130
131	assert.Contains(t, body, "https://o2.bnema.dev:9999/")
132}
133
134func TestHandler_OnboardingPage_IPHostHidesGoToSiteLink(t *testing.T) {
135	srv, _, _ := newTestServer(t)
136	body := getOnboardingBody(t, srv, "100.83.240.53")
137
138	assert.NotContains(t, body, "Go to site")
139}
140
141func TestHandler_OnboardingPage_IncludesFingerprintAndClientImportCopy(t *testing.T) {
142	srv, _, _ := newTestServer(t)
143	body := getOnboardingBody(t, srv, "o2.bnema.dev")
144
145	// Fingerprint must be visible
146	assert.Contains(t, body, testFingerprint)
147
148	// Client-import focused: Firefox/Zen guidance present
149	assert.Contains(t, body, "Firefox")
150	assert.Contains(t, body, "Zen")
151
152	// Should NOT suggest gordon ca install as the primary action
153	assert.NotContains(t, body, "sudo gordon ca install")
154}
155
156func TestHandler_OnboardingPage_GoToSiteLinkIsPlainLink(t *testing.T) {
157	srv, _, _ := newTestServer(t)
158
159	body := getOnboardingBody(t, srv, "o2.bnema.dev")
160
161	assert.Contains(t, body, `<a href="https://o2.bnema.dev/">Go to site &#x2192;</a>`)
162	assert.NotContains(t, body, "onclick=")
163	assert.NotContains(t, body, "document.cookie")
164}
165
166func TestHandler_OnboardingPage_HEAD(t *testing.T) {
167	srv, _, _ := newTestServer(t)
168
169	req, err := http.NewRequest(http.MethodHead, srv.URL+"/.well-known/gordon/ca", nil)
170	require.NoError(t, err)
171
172	resp, err := http.DefaultClient.Do(req)
173	require.NoError(t, err)
174	defer resp.Body.Close()
175
176	assert.Equal(t, http.StatusOK, resp.StatusCode)
177	assert.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
178
179	body, err := io.ReadAll(resp.Body)
180	require.NoError(t, err)
181	assert.Empty(t, body)
182}
183
184func TestHandler_CACert_HEAD(t *testing.T) {
185	srv, _, _ := newTestServer(t)
186
187	req, err := http.NewRequest(http.MethodHead, srv.URL+"/.well-known/gordon/ca.crt", nil)
188	require.NoError(t, err)
189
190	resp, err := http.DefaultClient.Do(req)
191	require.NoError(t, err)
192	defer resp.Body.Close()
193
194	assert.Equal(t, http.StatusOK, resp.StatusCode)
195	assert.Equal(t, "application/x-x509-ca-cert", resp.Header.Get("Content-Type"))
196
197	body, err := io.ReadAll(resp.Body)
198	require.NoError(t, err)
199	assert.Empty(t, body)
200}
201
202func TestHandler_Mobileconfig_HEAD(t *testing.T) {
203	srv, _, _ := newTestServer(t)
204
205	req, err := http.NewRequest(http.MethodHead, srv.URL+"/.well-known/gordon/ca.mobileconfig", nil)
206	require.NoError(t, err)
207
208	resp, err := http.DefaultClient.Do(req)
209	require.NoError(t, err)
210	defer resp.Body.Close()
211
212	assert.Equal(t, http.StatusOK, resp.StatusCode)
213	assert.Equal(t, "application/x-apple-aspen-config", resp.Header.Get("Content-Type"))
214
215	body, err := io.ReadAll(resp.Body)
216	require.NoError(t, err)
217	assert.Empty(t, body)
218}

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 7

Tags 106

↑↓ moveenter openesc close