http_timeouts_config_test.go RawHistoryFolder

1package app
2
3import (
4	"net/http"
5	"os"
6	"path/filepath"
7	"testing"
8	"time"
9
10	"github.com/bnema/zerowrap"
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/require"
13
14	"github.com/bnema/gordon/internal/domain"
15	"github.com/bnema/gordon/internal/usecase/traffic"
16)
17
18func writeTimeoutConfig(t *testing.T, body string) string {
19	t.Helper()
20	path := filepath.Join(t.TempDir(), "gordon.toml")
21	require.NoError(t, os.WriteFile(path, []byte(body), 0o600))
22	return path
23}
24
25func TestInitConfigHTTPTimeoutDefaults(t *testing.T) {
26	_, cfg, err := initConfig(writeTimeoutConfig(t, ""))
27	require.NoError(t, err)
28
29	registry, err := buildRegistryHTTPTimeouts(cfg)
30	require.NoError(t, err)
31	assert.Equal(t, domain.RegistryHTTPTimeouts{
32		ReadHeaderTimeout: 10 * time.Second,
33		ReadTimeout:       5 * time.Minute,
34		WriteTimeout:      5 * time.Minute,
35		IdleTimeout:       120 * time.Second,
36	}, registry)
37
38	entry, err := traffic.ParseHTTPOptions(cfg.Traffic.HTTP)
39	require.NoError(t, err)
40	assert.Equal(t, domain.HTTPOptions{
41		ReadHeaderTimeout:   5 * time.Second,
42		IdleTimeout:         30 * time.Second,
43		TransferIdleTimeout: 2 * time.Minute,
44	}, entry)
45}
46
47func TestInitConfigHTTPTimeoutFileOverride(t *testing.T) {
48	path := writeTimeoutConfig(t, `
49[server.http]
50read_header_timeout = "3s"
51read_timeout = "1m"
52write_timeout = "2m"
53idle_timeout = "45s"
54
55[traffic.http]
56read_header_timeout = "4s"
57idle_timeout = "15s"
58transfer_idle_timeout = "10m"
59`)
60	_, cfg, err := initConfig(path)
61	require.NoError(t, err)
62
63	registry, err := buildRegistryHTTPTimeouts(cfg)
64	require.NoError(t, err)
65	assert.Equal(t, 3*time.Second, registry.ReadHeaderTimeout)
66	assert.Equal(t, time.Minute, registry.ReadTimeout)
67	assert.Equal(t, 2*time.Minute, registry.WriteTimeout)
68	assert.Equal(t, 45*time.Second, registry.IdleTimeout)
69
70	entry, err := traffic.ParseHTTPOptions(cfg.Traffic.HTTP)
71	require.NoError(t, err)
72	assert.Equal(t, 4*time.Second, entry.ReadHeaderTimeout)
73	assert.Equal(t, 15*time.Second, entry.IdleTimeout)
74	assert.Equal(t, 10*time.Minute, entry.TransferIdleTimeout)
75}
76
77func TestInitConfigHTTPTimeoutEnvOverride(t *testing.T) {
78	t.Setenv("GORDON_TRAFFIC_HTTP_TRANSFER_IDLE_TIMEOUT", "7m")
79	t.Setenv("GORDON_TRAFFIC_HTTP_READ_HEADER_TIMEOUT", "6s")
80	t.Setenv("GORDON_TRAFFIC_HTTP_IDLE_TIMEOUT", "11s")
81	t.Setenv("GORDON_SERVER_HTTP_WRITE_TIMEOUT", "9m")
82	t.Setenv("GORDON_SERVER_HTTP_READ_TIMEOUT", "8m")
83	t.Setenv("GORDON_SERVER_HTTP_READ_HEADER_TIMEOUT", "12s")
84	t.Setenv("GORDON_SERVER_HTTP_IDLE_TIMEOUT", "50s")
85	t.Setenv("GORDON_TRAFFIC_TCP_DIAL_TIMEOUT", "3s")
86
87	path := writeTimeoutConfig(t, "[traffic.http]\ntransfer_idle_timeout = \"1m\"\n")
88	_, cfg, err := initConfig(path)
89	require.NoError(t, err)
90
91	entry, err := traffic.ParseHTTPOptions(cfg.Traffic.HTTP)
92	require.NoError(t, err)
93	assert.Equal(t, 7*time.Minute, entry.TransferIdleTimeout, "env wins over file")
94	assert.Equal(t, 6*time.Second, entry.ReadHeaderTimeout)
95	assert.Equal(t, 11*time.Second, entry.IdleTimeout)
96
97	registry, err := buildRegistryHTTPTimeouts(cfg)
98	require.NoError(t, err)
99	assert.Equal(t, 9*time.Minute, registry.WriteTimeout)
100	assert.Equal(t, 8*time.Minute, registry.ReadTimeout)
101	assert.Equal(t, 12*time.Second, registry.ReadHeaderTimeout)
102	assert.Equal(t, 50*time.Second, registry.IdleTimeout)
103
104	// Existing traffic.tcp keys are overridable too (they need a default).
105	assert.Equal(t, "3s", cfg.Traffic.TCP.DialTimeout)
106}
107
108func TestInitConfigRejectsInvalidHTTPTimeouts(t *testing.T) {
109	tests := []struct {
110		name string
111		body string
112		key  string
113	}{
114		{"malformed registry", "[server.http]\nread_timeout = \"soon\"\n", "server.http.read_timeout"},
115		{"negative registry", "[server.http]\nwrite_timeout = \"-5s\"\n", "server.http.write_timeout"},
116		{"zero registry", "[server.http]\nidle_timeout = \"0s\"\n", "server.http.idle_timeout"},
117		{"malformed traffic", "[traffic.http]\nread_header_timeout = \"x\"\n", "traffic.http.read_header_timeout"},
118		{"negative traffic", "[traffic.http]\ntransfer_idle_timeout = \"-1m\"\n", "traffic.http.transfer_idle_timeout"},
119		{"zero traffic", "[traffic.http]\nidle_timeout = \"0\"\n", "traffic.http.idle_timeout"},
120	}
121	for _, tt := range tests {
122		t.Run(tt.name, func(t *testing.T) {
123			_, _, err := initConfig(writeTimeoutConfig(t, tt.body))
124			require.Error(t, err)
125			assert.ErrorContains(t, err, tt.key)
126		})
127	}
128}
129
130func TestInitConfigRejectsInvalidHTTPTimeoutFromEnv(t *testing.T) {
131	t.Setenv("GORDON_TRAFFIC_HTTP_TRANSFER_IDLE_TIMEOUT", "-3s")
132	_, _, err := initConfig(writeTimeoutConfig(t, ""))
133	require.ErrorContains(t, err, "traffic.http.transfer_idle_timeout")
134}
135
136func TestStartServerAppliesConfiguredTimeouts(t *testing.T) {
137	timeouts := domain.RegistryHTTPTimeouts{
138		ReadHeaderTimeout: 1 * time.Second,
139		ReadTimeout:       2 * time.Second,
140		WriteTimeout:      3 * time.Second,
141		IdleTimeout:       4 * time.Second,
142	}
143	errChan := make(chan error, 1)
144	srv, ready := startServer("127.0.0.1:0", http.NotFoundHandler(), "test", nil, timeouts, errChan, zerowrap.Default())
145	<-ready
146	defer func() { _ = srv.Close() }()
147
148	assert.Equal(t, timeouts.ReadHeaderTimeout, srv.ReadHeaderTimeout)
149	assert.Equal(t, timeouts.ReadTimeout, srv.ReadTimeout)
150	assert.Equal(t, timeouts.WriteTimeout, srv.WriteTimeout)
151	assert.Equal(t, timeouts.IdleTimeout, srv.IdleTimeout)
152}
153
154func TestStartServerFallsBackToDefaultTimeouts(t *testing.T) {
155	errChan := make(chan error, 1)
156	srv, ready := startServer("127.0.0.1:0", http.NotFoundHandler(), "test", nil, domain.RegistryHTTPTimeouts{}, errChan, zerowrap.Default())
157	<-ready
158	defer func() { _ = srv.Close() }()
159
160	assert.Equal(t, domain.DefaultRegistryHTTPTimeouts(), domain.RegistryHTTPTimeouts{
161		ReadHeaderTimeout: srv.ReadHeaderTimeout,
162		ReadTimeout:       srv.ReadTimeout,
163		WriteTimeout:      srv.WriteTimeout,
164		IdleTimeout:       srv.IdleTimeout,
165	})
166}
167
168func TestStartRegistryServersRejectsInvalidTimeouts(t *testing.T) {
169	cfg := Config{}
170	cfg.Server.HTTP.ReadTimeout = "-1s"
171	_, _, _, _, err := startRegistryServers(cfg, http.NotFoundHandler(), make(chan error, 1), zerowrap.Default())
172	require.ErrorContains(t, err, "server.http.read_timeout")
173}

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