controlplane_resolver_test.go RawHistoryFolder

1package cli
2
3import (
4	"context"
5	"encoding/json"
6	"errors"
7	"net/http"
8	"os"
9	"path/filepath"
10	"testing"
11	"time"
12
13	"github.com/stretchr/testify/assert"
14	"github.com/stretchr/testify/require"
15
16	"github.com/bnema/gordon/internal/adapters/in/cli/remote"
17	"github.com/bnema/gordon/internal/adapters/localadmin"
18)
19
20func isolateControlPlaneResolver(t *testing.T) {
21	t.Helper()
22	t.Setenv("XDG_CONFIG_HOME", filepath.Join(t.TempDir(), "xdg"))
23	t.Setenv("GORDON_REMOTE", "")
24	t.Setenv("GORDON_TOKEN", "")
25	originalRemoteFlag, originalTokenFlag, originalInsecureTLSFlag := remoteFlag, tokenFlag, insecureTLSFlag
26	originalFactory := newLocalControlPlaneClient
27	t.Cleanup(func() {
28		remoteFlag = originalRemoteFlag
29		tokenFlag = originalTokenFlag
30		insecureTLSFlag = originalInsecureTLSFlag
31		newLocalControlPlaneClient = originalFactory
32	})
33	remoteFlag = ""
34	tokenFlag = ""
35	insecureTLSFlag = false
36}
37
38func localControlPlaneTestClient(t *testing.T, handler http.Handler) *remote.Client {
39	t.Helper()
40	dir := t.TempDir()
41	listener, _, err := localadmin.Listen(dir)
42	require.NoError(t, err)
43	server := &http.Server{Handler: handler, ReadHeaderTimeout: 5 * time.Second}
44	go func() { _ = server.Serve(listener) }()
45	t.Cleanup(func() { _ = server.Close() })
46	return remote.NewLocalClientForSocket(localadmin.SocketPath(dir))
47}
48
49func TestResolveControlPlane_LocalUsesSocketBackedRemotePlane(t *testing.T) {
50	isolateControlPlaneResolver(t)
51	requests := make(chan string, 1)
52	client := localControlPlaneTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
53		requests <- r.URL.Path
54		w.Header().Set("Content-Type", "application/json")
55		_ = json.NewEncoder(w).Encode(remote.Status{RegistryDomain: "socket.local"})
56	}))
57	factoryCalls := 0
58	newLocalControlPlaneClient = func() (*remote.Client, error) {
59		factoryCalls++
60		return client, nil
61	}
62
63	// A deliberately unusable config path proves local resolution does not open a kernel/store.
64	handle, err := resolveControlPlane(filepath.Join(t.TempDir(), "missing", "gordon.toml"))
65	require.NoError(t, err)
66	require.NotNil(t, handle)
67	assert.False(t, handle.isRemote)
68	assert.IsType(t, &remote.Client{}, handle.plane)
69	assert.Equal(t, 1, factoryCalls)
70
71	status, err := handle.plane.GetStatus(context.Background())
72	require.NoError(t, err)
73	assert.Equal(t, "socket.local", status.RegistryDomain)
74	select {
75	case path := <-requests:
76		assert.Equal(t, "/admin/status", path)
77	case <-time.After(2 * time.Second):
78		t.Fatal("socket server received no request")
79	}
80}
81
82func TestResolveControlPlane_LocalFailsClosedWhenDaemonUnavailable(t *testing.T) {
83	isolateControlPlaneResolver(t)
84	newLocalControlPlaneClient = func() (*remote.Client, error) {
85		return nil, remote.ErrDaemonUnavailable
86	}
87
88	handle, err := resolveControlPlane(filepath.Join(t.TempDir(), "would-create-store.toml"))
89	require.Error(t, err)
90	assert.ErrorIs(t, err, remote.ErrDaemonUnavailable)
91	assert.Nil(t, handle)
92}
93
94func TestResolveControlPlane_ExplicitRemoteDoesNotUseLocalFactory(t *testing.T) {
95	isolateControlPlaneResolver(t)
96	remoteFlag = "https://example.invalid"
97	tokenFlag = "token"
98	newLocalControlPlaneClient = func() (*remote.Client, error) {
99		t.Fatal("local factory invoked for explicit remote")
100		return nil, errors.New("unreachable")
101	}
102
103	handle, err := resolveControlPlane("")
104	require.NoError(t, err)
105	assert.True(t, handle.isRemote)
106	assert.IsType(t, &remote.Client{}, handle.plane)
107}
108
109func TestResolveControlPlane_ExplicitUnknownRemoteReturnsError(t *testing.T) {
110	isolateControlPlaneResolver(t)
111	t.Setenv("XDG_CONFIG_HOME", filepath.Join(t.TempDir(), "xdg"))
112	remoteFlag = "does-not-exist"
113
114	handle, err := resolveControlPlane("")
115	require.Error(t, err)
116	assert.Nil(t, handle)
117	assert.Contains(t, err.Error(), "does-not-exist")
118}
119
120func TestResolveControlPlane_LocalIgnoresConfigAndStoreState(t *testing.T) {
121	isolateControlPlaneResolver(t)
122	client := localControlPlaneTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
123		w.Header().Set("Content-Type", "application/json")
124		_ = json.NewEncoder(w).Encode(remote.Status{})
125	}))
126	newLocalControlPlaneClient = func() (*remote.Client, error) { return client, nil }
127
128	lockedStore := filepath.Join(t.TempDir(), "state.db")
129	require.NoError(t, os.WriteFile(lockedStore, []byte("active daemon store"), 0o600))
130	handle, err := resolveControlPlane(lockedStore)
131	require.NoError(t, err)
132	_, err = handle.plane.GetStatus(context.Background())
133	require.NoError(t, err)
134}

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