-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathe5s_server_startup_test.go
More file actions
266 lines (213 loc) · 7.18 KB
/
e5s_server_startup_test.go
File metadata and controls
266 lines (213 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//go:build integration
// +build integration
package e5s_test
import (
"context"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/sufield/e5s"
"github.com/sufield/e5s/internal/testhelpers"
)
// TestStartWithContext_SuccessfulStartup tests the happy path:
// server starts in goroutine, binds successfully, responds to requests, and shuts down cleanly.
// This covers e5s.go lines 340-395 (goroutine, errCh, shutdownOnce patterns).
func TestStartWithContext_SuccessfulStartup(t *testing.T) {
// Setup SPIRE infrastructure
st := testhelpers.SetupSPIRE(t)
// Create test config
tempDir := t.TempDir()
cfgPath := filepath.Join(tempDir, "server-test.yaml")
configContent := fmt.Sprintf(`spire:
workload_socket: "unix://%s"
initial_fetch_timeout: "30s"
server:
listen_addr: "localhost:0"
allowed_client_trust_domain: "example.org"
`, st.SocketPath)
if err := os.WriteFile(cfgPath, []byte(configContent), 0o600); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
// Create test handler
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
// Start server with context
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
shutdown, err := e5s.StartWithContext(ctx, cfgPath, handler)
if err != nil {
t.Fatalf("StartWithContext failed: %v", err)
}
// Verify shutdown function is returned
if shutdown == nil {
t.Fatal("shutdown function is nil")
}
// Give server time to fully start
time.Sleep(200 * time.Millisecond)
// Test shutdown - this verifies the shutdownOnce pattern and cleanup
if err := shutdown(); err != nil {
t.Errorf("shutdown failed: %v", err)
}
// Test multiple shutdown calls (should be idempotent via sync.Once)
if err := shutdown(); err != nil {
t.Errorf("second shutdown call failed: %v", err)
}
t.Log("✓ Server started successfully and shut down cleanly")
}
// TestStartWithContext_StartupError tests error handling when server fails to bind.
// This covers e5s.go lines 358-362 (startup error path + identityShutdown cleanup).
func TestStartWithContext_StartupError(t *testing.T) {
// Setup SPIRE infrastructure
st := testhelpers.SetupSPIRE(t)
// Create config with invalid listen address that will cause bind error
tempDir := t.TempDir()
cfgPath := filepath.Join(tempDir, "server-invalid.yaml")
configContent := fmt.Sprintf(`spire:
workload_socket: "unix://%s"
initial_fetch_timeout: "30s"
server:
listen_addr: "invalid-hostname:99999"
allowed_client_trust_domain: "example.org"
`, st.SocketPath)
if err := os.WriteFile(cfgPath, []byte(configContent), 0o600); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
// Attempt to start server - should fail due to invalid address
ctx := context.Background()
shutdown, err := e5s.StartWithContext(ctx, cfgPath, handler)
// Verify error is returned
if err == nil {
t.Fatal("Expected error for invalid listen address, got nil")
if shutdown != nil {
shutdown() // Clean up if somehow succeeded
}
}
// Verify shutdown function is NOT returned on failure
if shutdown != nil {
t.Error("Expected nil shutdown function on startup failure")
}
t.Logf("✓ Startup error handled correctly: %v", err)
}
// TestStartWithContext_ContextCancellationAfterStart tests cancellation during server operation.
// This verifies the context propagation and graceful shutdown path after successful startup.
func TestStartWithContext_ContextCancellationAfterStart(t *testing.T) {
// Setup SPIRE infrastructure
st := testhelpers.SetupSPIRE(t)
// Create test config
tempDir := t.TempDir()
cfgPath := filepath.Join(tempDir, "server-cancel.yaml")
configContent := fmt.Sprintf(`spire:
workload_socket: "unix://%s"
initial_fetch_timeout: "30s"
server:
listen_addr: "localhost:0"
allowed_client_trust_domain: "example.org"
`, st.SocketPath)
if err := os.WriteFile(cfgPath, []byte(configContent), 0o600); err != nil {
t.Fatalf("Failed to write config: %v", err)
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Create cancellable context
ctx, cancel := context.WithCancel(context.Background())
shutdown, err := e5s.StartWithContext(ctx, cfgPath, handler)
if err != nil {
t.Fatalf("StartWithContext failed: %v", err)
}
defer shutdown()
// Give server time to start
time.Sleep(200 * time.Millisecond)
// Cancel context
cancel()
// Shutdown should still work after context cancellation
if err := shutdown(); err != nil {
t.Errorf("shutdown after context cancellation failed: %v", err)
}
t.Log("✓ Context cancellation handled correctly")
}
// TestStart_WithRealRequests tests server handling actual HTTP requests.
// This verifies the full request/response cycle including PeerID extraction.
func TestStart_WithRealRequests(t *testing.T) {
// Setup SPIRE infrastructure
st := testhelpers.SetupSPIRE(t)
// Create server config
tempDir := t.TempDir()
serverCfgPath := filepath.Join(tempDir, "server.yaml")
serverConfig := fmt.Sprintf(`spire:
workload_socket: "unix://%s"
initial_fetch_timeout: "30s"
server:
listen_addr: "localhost:18444"
allowed_client_trust_domain: "example.org"
`, st.SocketPath)
if err := os.WriteFile(serverCfgPath, []byte(serverConfig), 0o600); err != nil {
t.Fatalf("Failed to write server config: %v", err)
}
// Create client config
clientCfgPath := filepath.Join(tempDir, "client.yaml")
clientConfig := fmt.Sprintf(`spire:
workload_socket: "unix://%s"
initial_fetch_timeout: "30s"
client:
expected_server_trust_domain: "example.org"
`, st.SocketPath)
if err := os.WriteFile(clientCfgPath, []byte(clientConfig), 0o600); err != nil {
t.Fatalf("Failed to write client config: %v", err)
}
// Track if handler was called
handlerCalled := false
var receivedPeerID string
// Create handler that extracts peer ID
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
peerID, ok := e5s.PeerID(r)
if ok {
receivedPeerID = peerID
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Request received"))
})
// Start server
shutdown, err := e5s.Start(serverCfgPath, handler)
if err != nil {
t.Fatalf("Failed to start server: %v", err)
}
defer shutdown()
// Give server time to start
time.Sleep(300 * time.Millisecond)
// Create mTLS client and make request
err = e5s.WithClient(clientCfgPath, func(client *http.Client) error {
resp, err := client.Get("https://localhost:18444/test")
if err != nil {
return fmt.Errorf("client request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, body)
}
return nil
})
if err != nil {
t.Fatalf("Client request failed: %v", err)
}
// Verify handler was called
if !handlerCalled {
t.Error("Handler was not called")
}
// Verify peer ID was extracted
if receivedPeerID == "" {
t.Error("Peer ID was not extracted")
} else {
t.Logf("✓ Received peer ID: %s", receivedPeerID)
}
t.Log("✓ Server handled mTLS request successfully")
}