v0.3.90 6f6753c

api_key_command_test.go RawHistoryFolder

1package auth
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"strings"
8	"testing"
9	"time"
10)
11
12func TestAPIKeyCommandHelperProcess(t *testing.T) {
13	if os.Getenv("ZOT_API_KEY_COMMAND_HELPER") != "1" {
14		return
15	}
16	args := os.Args
17	for len(args) > 0 && args[0] != "--" {
18		args = args[1:]
19	}
20	if len(args) < 2 {
21		os.Exit(2)
22	}
23	switch args[1] {
24	case "print":
25		fmt.Print(os.Getenv("ZOT_API_KEY_COMMAND_VALUE"))
26	case "fail":
27		fmt.Fprint(os.Stderr, os.Getenv("ZOT_API_KEY_COMMAND_VALUE"))
28		os.Exit(1)
29	case "sleep":
30		time.Sleep(5 * time.Second)
31	case "large":
32		fmt.Print(strings.Repeat("x", maxAPIKeyCommandOutput+1))
33	case "count":
34		if len(args) < 3 {
35			os.Exit(2)
36		}
37		f, err := os.OpenFile(args[2], os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
38		if err != nil {
39			os.Exit(3)
40		}
41		_, _ = f.WriteString("run\n")
42		_ = f.Close()
43		fmt.Print("cached-key\n")
44	default:
45		os.Exit(2)
46	}
47	os.Exit(0)
48}
49
50func apiKeyHelperCommand(mode string, extra ...string) APIKeyCommand {
51	args := []string{"-test.run=^TestAPIKeyCommandHelperProcess$", "--", mode}
52	args = append(args, extra...)
53	return APIKeyCommand{Program: os.Args[0], Args: args}
54}
55
56func resetAPIKeyCommandCache(t *testing.T) {
57	t.Helper()
58	apiKeyCommandCache.Lock()
59	oldValues := apiKeyCommandCache.values
60	oldInflight := apiKeyCommandCache.inflight
61	apiKeyCommandCache.values = make(map[[32]byte]string)
62	apiKeyCommandCache.inflight = make(map[[32]byte]*apiKeyCommandCall)
63	apiKeyCommandCache.Unlock()
64	t.Cleanup(func() {
65		apiKeyCommandCache.Lock()
66		apiKeyCommandCache.values = oldValues
67		apiKeyCommandCache.inflight = oldInflight
68		apiKeyCommandCache.Unlock()
69	})
70}
71
72func TestResolveAPIKeyCommand(t *testing.T) {
73	resetAPIKeyCommandCache(t)
74	t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
75	t.Setenv("ZOT_API_KEY_COMMAND_VALUE", "secret-value\r\n")
76
77	got, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("print"))
78	if err != nil {
79		t.Fatal(err)
80	}
81	if got != "secret-value" {
82		t.Fatalf("ResolveAPIKeyCommand() = %q, want %q", got, "secret-value")
83	}
84}
85
86func TestResolveAPIKeyCommandCachesSuccess(t *testing.T) {
87	resetAPIKeyCommandCache(t)
88	t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
89	countPath := t.TempDir() + string(os.PathSeparator) + "count"
90	command := apiKeyHelperCommand("count", countPath)
91
92	for range 2 {
93		got, err := ResolveAPIKeyCommand(context.Background(), command)
94		if err != nil {
95			t.Fatal(err)
96		}
97		if got != "cached-key" {
98			t.Fatalf("got %q", got)
99		}
100	}
101	contents, err := os.ReadFile(countPath)
102	if err != nil {
103		t.Fatal(err)
104	}
105	if got := strings.Count(string(contents), "run\n"); got != 1 {
106		t.Fatalf("command ran %d times, want 1", got)
107	}
108}
109
110func TestResolveAPIKeyCommandFailureDoesNotExposeOutput(t *testing.T) {
111	resetAPIKeyCommandCache(t)
112	t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
113	t.Setenv("ZOT_API_KEY_COMMAND_VALUE", "must-not-appear")
114
115	_, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("fail"))
116	if err == nil {
117		t.Fatal("expected an error")
118	}
119	if strings.Contains(err.Error(), "must-not-appear") {
120		t.Fatalf("error exposed command output: %v", err)
121	}
122}
123
124func TestResolveAPIKeyCommandTimeoutAndOutputLimit(t *testing.T) {
125	resetAPIKeyCommandCache(t)
126	t.Setenv("ZOT_API_KEY_COMMAND_HELPER", "1")
127
128	t.Run("timeout", func(t *testing.T) {
129		command := apiKeyHelperCommand("sleep")
130		command.TimeoutMS = 20
131		if _, err := ResolveAPIKeyCommand(context.Background(), command); err == nil || !strings.Contains(err.Error(), "timed out") {
132			t.Fatalf("error = %v, want timeout", err)
133		}
134	})
135	t.Run("output limit", func(t *testing.T) {
136		if _, err := ResolveAPIKeyCommand(context.Background(), apiKeyHelperCommand("large")); err == nil || !strings.Contains(err.Error(), "exceeds") {
137			t.Fatalf("error = %v, want output limit", err)
138		}
139	})
140}

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 1

Tags 476

↑↓ moveenter openesc close