v2.31.0-dev14 3751047

integration_test.go RawHistoryFolder

1package registrypush_test
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"net/http"
8	"net/http/httptest"
9	"strings"
10	"testing"
11
12	"github.com/bnema/zerowrap"
13	v1 "github.com/google/go-containerregistry/pkg/v1"
14	"github.com/google/go-containerregistry/pkg/v1/random"
15	"github.com/stretchr/testify/assert"
16	"github.com/stretchr/testify/require"
17
18	registryhttp "github.com/bnema/gordon/internal/adapters/in/http/registry"
19	"github.com/bnema/gordon/internal/adapters/out/filesystem"
20	"github.com/bnema/gordon/internal/domain"
21	registrysvc "github.com/bnema/gordon/internal/usecase/registry"
22	"github.com/bnema/gordon/pkg/registrypush"
23)
24
25func TestIntegration_PushSingleLayerImage(t *testing.T) {
26	t.Parallel()
27
28	ctx := context.Background()
29	img, err := random.Image(512, 1)
30	require.NoError(t, err)
31
32	harness := newRegistryHarness(t, registryhttp.DefaultMaxBlobChunkSize)
33	defer harness.Close()
34
35	err = harness.Push(ctx, img, registrypush.WithChunkSize(256))
36	require.NoError(t, err)
37
38	harness.RequireManifestAndBlobs(ctx, t, img)
39}
40
41func TestIntegration_PushMultiLayerChunked(t *testing.T) {
42	t.Parallel()
43
44	ctx := context.Background()
45	img, err := random.Image(4096, 3)
46	require.NoError(t, err)
47
48	harness := newRegistryHarness(t, 1025)
49	defer harness.Close()
50
51	err = harness.Push(ctx, img, registrypush.WithChunkSize(1024))
52	require.NoError(t, err)
53
54	harness.RequireManifestAndBlobs(ctx, t, img)
55}
56
57func TestIntegration_PushSkipsExistingBlobs(t *testing.T) {
58	t.Parallel()
59
60	ctx := context.Background()
61	img, err := random.Image(1024, 2)
62	require.NoError(t, err)
63
64	harness := newRegistryHarness(t, registryhttp.DefaultMaxBlobChunkSize)
65	defer harness.Close()
66
67	err = harness.Push(ctx, img, registrypush.WithChunkSize(256))
68	require.NoError(t, err)
69
70	err = harness.Push(ctx, img, registrypush.WithChunkSize(256))
71	require.NoError(t, err)
72}
73
74func TestIntegration_ServerRejectsOversizedChunk(t *testing.T) {
75	t.Parallel()
76
77	ctx := context.Background()
78	img, err := random.Image(4096, 1)
79	require.NoError(t, err)
80
81	harness := newRegistryHarness(t, 512)
82	defer harness.Close()
83
84	err = harness.Push(ctx, img, registrypush.WithChunkSize(4096))
85	require.Error(t, err)
86	assert.Contains(t, err.Error(), "413")
87}
88
89func TestIntegration_ManifestRoundtrip(t *testing.T) {
90	t.Parallel()
91
92	ctx := context.Background()
93	img, err := random.Image(1024, 2)
94	require.NoError(t, err)
95
96	harness := newRegistryHarness(t, registryhttp.DefaultMaxBlobChunkSize)
97	defer harness.Close()
98
99	err = harness.Push(ctx, img, registrypush.WithChunkSize(512))
100	require.NoError(t, err)
101
102	expectedManifest, err := img.RawManifest()
103	require.NoError(t, err)
104
105	expectedMediaType, err := img.MediaType()
106	require.NoError(t, err)
107
108	resp := harness.GetManifest(t)
109	body, readErr := io.ReadAll(resp.Body)
110	require.NoError(t, readErr)
111	require.NoError(t, resp.Body.Close())
112
113	assert.Equal(t, http.StatusOK, resp.StatusCode)
114	assert.Equal(t, expectedManifest, body)
115	assert.Equal(t, string(expectedMediaType), resp.Header.Get("Content-Type"))
116}
117
118type registryHarness struct {
119	server *httptest.Server
120	ref    string
121}
122
123func newRegistryHarness(t *testing.T, maxBlobChunkSize int64) *registryHarness {
124	t.Helper()
125
126	log := zerowrap.Default()
127	rootDir := t.TempDir()
128
129	blobStore, err := filesystem.NewBlobStorage(rootDir, log)
130	require.NoError(t, err)
131
132	manifestStore, err := filesystem.NewManifestStorage(rootDir, log)
133	require.NoError(t, err)
134
135	svc := registrysvc.NewService(blobStore, manifestStore, noopEventPublisher{})
136	handler := registryhttp.NewHandler(svc, log, maxBlobChunkSize)
137	server := httptest.NewServer(handler)
138
139	host := strings.TrimPrefix(server.URL, "http://")
140
141	return &registryHarness{
142		server: server,
143		ref:    host + "/testapp:v1.0.0",
144	}
145}
146
147func (h *registryHarness) Close() {
148	h.server.Close()
149}
150
151func (h *registryHarness) Push(ctx context.Context, img v1.Image, opts ...registrypush.Option) error {
152	allOpts := append([]registrypush.Option{
153		registrypush.WithTransport(h.server.Client().Transport),
154		registrypush.WithImageSource(func(context.Context, string) (v1.Image, error) {
155			return img, nil
156		}),
157	}, opts...)
158
159	pusher := registrypush.New(allOpts...)
160	return pusher.Push(ctx, h.ref)
161}
162
163func (h *registryHarness) GetManifest(t *testing.T) *http.Response {
164	t.Helper()
165
166	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/testapp/manifests/v1.0.0", h.server.URL), nil)
167	require.NoError(t, err)
168
169	resp, err := h.server.Client().Do(req)
170	require.NoError(t, err)
171
172	return resp
173}
174
175func (h *registryHarness) RequireManifestAndBlobs(ctx context.Context, t *testing.T, img v1.Image) {
176	t.Helper()
177
178	manifestResp := h.GetManifest(t)
179	require.Equal(t, http.StatusOK, manifestResp.StatusCode)
180	require.NoError(t, manifestResp.Body.Close())
181
182	layers, err := img.Layers()
183	require.NoError(t, err)
184
185	for _, layer := range layers {
186		digest, digestErr := layer.Digest()
187		require.NoError(t, digestErr)
188
189		req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v2/testapp/blobs/%s", h.server.URL, digest.String()), nil)
190		require.NoError(t, reqErr)
191
192		resp, respErr := h.server.Client().Do(req)
193		require.NoError(t, respErr)
194		assert.Equal(t, http.StatusOK, resp.StatusCode)
195		require.NoError(t, resp.Body.Close())
196	}
197}
198
199type noopEventPublisher struct{}
200
201func (noopEventPublisher) Publish(domain.EventType, any) error {
202	return nil
203}

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