v2.31.0-dev6 8056b57

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

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 8

Tags 106

↑↓ moveenter openesc close