v0.15.0 9006957

updater_test.go RawHistoryFolder

1package filtering
2
3import (
4	"context"
5	"net/http"
6	"net/http/httptest"
7	"testing"
8	"time"
9
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestFetchRemoteVersion(t *testing.T) {
15	tests := []struct {
16		name           string
17		responseStatus int
18		etag           string
19		lastModified   string
20		wantVersion    string
21		wantErr        bool
22	}{
23		{
24			name:           "returns ETag when present",
25			responseStatus: http.StatusOK,
26			etag:           `"abc123"`,
27			wantVersion:    `"abc123"`,
28			wantErr:        false,
29		},
30		{
31			name:           "returns Last-Modified when no ETag",
32			responseStatus: http.StatusOK,
33			lastModified:   "Sat, 29 Nov 2025 12:48:00 GMT",
34			wantVersion:    "Sat, 29 Nov 2025 12:48:00 GMT",
35			wantErr:        false,
36		},
37		{
38			name:           "prefers ETag over Last-Modified",
39			responseStatus: http.StatusOK,
40			etag:           `"xyz789"`,
41			lastModified:   "Sat, 29 Nov 2025 12:48:00 GMT",
42			wantVersion:    `"xyz789"`,
43			wantErr:        false,
44		},
45		{
46			name:           "error when no version headers",
47			responseStatus: http.StatusOK,
48			wantVersion:    "",
49			wantErr:        true,
50		},
51		{
52			name:           "server error",
53			responseStatus: http.StatusInternalServerError,
54			wantVersion:    "",
55			wantErr:        true,
56		},
57	}
58
59	for _, tt := range tests {
60		t.Run(tt.name, func(t *testing.T) {
61			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62				// Verify HEAD request
63				assert.Equal(t, http.MethodHead, r.Method)
64
65				if tt.etag != "" {
66					w.Header().Set("ETag", tt.etag)
67				}
68				if tt.lastModified != "" {
69					w.Header().Set("Last-Modified", tt.lastModified)
70				}
71				w.WriteHeader(tt.responseStatus)
72			}))
73			defer server.Close()
74
75			fu := &FilterUpdater{
76				httpClient: &http.Client{Timeout: 5 * time.Second},
77			}
78
79			version, err := fu.fetchRemoteVersion(context.Background(), server.URL)
80
81			if tt.wantErr {
82				assert.Error(t, err)
83			} else {
84				require.NoError(t, err)
85				assert.Equal(t, tt.wantVersion, version)
86			}
87		})
88	}
89}
90
91func TestNeedsUpdate(t *testing.T) {
92	tests := []struct {
93		name           string
94		storedVersion  string
95		remoteETag     string
96		serverError    bool
97		expectedResult bool
98	}{
99		{
100			name:           "etags differ - needs update",
101			storedVersion:  `"old-etag"`,
102			remoteETag:     `"new-etag"`,
103			expectedResult: true,
104		},
105		{
106			name:           "etags same - no update",
107			storedVersion:  `"same-etag"`,
108			remoteETag:     `"same-etag"`,
109			expectedResult: false,
110		},
111		{
112			name:           "no stored version - needs update",
113			storedVersion:  "",
114			remoteETag:     `"any-etag"`,
115			expectedResult: true,
116		},
117		{
118			name:           "server error - assume update needed",
119			storedVersion:  `"old-etag"`,
120			serverError:    true,
121			expectedResult: true,
122		},
123	}
124
125	for _, tt := range tests {
126		t.Run(tt.name, func(t *testing.T) {
127			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
128				if tt.serverError {
129					w.WriteHeader(http.StatusInternalServerError)
130					return
131				}
132				w.Header().Set("ETag", tt.remoteETag)
133				w.WriteHeader(http.StatusOK)
134			}))
135			defer server.Close()
136
137			mockStore := &mockFilterStore{
138				sourceVersions: map[string]string{
139					server.URL: tt.storedVersion,
140				},
141			}
142
143			fm := &FilterManager{store: mockStore}
144			fu := &FilterUpdater{
145				manager:    fm,
146				httpClient: &http.Client{Timeout: 5 * time.Second},
147			}
148
149			result := fu.needsUpdate(context.Background(), server.URL)
150			assert.Equal(t, tt.expectedResult, result)
151		})
152	}
153}
154
155// mockFilterStore implements FilterStore interface for testing
156type mockFilterStore struct {
157	sourceVersions map[string]string
158	cachedFilters  *CompiledFilters
159	cacheExists    bool
160	cacheModTime   time.Time
161	lastCheckTime  time.Time
162}
163
164func (m *mockFilterStore) LoadCached() (*CompiledFilters, error) {
165	return m.cachedFilters, nil
166}
167
168func (m *mockFilterStore) SaveCache(filters *CompiledFilters) error {
169	m.cachedFilters = filters
170	return nil
171}
172
173func (m *mockFilterStore) GetCacheInfo() (bool, time.Time, error) {
174	return m.cacheExists, m.cacheModTime, nil
175}
176
177func (m *mockFilterStore) GetSourceVersion(url string) string {
178	if m.sourceVersions == nil {
179		return ""
180	}
181	return m.sourceVersions[url]
182}
183
184func (m *mockFilterStore) SetSourceVersion(url string, version string) error {
185	if m.sourceVersions == nil {
186		m.sourceVersions = make(map[string]string)
187	}
188	m.sourceVersions[url] = version
189	return nil
190}
191
192func (m *mockFilterStore) GetLastCheckTime() time.Time {
193	return m.lastCheckTime
194}

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 42

↑↓ moveenter openesc close