-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselfhosted_zero_config_test.go
More file actions
368 lines (314 loc) · 10.2 KB
/
Copy pathselfhosted_zero_config_test.go
File metadata and controls
368 lines (314 loc) · 10.2 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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
// Copyright 2025 AxonFlow
// SPDX-License-Identifier: MIT
//
// selfhosted_zero_config_test.go - Tests for zero-configuration self-hosted mode
//
// These tests verify that the SDK works correctly when connecting to a
// self-hosted AxonFlow agent running in zero-config mode (no authentication).
//
// Run with:
// go test -v -tags=integration ./...
//
//go:build integration
// +build integration
package axonflow
import (
"os"
"strings"
"testing"
"time"
)
// getZeroConfigTestConfig returns configuration for zero-config self-hosted mode
func getZeroConfigTestConfig() AxonFlowConfig {
agentURL := os.Getenv("AXONFLOW_AGENT_URL")
if agentURL == "" {
agentURL = "http://localhost:8080"
}
return AxonFlowConfig{
Endpoint: agentURL,
ClientID: "default",
ClientSecret: "", // Empty - zero-config mode
Debug: true,
Timeout: 30 * time.Second,
}
}
// isLocalhost checks if the agent URL is localhost
func isLocalhostURL(url string) bool {
return strings.Contains(url, "localhost") || strings.Contains(url, "127.0.0.1")
}
// ============================================================
// 1. CLIENT INITIALIZATION WITHOUT CREDENTIALS
// ============================================================
// TestZeroConfig_ClientCreation_EmptySecret tests that client can be created
// with empty client_secret for localhost endpoints
func TestZeroConfig_ClientCreation_EmptySecret(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
// Should not panic or error
client := NewClient(config)
if client == nil {
t.Fatal("Expected client to be created")
}
t.Log("✅ Client created with empty secret for localhost")
}
// TestZeroConfig_ClientCreation_WhitespaceSecret tests that client can be created
// with whitespace-only client_secret for localhost endpoints
func TestZeroConfig_ClientCreation_WhitespaceSecret(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
config.ClientSecret = " " // Whitespace only
client := NewClient(config)
if client == nil {
t.Fatal("Expected client to be created")
}
t.Log("✅ Client created with whitespace secret for localhost")
}
// ============================================================
// 2. GATEWAY MODE WITHOUT AUTHENTICATION
// ============================================================
// TestZeroConfig_GatewayMode_PreCheckEmptyToken tests that pre-check works
// with empty user token in self-hosted mode
func TestZeroConfig_GatewayMode_PreCheckEmptyToken(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
// Pre-check with empty user token
result, err := client.GetPolicyApprovedContext(
"", // Empty token - zero-config scenario
"What is the weather in Paris?",
nil,
nil,
)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
if result.ContextID == "" {
t.Error("Expected non-empty context_id")
}
if result.ExpiresAt.IsZero() {
t.Error("Expected expires_at to be set")
}
t.Logf("✅ Pre-check succeeded with empty token: %s", result.ContextID)
}
// TestZeroConfig_GatewayMode_PreCheckWhitespaceToken tests that pre-check works
// with whitespace-only user token
func TestZeroConfig_GatewayMode_PreCheckWhitespaceToken(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
result, err := client.GetPolicyApprovedContext(
" ", // Whitespace only token
"Simple test query",
nil,
nil,
)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
if result.ContextID == "" {
t.Error("Expected non-empty context_id")
}
t.Log("✅ Pre-check succeeded with whitespace token")
}
// TestZeroConfig_GatewayMode_FullFlow tests the complete Gateway Mode flow
// without any credentials
func TestZeroConfig_GatewayMode_FullFlow(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
// Step 1: Pre-check
preCheck, err := client.GetPolicyApprovedContext(
"",
"Analyze quarterly sales data",
nil,
nil,
)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
if preCheck.ContextID == "" {
t.Fatal("Expected context_id from pre-check")
}
// Step 2: Audit (simulating LLM call completion)
audit, err := client.AuditLLMCall(
preCheck.ContextID,
"Generated sales analysis report",
"openai",
"gpt-4",
TokenUsage{PromptTokens: 100, CompletionTokens: 75, TotalTokens: 175},
350,
nil,
)
if err != nil {
t.Fatalf("AuditLLMCall failed: %v", err)
}
if !audit.Success {
t.Error("Expected audit to succeed")
}
if audit.AuditID == "" {
t.Error("Expected audit_id to be set")
}
t.Logf("✅ Full Gateway Mode flow completed: %s", audit.AuditID)
}
// ============================================================
// 3. PROXY MODE WITHOUT AUTHENTICATION
// ============================================================
// TestZeroConfig_ProxyMode_ProxyLLMCallEmptyToken tests that ProxyLLMCall works
// with empty user token in self-hosted mode
func TestZeroConfig_ProxyMode_ProxyLLMCallEmptyToken(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
resp, err := client.ProxyLLMCall(
"", // Empty token
"What is 2 + 2?",
"chat",
nil,
)
// Should either succeed or be blocked by policy (but not auth error)
if resp != nil {
if resp.Blocked {
t.Logf("⚠️ Query blocked by policy (not auth): %s", resp.BlockReason)
} else {
t.Log("✅ Query executed with empty token")
}
} else if err != nil {
// Check if it's an auth error (which should NOT happen)
if strings.Contains(err.Error(), "401") || strings.Contains(err.Error(), "unauthorized") {
t.Fatalf("Should not get auth error in zero-config mode: %v", err)
}
// Other errors might be acceptable (e.g., 403 for policy block)
t.Logf("Query error (may be policy block): %v", err)
}
}
// ============================================================
// 4. POLICY ENFORCEMENT STILL WORKS
// ============================================================
// TestZeroConfig_PolicyEnforcement_SQLInjection verifies SQL injection is still
// blocked even without authentication
func TestZeroConfig_PolicyEnforcement_SQLInjection(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
result, err := client.GetPolicyApprovedContext(
"",
"SELECT * FROM users WHERE id=1; DROP TABLE users;--",
nil,
nil,
)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
if result.Approved {
t.Error("SQL injection should be blocked")
}
if result.BlockReason == "" {
t.Error("Expected block_reason to be set")
}
t.Logf("✅ SQL injection blocked: %s", result.BlockReason)
}
// TestZeroConfig_PolicyEnforcement_PII verifies PII is detected
// even without authentication. Note: In community stack, PII may be in warn-only
// mode (detected but not blocked). We verify detection occurred.
func TestZeroConfig_PolicyEnforcement_PII(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
result, err := client.GetPolicyApprovedContext(
"",
"My social security number is 123-45-6789",
nil,
nil,
)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
// PII should either be blocked OR detected (warn mode)
// In community stack, PII may be in warn-only mode
if result.Approved {
// If approved, check if policies were at least evaluated (warn mode)
if len(result.Policies) > 0 {
t.Logf("✅ PII detected in warn mode (policies=%v)", result.Policies)
return
}
// No policies evaluated means PII detection is completely disabled
t.Skip("PII detection not enabled in community stack")
}
t.Log("✅ PII blocked without credentials")
}
// ============================================================
// 5. HEALTH CHECK WITHOUT AUTH
// ============================================================
// TestZeroConfig_HealthCheck tests that health check works without authentication
func TestZeroConfig_HealthCheck(t *testing.T) {
config := getZeroConfigTestConfig()
if !isLocalhostURL(config.Endpoint) {
t.Skip("Zero-config tests require localhost endpoint")
}
client := NewClient(config)
err := client.HealthCheck()
if err != nil {
t.Fatalf("Health check failed: %v", err)
}
t.Log("✅ Health check succeeded without credentials")
}
// ============================================================
// 6. FIRST-TIME USER EXPERIENCE
// ============================================================
// TestZeroConfig_FirstTimeUser simulates a brand new user with minimal configuration
func TestZeroConfig_FirstTimeUser(t *testing.T) {
agentURL := os.Getenv("AXONFLOW_AGENT_URL")
if agentURL == "" {
agentURL = "http://localhost:8080"
}
if !isLocalhostURL(agentURL) {
t.Skip("Zero-config tests require localhost endpoint")
}
// First-time user - minimal configuration
client := NewClient(AxonFlowConfig{
Endpoint: agentURL,
ClientID: "first-time-user",
ClientSecret: "", // Empty - zero-config
Debug: true,
Timeout: 30 * time.Second,
})
// Step 1: Health check should work
if err := client.HealthCheck(); err != nil {
t.Fatalf("Health check failed: %v", err)
}
// Step 2: Pre-check should work with empty token
result, err := client.GetPolicyApprovedContext(
"",
"Hello, this is my first query!",
nil,
nil,
)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
if result.ContextID == "" {
t.Error("Expected context_id")
}
t.Log("✅ First-time user experience validated")
t.Log(" - Client creation: OK")
t.Log(" - Health check: OK")
t.Log(" - Pre-check: OK")
}
// Note: Section 7 (Auth Headers) tests are in selfhosted_auth_headers_test.go
// They use httptest and run without the integration build tag