@@ -141,6 +141,7 @@ func TestServerCmd_AllArgs_Defaults(t *testing.T) {
141141 {"chat-base-path default" , FlagChatBasePath , "/chat" , func () any { return viper .GetString (FlagChatBasePath ) }},
142142 {"term-width default" , FlagTermWidth , uint16 (80 ), func () any { return viper .GetUint16 (FlagTermWidth ) }},
143143 {"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 ) }},
144145 }
145146
146147 for _ , tt := range tests {
@@ -175,6 +176,7 @@ func TestServerCmd_AllEnvVars(t *testing.T) {
175176 {"AGENTAPI_CHAT_BASE_PATH" , "AGENTAPI_CHAT_BASE_PATH" , "/api" , "/api" , func () any { return viper .GetString (FlagChatBasePath ) }},
176177 {"AGENTAPI_TERM_WIDTH" , "AGENTAPI_TERM_WIDTH" , "120" , uint16 (120 ), func () any { return viper .GetUint16 (FlagTermWidth ) }},
177178 {"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 ) }},
178180 }
179181
180182 for _ , tt := range tests {
@@ -291,3 +293,119 @@ func TestMixed_ConfigurationScenarios(t *testing.T) {
291293 assert .Equal (t , uint16 (1000 ), viper .GetUint16 (FlagTermHeight )) // default
292294 })
293295}
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