@@ -141,6 +141,7 @@ func TestServerCmd_AllArgs_Defaults(t *testing.T) {
141
141
{"chat-base-path default" , FlagChatBasePath , "/chat" , func () any { return viper .GetString (FlagChatBasePath ) }},
142
142
{"term-width default" , FlagTermWidth , uint16 (80 ), func () any { return viper .GetUint16 (FlagTermWidth ) }},
143
143
{"term-height default" , FlagTermHeight , uint16 (1000 ), func () any { return viper .GetUint16 (FlagTermHeight ) }},
144
+ {"allowed-hosts default" , FlagAllowedHosts , []string {"localhost:3284" }, func () any { return viper .GetStringSlice (FlagAllowedHosts ) }},
144
145
}
145
146
146
147
for _ , tt := range tests {
@@ -175,6 +176,7 @@ func TestServerCmd_AllEnvVars(t *testing.T) {
175
176
{"AGENTAPI_CHAT_BASE_PATH" , "AGENTAPI_CHAT_BASE_PATH" , "/api" , "/api" , func () any { return viper .GetString (FlagChatBasePath ) }},
176
177
{"AGENTAPI_TERM_WIDTH" , "AGENTAPI_TERM_WIDTH" , "120" , uint16 (120 ), func () any { return viper .GetUint16 (FlagTermWidth ) }},
177
178
{"AGENTAPI_TERM_HEIGHT" , "AGENTAPI_TERM_HEIGHT" , "500" , uint16 (500 ), func () any { return viper .GetUint16 (FlagTermHeight ) }},
179
+ {"AGENTAPI_ALLOWED_HOSTS" , "AGENTAPI_ALLOWED_HOSTS" , "localhost:3284 localhost:3285" , []string {"localhost:3284" , "localhost:3285" }, func () any { return viper .GetStringSlice (FlagAllowedHosts ) }},
178
180
}
179
181
180
182
for _ , tt := range tests {
@@ -291,3 +293,119 @@ func TestMixed_ConfigurationScenarios(t *testing.T) {
291
293
assert .Equal (t , uint16 (1000 ), viper .GetUint16 (FlagTermHeight )) // default
292
294
})
293
295
}
296
+
297
+ func TestServerCmd_AllowedHosts (t * testing.T ) {
298
+ tests := []struct {
299
+ name string
300
+ env map [string ]string
301
+ args []string
302
+ expectedErr string
303
+ expected []string // only checked if expectedErr is empty
304
+ }{
305
+ // Environment variable scenarios (space-separated format)
306
+ {
307
+ name : "env: single valid host" ,
308
+ env : map [string ]string {"AGENTAPI_ALLOWED_HOSTS" : "localhost:3284" },
309
+ args : []string {},
310
+ expected : []string {"localhost:3284" },
311
+ },
312
+ {
313
+ name : "env: multiple valid hosts space-separated" ,
314
+ env : map [string ]string {"AGENTAPI_ALLOWED_HOSTS" : "localhost:3284 example.com 192.168.1.1:8080" },
315
+ args : []string {},
316
+ expected : []string {"localhost:3284" , "example.com" , "192.168.1.1:8080" },
317
+ },
318
+ {
319
+ name : "env: host with tab" ,
320
+ env : map [string ]string {"AGENTAPI_ALLOWED_HOSTS" : "localhost:3284\t example.com" },
321
+ args : []string {},
322
+ expected : []string {"localhost:3284" , "example.com" },
323
+ },
324
+ {
325
+ name : "env: host with comma (invalid)" ,
326
+ env : map [string ]string {"AGENTAPI_ALLOWED_HOSTS" : "localhost:3284,example.com" },
327
+ args : []string {},
328
+ expectedErr : "contains comma characters" ,
329
+ },
330
+
331
+ // CLI flag scenarios (comma-separated format)
332
+ {
333
+ name : "flag: single valid host" ,
334
+ args : []string {"--allowed-hosts" , "localhost:3284" },
335
+ expected : []string {"localhost:3284" },
336
+ },
337
+ {
338
+ name : "flag: multiple valid hosts comma-separated" ,
339
+ args : []string {"--allowed-hosts" , "localhost:3284,example.com,192.168.1.1:8080" },
340
+ expected : []string {"localhost:3284" , "example.com" , "192.168.1.1:8080" },
341
+ },
342
+ {
343
+ name : "flag: multiple valid hosts with multiple flags" ,
344
+ args : []string {"--allowed-hosts" , "localhost:3284" , "--allowed-hosts" , "example.com" },
345
+ expected : []string {"localhost:3284" , "example.com" },
346
+ },
347
+ {
348
+ name : "flag: host with newline" ,
349
+ args : []string {"--allowed-hosts" , "localhost:3284\n " },
350
+ expected : []string {"localhost:3284" },
351
+ },
352
+ {
353
+ name : "flag: host with space in comma-separated list (invalid)" ,
354
+ args : []string {"--allowed-hosts" , "localhost:3284,example .com" },
355
+ expectedErr : "contains whitespace characters" ,
356
+ },
357
+
358
+ // Mixed scenarios (env + flag precedence)
359
+ {
360
+ name : "mixed: flag overrides env" ,
361
+ env : map [string ]string {"AGENTAPI_ALLOWED_HOSTS" : "localhost:8080" },
362
+ args : []string {"--allowed-hosts" , "override.com" },
363
+ expected : []string {"override.com" },
364
+ },
365
+ {
366
+ name : "mixed: flag overrides env but flag is invalid" ,
367
+ env : map [string ]string {"AGENTAPI_ALLOWED_HOSTS" : "localhost:8080" },
368
+ args : []string {"--allowed-hosts" , "invalid .com" },
369
+ expectedErr : "contains whitespace characters" ,
370
+ },
371
+
372
+ // Empty hosts are not allowed
373
+ {
374
+ name : "empty host" ,
375
+ args : []string {"--allowed-hosts" , "" },
376
+ expectedErr : "allowed hosts must not be empty" ,
377
+ },
378
+
379
+ // Default behavior
380
+ {
381
+ name : "default hosts when neither env nor flag provided" ,
382
+ args : []string {},
383
+ expected : []string {"localhost:3284" , "localhost:3000" , "localhost:3001" },
384
+ },
385
+ }
386
+
387
+ for _ , tt := range tests {
388
+ t .Run (tt .name , func (t * testing.T ) {
389
+ isolateViper (t )
390
+
391
+ // Set environment variables if provided
392
+ for key , value := range tt .env {
393
+ t .Setenv (key , value )
394
+ }
395
+
396
+ serverCmd := CreateServerCmd ()
397
+ // --print-openapi acts as an agent command that immediately exits
398
+ // use a 0 port to pick a random free port
399
+ serverCmd .SetArgs (append (tt .args , "--port" , "0" , "--" , "sh" , "-c" , "echo ok" ))
400
+ err := serverCmd .Execute ()
401
+
402
+ if tt .expectedErr != "" {
403
+ require .Error (t , err )
404
+ assert .Contains (t , err .Error (), tt .expectedErr )
405
+ } else {
406
+ require .NoError (t , err )
407
+ assert .Equal (t , tt .expected , viper .GetStringSlice (FlagAllowedHosts ))
408
+ }
409
+ })
410
+ }
411
+ }
0 commit comments