v3.4.1 de8f68e

push_registry_target_test.go RawHistoryFolder

1package cli
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"io"
8	"net/http"
9	"testing"
10
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/mock"
13	"github.com/stretchr/testify/require"
14
15	"github.com/bnema/gordon/internal/adapters/dto"
16	climocks "github.com/bnema/gordon/internal/adapters/in/cli/mocks"
17	"github.com/bnema/gordon/internal/adapters/in/cli/remote"
18)
19
20// remoteWithRegistry returns a remote handle whose /auth/verify reports registryDomain.
21func remoteWithRegistry(t *testing.T, registryDomain string) *controlPlaneHandle {
22	t.Helper()
23	plane := climocks.NewMockControlPlane(t)
24	plane.EXPECT().VerifyAuth(mock.Anything).Return(&dto.AuthVerifyResponse{Valid: true, RegistryDomain: registryDomain}, nil).Once()
25	return &controlPlaneHandle{plane: plane, isRemote: true}
26}
27
28func TestResolveImageTarget_BareNameUsesTargetRegistry(t *testing.T) {
29	tests := []struct {
30		name       string
31		arg        string
32		wantName   string
33		wantSource string
34	}{
35		{"bare name", "myapp", "myapp", "myapp"},
36		{"bare name with tag", "myapp:v1.2.3", "myapp", "myapp:v1.2.3"},
37		{"namespaced name", "team/myapp:v1", "team/myapp", "team/myapp:v1"},
38		{"bare digest", "myapp@sha256:deadbeef", "myapp", "myapp@sha256:deadbeef"},
39	}
40	for _, tt := range tests {
41		t.Run(tt.name, func(t *testing.T) {
42			var out bytes.Buffer
43			registry, name, source, err := resolveImageTarget(context.Background(), &out, remoteWithRegistry(t, "reg.example.com"), tt.arg, "Dockerfile")
44			require.NoError(t, err)
45			assert.Equal(t, "reg.example.com", registry)
46			assert.Equal(t, tt.wantName, name)
47			assert.Equal(t, tt.wantSource, source)
48			assert.Contains(t, out.String(), "reg.example.com")
49		})
50	}
51}
52
53func TestResolveImageTarget_QualifiedRefMatchingTarget(t *testing.T) {
54	registry, name, source, err := resolveImageTarget(context.Background(), io.Discard, remoteWithRegistry(t, "reg.example.com."), "Reg.Example.com/myapp:v1", "Dockerfile")
55	require.NoError(t, err)
56	assert.Equal(t, "Reg.Example.com", registry)
57	assert.Equal(t, "myapp", name)
58	assert.Equal(t, "Reg.Example.com/myapp:v1", source)
59}
60
61func TestResolveImageTarget_RejectsMismatchedRegistry(t *testing.T) {
62	for _, ref := range []string{"other.example.org/myapp:v1", "reg.example.com:443/myapp:v1"} {
63		t.Run(ref, func(t *testing.T) {
64			_, _, _, err := resolveImageTarget(context.Background(), io.Discard, remoteWithRegistry(t, "reg.example.com"), ref, "Dockerfile")
65			require.Error(t, err)
66			assert.Contains(t, err.Error(), "reg.example.com/myapp")
67		})
68	}
69}
70
71func TestTargetRegistryDomain_RemoteFallsBackToStatusForOlderServers(t *testing.T) {
72	plane := climocks.NewMockControlPlane(t)
73	plane.EXPECT().VerifyAuth(mock.Anything).Return(&dto.AuthVerifyResponse{Valid: true}, nil).Once()
74	plane.EXPECT().GetStatus(mock.Anything).Return(&remote.Status{RegistryDomain: "reg.example.com"}, nil).Once()
75
76	got, err := targetRegistryDomain(context.Background(), &controlPlaneHandle{plane: plane, isRemote: true})
77	require.NoError(t, err)
78	assert.Equal(t, "reg.example.com", got)
79}
80
81func TestTargetRegistryDomain_LocalSocketUsesStatus(t *testing.T) {
82	plane := climocks.NewMockControlPlane(t)
83	plane.EXPECT().GetStatus(mock.Anything).Return(&remote.Status{RegistryDomain: "reg.example.com"}, nil).Once()
84
85	got, err := targetRegistryDomain(context.Background(), &controlPlaneHandle{plane: plane})
86	require.NoError(t, err)
87	assert.Equal(t, "reg.example.com", got)
88}
89
90func TestTargetRegistryDomain_Errors(t *testing.T) {
91	tests := []struct {
92		name    string
93		setup   func(*climocks.MockControlPlane)
94		wantErr string
95	}{
96		{
97			name: "verify fails",
98			setup: func(p *climocks.MockControlPlane) {
99				p.EXPECT().VerifyAuth(mock.Anything).Return(nil, errors.New("boom")).Once()
100			},
101			wantErr: "boom",
102		},
103		{
104			name: "token without admin scope",
105			setup: func(p *climocks.MockControlPlane) {
106				p.EXPECT().VerifyAuth(mock.Anything).Return(nil, &remote.HTTPError{StatusCode: http.StatusForbidden, Status: "403 Forbidden"}).Once()
107			},
108			wantErr: "admin scope",
109		},
110		{
111			name: "token invalid",
112			setup: func(p *climocks.MockControlPlane) {
113				p.EXPECT().VerifyAuth(mock.Anything).Return(&dto.AuthVerifyResponse{Valid: false}, nil).Once()
114			},
115			wantErr: "token is not valid",
116		},
117		{
118			name: "older server denies status",
119			setup: func(p *climocks.MockControlPlane) {
120				p.EXPECT().VerifyAuth(mock.Anything).Return(&dto.AuthVerifyResponse{Valid: true}, nil).Once()
121				p.EXPECT().GetStatus(mock.Anything).Return(nil, &remote.HTTPError{StatusCode: http.StatusForbidden, Status: "403 Forbidden"}).Once()
122			},
123			wantErr: "admin:status:read",
124		},
125		{
126			name: "no registry domain configured",
127			setup: func(p *climocks.MockControlPlane) {
128				p.EXPECT().VerifyAuth(mock.Anything).Return(&dto.AuthVerifyResponse{Valid: true}, nil).Once()
129				p.EXPECT().GetStatus(mock.Anything).Return(&remote.Status{}, nil).Once()
130			},
131			wantErr: "registry_domain",
132		},
133	}
134	for _, tt := range tests {
135		t.Run(tt.name, func(t *testing.T) {
136			plane := climocks.NewMockControlPlane(t)
137			tt.setup(plane)
138			_, err := targetRegistryDomain(context.Background(), &controlPlaneHandle{plane: plane, isRemote: true})
139			require.ErrorContains(t, err, tt.wantErr)
140		})
141	}
142}

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