2.0.0 4803e67

start_test.go RawHistoryFolder

1package cmd
2
3import (
4	"bytes"
5	"context"
6	"fmt"
7	"os"
8	"path/filepath"
9	"syscall"
10	"testing"
11	"time"
12
13	"github.com/spf13/cobra"
14	"github.com/spf13/viper"
15	"github.com/stretchr/testify/assert"
16	"github.com/stretchr/testify/require"
17)
18
19func TestStartCommand(t *testing.T) {
20	// Test start command structure
21	assert.Equal(t, "start", startCmd.Use)
22	assert.Contains(t, startCmd.Short, "Start")
23	assert.Contains(t, startCmd.Long, "registry")
24}
25
26func TestRunStart_ConfigError(t *testing.T) {
27	// Save original values
28	originalCfgFile := cfgFile
29	defer func() {
30		cfgFile = originalCfgFile
31		viper.Reset()
32	}()
33
34	// Set non-existent config file
35	cfgFile = "/nonexistent/config.toml"
36	
37	// Capture stderr to check config error message
38	var stderr bytes.Buffer
39	originalStderr := os.Stderr
40	r, w, _ := os.Pipe()
41	os.Stderr = w
42
43	// Initialize config to trigger error handling
44	done := make(chan bool)
45	go func() {
46		defer func() {
47			if recover() != nil {
48				// Recovery from log.Fatal is expected in some cases
49			}
50			done <- true
51		}()
52		
53		// Call initConfig directly to test config error handling
54		initConfig()
55	}()
56
57	// Give it a moment to process
58	select {
59	case <-done:
60		// Expected
61	case <-time.After(1 * time.Second):
62		t.Fatal("initConfig should have completed quickly")
63	}
64
65	w.Close()
66	os.Stderr = originalStderr
67	stderr.ReadFrom(r)
68
69	// Check that an error message was printed
70	stderrContent := stderr.String()
71	assert.Contains(t, stderrContent, "Error reading config file")
72}
73
74func TestCreatePidFile(t *testing.T) {
75	// Test PID file creation
76	pidFile := createPidFile()
77	
78	if pidFile != "" {
79		// Verify file exists and contains valid PID
80		assert.FileExists(t, pidFile)
81		
82		content, err := os.ReadFile(pidFile)
83		require.NoError(t, err)
84		
85		var pid int
86		_, err = fmt.Sscanf(string(content), "%d", &pid)
87		assert.NoError(t, err)
88		assert.Equal(t, os.Getpid(), pid)
89		
90		// Clean up
91		removePidFile(pidFile)
92		assert.NoFileExists(t, pidFile)
93	}
94	
95	// Test should pass even if no writable location is found
96	// (function returns empty string in that case)
97}
98
99func TestRemovePidFile(t *testing.T) {
100	tests := []struct {
101		name        string
102		setupFunc   func(t *testing.T) string
103		expectError bool
104	}{
105		{
106			name: "remove existing file",
107			setupFunc: func(t *testing.T) string {
108				tempDir := t.TempDir()
109				pidFile := filepath.Join(tempDir, "test.pid")
110				err := os.WriteFile(pidFile, []byte("12345"), 0644)
111				require.NoError(t, err)
112				return pidFile
113			},
114			expectError: false,
115		},
116		{
117			name: "remove non-existent file",
118			setupFunc: func(t *testing.T) string {
119				tempDir := t.TempDir()
120				return filepath.Join(tempDir, "nonexistent.pid")
121			},
122			expectError: true, // Warning logged, but no panic
123		},
124	}
125
126	for _, tt := range tests {
127		t.Run(tt.name, func(t *testing.T) {
128			pidFile := tt.setupFunc(t)
129			
130			// Should not panic regardless of whether file exists
131			assert.NotPanics(t, func() {
132				removePidFile(pidFile)
133			})
134			
135			// File should not exist after removal attempt
136			assert.NoFileExists(t, pidFile)
137		})
138	}
139}
140
141func TestCreatePidFile_Locations(t *testing.T) {
142	// Test that createPidFile tries multiple locations
143	// We can't easily mock os.WriteFile to test all failure scenarios,
144	// but we can verify the function doesn't panic
145	assert.NotPanics(t, func() {
146		pidFile := createPidFile()
147		if pidFile != "" {
148			removePidFile(pidFile)
149		}
150	})
151}
152
153func TestCreatePidFile_Content(t *testing.T) {
154	pidFile := createPidFile()
155	if pidFile == "" {
156		t.Skip("No writable location found for PID file")
157	}
158	
159	defer removePidFile(pidFile)
160	
161	// Verify PID file contains current process ID
162	content, err := os.ReadFile(pidFile)
163	require.NoError(t, err)
164	
165	var storedPid int
166	_, err = fmt.Sscanf(string(content), "%d", &storedPid)
167	require.NoError(t, err)
168	
169	assert.Equal(t, os.Getpid(), storedPid)
170}
171
172func TestPidFilePermissions(t *testing.T) {
173	pidFile := createPidFile()
174	if pidFile == "" {
175		t.Skip("No writable location found for PID file")
176	}
177	
178	defer removePidFile(pidFile)
179	
180	// Check file permissions
181	info, err := os.Stat(pidFile)
182	require.NoError(t, err)
183	
184	// Should be readable by owner and group
185	assert.True(t, info.Mode().Perm()&0644 != 0)
186}
187
188func TestPidFileLocations(t *testing.T) {
189	// Test different potential PID file locations
190	// This mainly tests that the function handles various scenarios
191	
192	originalHome := os.Getenv("HOME")
193	defer os.Setenv("HOME", originalHome)
194	
195	// Test with invalid home directory
196	os.Setenv("HOME", "/nonexistent/invalid/path")
197	
198	assert.NotPanics(t, func() {
199		pidFile := createPidFile()
200		if pidFile != "" {
201			removePidFile(pidFile)
202		}
203	})
204}
205
206func TestStartCmdStructure(t *testing.T) {
207	// Verify command is properly registered
208	found := false
209	for _, cmd := range rootCmd.Commands() {
210		if cmd.Name() == "start" {
211			found = true
212			break
213		}
214	}
215	assert.True(t, found, "start command should be registered with root command")
216}
217
218func TestStartCmdHelp(t *testing.T) {
219	var output bytes.Buffer
220	
221	// Create a copy of the command to avoid affecting the global command
222	cmd := &cobra.Command{
223		Use:   startCmd.Use,
224		Short: startCmd.Short,
225		Long:  startCmd.Long,
226		Run:   startCmd.Run,
227	}
228	
229	cmd.SetOut(&output)
230	cmd.SetErr(&output)
231	cmd.SetArgs([]string{"--help"})
232	
233	err := cmd.Execute()
234	assert.NoError(t, err)
235	
236	helpOutput := output.String()
237	assert.Contains(t, helpOutput, "Start")
238	assert.Contains(t, helpOutput, "container registry")
239	assert.Contains(t, helpOutput, "reverse proxy")
240	assert.Contains(t, helpOutput, "server")
241}
242
243// Test helper functions for signal handling scenarios
244func TestStartCommand_SignalHandling(t *testing.T) {
245	// This is a simplified test since we can't easily test the full signal handling
246	// without actually starting the server. We mainly test that the command structure is correct.
247	
248	// Verify the command can be created and has the right properties
249	assert.NotNil(t, startCmd.Run)
250	assert.Equal(t, "start", startCmd.Use)
251}
252
253func TestRunStart_ValidConfig(t *testing.T) {
254	// Create a valid config file
255	tempDir := t.TempDir()
256	configFile := filepath.Join(tempDir, "gordon.toml")
257	
258	configContent := `
259[server]
260port = 8080
261registry_port = 5000
262runtime = "docker"
263
264[logging]
265enabled = false
266dir = "` + filepath.Join(tempDir, "logs") + `"
267
268[registry]
269enabled = false
270
271[routes]
272`
273	
274	err := os.WriteFile(configFile, []byte(configContent), 0644)
275	require.NoError(t, err)
276
277	// Save original values
278	originalCfgFile := cfgFile
279	defer func() {
280		cfgFile = originalCfgFile
281		viper.Reset()
282	}()
283
284	// Set config file
285	cfgFile = configFile
286	viper.Reset()
287
288	// We can't actually run the full start command without Docker,
289	// but we can test that it doesn't immediately fail with config errors
290	// by mocking the execution
291	
292	// This is a limited test since runStart calls log.Fatal on various errors
293	// and we can't easily test the full server startup without actual dependencies
294	assert.NotNil(t, startCmd.Run, "Start command should have a run function")
295}
296
297func TestSignalHandling_Concepts(t *testing.T) {
298	// Test the concepts used in signal handling
299	
300	// Test that we can create signal channels (used in runStart)
301	sigChan := make(chan os.Signal, 1)
302	reloadChan := make(chan os.Signal, 1)
303	
304	assert.NotNil(t, sigChan)
305	assert.NotNil(t, reloadChan)
306	
307	// Test signal types that would be used
308	signals := []os.Signal{
309		syscall.SIGINT,
310		syscall.SIGTERM,
311		syscall.SIGUSR1,
312	}
313	
314	for _, sig := range signals {
315		assert.NotNil(t, sig)
316	}
317}
318
319func TestContextCancellation(t *testing.T) {
320	// Test context cancellation patterns used in runStart
321	ctx, cancel := context.WithCancel(context.Background())
322	assert.NotNil(t, ctx)
323	assert.NotNil(t, cancel)
324	
325	// Test timeout context
326	timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 30*time.Second)
327	assert.NotNil(t, timeoutCtx)
328	assert.NotNil(t, timeoutCancel)
329	
330	// Clean up
331	cancel()
332	timeoutCancel()
333}

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