v0.34.0 7c1886f

blobs3_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2package blobs3_test
3
4import (
5	"context"
6	"net/http"
7	"net/http/httptest"
8	"strings"
9	"testing"
10
11	"bnema.dev/ermite/internal/adapters/blobs3"
12	"bnema.dev/ermite/internal/adapters/blobtest"
13	"bnema.dev/ermite/internal/ports"
14	"github.com/johannesboyne/gofakes3"
15	"github.com/johannesboyne/gofakes3/backend/s3mem"
16	"github.com/stretchr/testify/require"
17)
18
19func fake(t *testing.T) (*blobs3.Store, *s3mem.Backend) {
20	t.Helper()
21	backend := s3mem.New()
22	require.NoError(t, backend.CreateBucket("b"))
23	handler := gofakes3.New(backend).Server()
24	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
25		t.Logf("S3 operation: %s %s", r.Method, r.URL.RequestURI())
26		for _, q := range []string{"policy", "acl", "tagging", "lifecycle", "versioning"} {
27			if r.URL.Query().Has(q) {
28				t.Errorf("unexpected admin operation %s", r.URL)
29			}
30		}
31		if r.Method == http.MethodPut && strings.Trim(r.URL.Path, "/") == "b" {
32			t.Error("unexpected bucket creation")
33		}
34		if r.Method == http.MethodPut && strings.HasPrefix(r.URL.Path, "/b/") {
35			if r.Header.Get("X-Amz-Content-Sha256") == "UNSIGNED-PAYLOAD" || r.Header.Get("X-Amz-Content-Sha256") == "" {
36				t.Error("payload must be signed")
37			}
38		}
39		handler.ServeHTTP(w, r)
40	}))
41	t.Cleanup(srv.Close)
42	s, e := blobs3.New(context.Background(), blobs3.Config{Bucket: "b", Prefix: "/pfx/", Endpoint: srv.URL, Region: "us-east-1", AccessKeyID: "test", SecretAccessKey: "test-secret", ForcePathStyle: true})
43	require.NoError(t, e)
44	return s, backend
45}
46func TestContract(t *testing.T) {
47	blobtest.Run(t, func(t *testing.T) ports.BlobStore { s, _ := fake(t); return s })
48}
49func TestPrefix(t *testing.T) {
50	s, b := fake(t)
51	_, e := s.Put(context.Background(), "ci/one", strings.NewReader("x"), 1)
52	require.NoError(t, e)
53	objects, e := b.ListBucket("b", nil, gofakes3.ListBucketPage{})
54	require.NoError(t, e)
55	require.Len(t, objects.Contents, 1)
56	require.Equal(t, "pfx/ci/one", objects.Contents[0].Key)
57}
58func TestMissingBucket(t *testing.T) {
59	s, b := fake(t)
60	require.NoError(t, b.DeleteBucket("b"))
61	e := s.Check(context.Background())
62	require.ErrorContains(t, e, `bucket "b" is not reachable at http://`)
63	require.NotContains(t, e.Error(), "test-secret")
64}
65
66func TestCheckDeniedHeadFallsBack(t *testing.T) {
67	var listed bool
68	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
69		if r.Method == http.MethodHead {
70			w.WriteHeader(http.StatusForbidden)
71			return
72		}
73		if r.Method != http.MethodGet || r.URL.Query().Get("list-type") != "2" || r.URL.Query().Get("max-keys") != "1" || r.URL.Query().Get("prefix") != "pfx/" {
74			t.Errorf("unexpected probe: %s %s", r.Method, r.URL)
75		}
76		listed = true
77		w.Header().Set("Content-Type", "application/xml")
78		_, _ = w.Write([]byte(`<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Name>b</Name><IsTruncated>false</IsTruncated></ListBucketResult>`))
79	}))
80	defer srv.Close()
81	s, e := blobs3.New(context.Background(), blobs3.Config{Bucket: "b", Prefix: "pfx", Endpoint: srv.URL, Region: "test-region", AccessKeyID: "test", SecretAccessKey: "test-secret", ForcePathStyle: true})
82	require.NoError(t, e)
83	require.NoError(t, s.Check(context.Background()))
84	require.True(t, listed)
85}

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 2

Tags 45

↑↓ moveenter openesc close