v2.31.0-dev5 4162c86

handler_test.go RawHistoryFolder

1package proxy
2
3import (
4	"bytes"
5	"io"
6	"net"
7	"net/http"
8	"net/http/httptest"
9	"testing"
10
11	"github.com/bnema/zerowrap"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/mock"
14
15	"github.com/bnema/gordon/internal/boundaries/in"
16	inmocks "github.com/bnema/gordon/internal/boundaries/in/mocks"
17	"github.com/bnema/gordon/internal/domain"
18)
19
20func testLogger() zerowrap.Logger {
21	return zerowrap.Default()
22}
23
24func TestHandler_ConcurrentConnectionLimit_503WhenFull(t *testing.T) {
25	proxySvc := inmocks.NewMockProxyService(t)
26
27	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{
28		MaxConcurrentConns: 1,
29	})
30
31	handler := NewHandler(proxySvc, nil, testLogger())
32
33	// Simulate an in-flight connection.
34	handler.activeConns.Add(1)
35	defer handler.activeConns.Add(-1)
36
37	req := httptest.NewRequest(http.MethodGet, "http://example.com/test", nil)
38	w := httptest.NewRecorder()
39	handler.ServeHTTP(w, req)
40
41	assert.Equal(t, http.StatusServiceUnavailable, w.Code)
42}
43
44func TestHandler_RoutesToRegistry(t *testing.T) {
45	proxySvc := inmocks.NewMockProxyService(t)
46
47	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{
48		RegistryPort: 5000,
49	})
50	proxySvc.EXPECT().IsRegistryDomain("registry.example.com").Return(true)
51	proxySvc.EXPECT().TrackRegistryRequest().Return()
52	proxySvc.EXPECT().ReleaseRegistryRequest().Return()
53
54	handler := NewHandler(proxySvc, nil, testLogger())
55
56	req := httptest.NewRequest(http.MethodGet, "http://registry.example.com/v2/", nil)
57	req.Host = "registry.example.com"
58	w := httptest.NewRecorder()
59	handler.ServeHTTP(w, req)
60
61	assert.True(t, w.Code == http.StatusBadGateway || w.Code == http.StatusServiceUnavailable)
62}
63
64func TestHandler_Returns404WhenNoTarget(t *testing.T) {
65	proxySvc := inmocks.NewMockProxyService(t)
66
67	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
68	proxySvc.EXPECT().IsRegistryDomain("unknown.example.com").Return(false)
69	proxySvc.EXPECT().GetTarget(mock.Anything, "unknown.example.com").Return(nil, domain.ErrNoTargetAvailable)
70
71	handler := NewHandler(proxySvc, nil, testLogger())
72
73	req := httptest.NewRequest(http.MethodGet, "http://unknown.example.com/", nil)
74	req.Host = "unknown.example.com"
75	w := httptest.NewRecorder()
76	handler.ServeHTTP(w, req)
77
78	assert.Equal(t, http.StatusNotFound, w.Code)
79}
80
81func TestHandler_ProxiesToTarget(t *testing.T) {
82	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
83		w.WriteHeader(http.StatusOK)
84		_, _ = w.Write([]byte("ok"))
85	}))
86	defer backend.Close()
87
88	proxySvc := inmocks.NewMockProxyService(t)
89
90	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{})
91	proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false)
92
93	backendPort := backend.Listener.Addr().(*net.TCPAddr).Port
94
95	target := &domain.ProxyTarget{
96		Host:        "127.0.0.1",
97		Port:        backendPort,
98		ContainerID: "c-1",
99		Scheme:      "http",
100	}
101	proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(target, nil)
102	proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
103
104	handler := NewHandler(proxySvc, nil, testLogger())
105
106	req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
107	req.Host = "app.example.com"
108	w := httptest.NewRecorder()
109	handler.ServeHTTP(w, req)
110
111	assert.Equal(t, http.StatusOK, w.Code)
112	assert.Equal(t, "ok", w.Body.String())
113}
114
115func TestHandler_ReadsMaxBodySizeConfig(t *testing.T) {
116	// Backend that reads the full request body, triggering MaxBytesReader.
117	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118		_, _ = io.ReadAll(r.Body)
119		w.WriteHeader(http.StatusOK)
120	}))
121	defer backend.Close()
122
123	proxySvc := inmocks.NewMockProxyService(t)
124	backendPort := backend.Listener.Addr().(*net.TCPAddr).Port
125
126	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{
127		MaxBodySize: 1024,
128	})
129	proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false)
130	proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(&domain.ProxyTarget{
131		Host:        "127.0.0.1",
132		Port:        backendPort,
133		ContainerID: "c-1",
134		Scheme:      "http",
135	}, nil)
136	proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
137
138	handler := NewHandler(proxySvc, nil, testLogger())
139
140	// Send a body larger than the 1024-byte limit.
141	largeBody := bytes.NewReader(make([]byte, 2048))
142	req := httptest.NewRequest(http.MethodPost, "http://app.example.com/test", largeBody)
143	req.Host = "app.example.com"
144	w := httptest.NewRecorder()
145	handler.ServeHTTP(w, req)
146
147	assert.Equal(t, http.StatusRequestEntityTooLarge, w.Code)
148}
149
150func TestHandler_UpdatedConfigReflected(t *testing.T) {
151	proxySvc := inmocks.NewMockProxyService(t)
152
153	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{
154		MaxConcurrentConns: 0,
155	}).Once()
156	proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false).Once()
157	proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(nil, domain.ErrNoTargetAvailable).Once()
158
159	handler := NewHandler(proxySvc, nil, testLogger())
160
161	req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
162	req.Host = "app.example.com"
163	rec := httptest.NewRecorder()
164	handler.ServeHTTP(rec, req)
165	assert.Equal(t, http.StatusNotFound, rec.Code)
166
167	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{
168		MaxConcurrentConns: 1,
169	}).Once()
170
171	handler.activeConns.Add(1)
172	defer handler.activeConns.Add(-1)
173
174	req2 := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
175	req2.Host = "app.example.com"
176	rec2 := httptest.NewRecorder()
177	handler.ServeHTTP(rec2, req2)
178	assert.Equal(t, http.StatusServiceUnavailable, rec2.Code)
179}
180
181func TestHandler_NoConcurrencyLimitWhenZero(t *testing.T) {
182	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
183		w.WriteHeader(http.StatusOK)
184	}))
185	defer backend.Close()
186
187	proxySvc := inmocks.NewMockProxyService(t)
188	backendPort := backend.Listener.Addr().(*net.TCPAddr).Port
189
190	proxySvc.EXPECT().ProxyConfig().Return(in.ProxyServiceConfig{
191		MaxConcurrentConns: 0,
192	})
193	proxySvc.EXPECT().IsRegistryDomain("app.example.com").Return(false)
194	proxySvc.EXPECT().GetTarget(mock.Anything, "app.example.com").Return(&domain.ProxyTarget{
195		Host:        "127.0.0.1",
196		Port:        backendPort,
197		ContainerID: "c-1",
198		Scheme:      "http",
199	}, nil)
200	proxySvc.EXPECT().TrackInFlight("c-1").Return(func() {})
201
202	handler := NewHandler(proxySvc, nil, testLogger())
203
204	req := httptest.NewRequest(http.MethodGet, "http://app.example.com/", nil)
205	req.Host = "app.example.com"
206	w := httptest.NewRecorder()
207	handler.ServeHTTP(w, req)
208
209	assert.Equal(t, http.StatusOK, w.Code)
210}

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