@@ -6,7 +6,10 @@ import (
66 "encoding/json"
77 "os"
88 "path/filepath"
9+ "runtime"
10+ "syscall"
911 "testing"
12+ "time"
1013
1114 "github.com/modelcontextprotocol/go-sdk/mcp"
1215
@@ -134,7 +137,7 @@ func TestRunQuery_MissingDB(t *testing.T) {
134137
135138 var out bytes.Buffer
136139 w := output .New (& out , & bytes.Buffer {})
137- err := runQuery (nil , []string {"select 1" }, & QueryFlags {}, & w )
140+ err := runQuery ([]string {"select 1" }, & QueryFlags {}, & w )
138141 if err == nil {
139142 t .Fatal ("expected error for missing db type" )
140143 }
@@ -149,7 +152,7 @@ func TestRunQuery_UnsupportedDriver(t *testing.T) {
149152
150153 var out bytes.Buffer
151154 w := output .New (& out , & bytes.Buffer {})
152- err := runQuery (nil , []string {"select 1" }, & QueryFlags {}, & w )
155+ err := runQuery ([]string {"select 1" }, & QueryFlags {}, & w )
153156 if err == nil {
154157 t .Fatal ("expected error for unsupported driver" )
155158 }
@@ -168,7 +171,7 @@ func TestRunQuery_PlaintextPasswordNotAllowed(t *testing.T) {
168171
169172 var out bytes.Buffer
170173 w := output .New (& out , & bytes.Buffer {})
171- err := runQuery (nil , []string {"select 1" }, & QueryFlags {}, & w )
174+ err := runQuery ([]string {"select 1" }, & QueryFlags {}, & w )
172175 if err == nil {
173176 t .Fatal ("expected error for plaintext password not allowed" )
174177 }
@@ -183,7 +186,7 @@ func TestRunSchemaDump_UnsupportedDriver(t *testing.T) {
183186
184187 var out bytes.Buffer
185188 w := output .New (& out , & bytes.Buffer {})
186- err := runSchemaDump (nil , nil , & SchemaFlags {}, & w )
189+ err := runSchemaDump (& SchemaFlags {}, & w )
187190 if err == nil {
188191 t .Fatal ("expected error for unsupported driver" )
189192 }
@@ -202,7 +205,7 @@ func TestRunSchemaDump_PlaintextPasswordNotAllowed(t *testing.T) {
202205
203206 var out bytes.Buffer
204207 w := output .New (& out , & bytes.Buffer {})
205- err := runSchemaDump (nil , nil , & SchemaFlags {}, & w )
208+ err := runSchemaDump (& SchemaFlags {}, & w )
206209 if err == nil {
207210 t .Fatal ("expected error for plaintext password not allowed" )
208211 }
@@ -217,7 +220,7 @@ func TestRunQuery_InvalidFormat(t *testing.T) {
217220
218221 var out bytes.Buffer
219222 w := output .New (& out , & bytes.Buffer {})
220- err := runQuery (nil , []string {"select 1" }, & QueryFlags {}, & w )
223+ err := runQuery ([]string {"select 1" }, & QueryFlags {}, & w )
221224 if err == nil {
222225 t .Fatal ("expected error for invalid format" )
223226 }
@@ -232,7 +235,7 @@ func TestRunSchemaDump_MissingDB(t *testing.T) {
232235
233236 var out bytes.Buffer
234237 w := output .New (& out , & bytes.Buffer {})
235- err := runSchemaDump (nil , nil , & SchemaFlags {}, & w )
238+ err := runSchemaDump (& SchemaFlags {}, & w )
236239 if err == nil {
237240 t .Fatal ("expected error for missing db type" )
238241 }
@@ -247,7 +250,7 @@ func TestRunSchemaDump_InvalidFormat(t *testing.T) {
247250
248251 var out bytes.Buffer
249252 w := output .New (& out , & bytes.Buffer {})
250- err := runSchemaDump (nil , nil , & SchemaFlags {}, & w )
253+ err := runSchemaDump (& SchemaFlags {}, & w )
251254 if err == nil {
252255 t .Fatal ("expected error for invalid format" )
253256 }
@@ -348,7 +351,7 @@ func TestRunProxy_SSHConnectError(t *testing.T) {
348351}
349352
350353func TestResolveSSH_NoConfig (t * testing.T ) {
351- client , err := app .ResolveSSH (nil , config.Profile {}, false , false )
354+ client , err := app .ResolveSSH (context . TODO () , config.Profile {}, false , false )
352355 if err != nil {
353356 t .Fatalf ("unexpected error: %v" , err )
354357 }
@@ -909,3 +912,262 @@ func TestFirstNonEmpty(t *testing.T) {
909912func configProfile (dbType string ) config.Profile {
910913 return config.Profile {DB : dbType }
911914}
915+
916+ func TestHandlePortConflict_NonTTY (t * testing.T ) {
917+ _ , err := handlePortConflict (3306 , "127.0.0.1" )
918+ if err == nil {
919+ t .Fatal ("expected error for non-TTY port conflict" )
920+ }
921+ if err .Code != errors .CodePortInUse {
922+ t .Fatalf ("expected CodePortInUse, got %s" , err .Code )
923+ }
924+ }
925+
926+ func TestModeForWebCommand (t * testing.T ) {
927+ if got := modeForWebCommand (true ); got != "web" {
928+ t .Fatalf ("expected 'web', got %q" , got )
929+ }
930+ if got := modeForWebCommand (false ); got != "serve" {
931+ t .Fatalf ("expected 'serve', got %q" , got )
932+ }
933+ }
934+
935+ func TestResolveWebOptions_InvalidAddr (t * testing.T ) {
936+ _ , xe := resolveWebOptions (& webCommandOptions {
937+ addr : "not-a-valid-addr" ,
938+ addrSet : true ,
939+ }, config.File {})
940+ if xe == nil {
941+ t .Fatal ("expected error for invalid addr" )
942+ }
943+ if xe .Code != errors .CodeCfgInvalid {
944+ t .Fatalf ("expected CodeCfgInvalid, got %s" , xe .Code )
945+ }
946+ }
947+
948+ func TestResolveWebOptions_EnvVars (t * testing.T ) {
949+ t .Setenv ("XSQL_WEB_HTTP_AUTH_TOKEN" , "env-token" )
950+ resolved , xe := resolveWebOptions (& webCommandOptions {
951+ addr : "0.0.0.0:9999" ,
952+ addrSet : true ,
953+ }, config.File {})
954+ if xe != nil {
955+ t .Fatalf ("unexpected error: %v" , xe )
956+ }
957+ if resolved .authToken != "env-token" {
958+ t .Fatalf ("expected env-token, got %s" , resolved .authToken )
959+ }
960+ if ! resolved .authRequired {
961+ t .Fatal ("expected authRequired=true" )
962+ }
963+ }
964+
965+ func TestResolveMCPServerOptions_HttpAddrEnv (t * testing.T ) {
966+ t .Setenv ("XSQL_MCP_TRANSPORT" , "streamable_http" )
967+ t .Setenv ("XSQL_MCP_HTTP_AUTH_TOKEN" , "token" )
968+ t .Setenv ("XSQL_MCP_HTTP_ADDR" , "127.0.0.1:5555" )
969+ cfg := config.File {
970+ Profiles : map [string ]config.Profile {},
971+ SSHProxies : map [string ]config.SSHProxy {},
972+ }
973+ resolved , xe := resolveMCPServerOptions (& mcpServerOptions {}, cfg )
974+ if xe != nil {
975+ t .Fatalf ("unexpected error: %v" , xe )
976+ }
977+ if resolved .httpAddr != "127.0.0.1:5555" {
978+ t .Fatalf ("expected 127.0.0.1:5555, got %s" , resolved .httpAddr )
979+ }
980+ }
981+
982+ func TestRunMCPServer_InvalidConfigPath (t * testing.T ) {
983+ GlobalConfig .ConfigStr = "/nonexistent/path/config.yaml"
984+ err := runMCPServer (& mcpServerOptions {})
985+ if err == nil {
986+ t .Fatal ("expected error for nonexistent config" )
987+ }
988+ }
989+
990+ func TestRunMCPServer_StreamableHTTPStarts (t * testing.T ) {
991+ if runtime .GOOS == "windows" {
992+ t .Skip ("skipping signal handling test on Windows" )
993+ }
994+ configPath := filepath .Join (t .TempDir (), "xsql.yaml" )
995+ if err := os .WriteFile (configPath , []byte ("profiles: {}\n mcp:\n transport: streamable_http\n http:\n addr: 127.0.0.1:0\n auth_token: test-token\n allow_plaintext_token: true\n " ), 0644 ); err != nil {
996+ t .Fatalf ("failed to write config: %v" , err )
997+ }
998+
999+ GlobalConfig .ConfigStr = configPath
1000+
1001+ // This will start the HTTP server and then we need to stop it
1002+ // We'll use a goroutine to run it and cancel after a short time
1003+ done := make (chan error , 1 )
1004+ go func () {
1005+ done <- runMCPServer (& mcpServerOptions {})
1006+ }()
1007+
1008+ // Give the server time to start
1009+ time .Sleep (100 * time .Millisecond )
1010+
1011+ // The server should be running, send SIGINT to stop it
1012+ p , _ := os .FindProcess (os .Getpid ())
1013+ _ = p .Signal (syscall .SIGINT )
1014+
1015+ select {
1016+ case err := <- done :
1017+ if err != nil {
1018+ t .Fatalf ("unexpected error: %v" , err )
1019+ }
1020+ case <- time .After (5 * time .Second ):
1021+ t .Fatal ("timeout waiting for server to stop" )
1022+ }
1023+ }
1024+
1025+ func TestNewServeCommand (t * testing.T ) {
1026+ var out bytes.Buffer
1027+ w := output .New (& out , & bytes.Buffer {})
1028+ cmd := NewServeCommand (& w )
1029+ if cmd .Use != "serve" {
1030+ t .Fatalf ("expected 'serve', got %s" , cmd .Use )
1031+ }
1032+ }
1033+
1034+ func TestNewWebCommand (t * testing.T ) {
1035+ var out bytes.Buffer
1036+ w := output .New (& out , & bytes.Buffer {})
1037+ cmd := NewWebCommand (& w )
1038+ if cmd .Use != "web" {
1039+ t .Fatalf ("expected 'web', got %s" , cmd .Use )
1040+ }
1041+ }
1042+
1043+ func TestNewMCPCommand (t * testing.T ) {
1044+ cmd := NewMCPCommand ()
1045+ if cmd .Use != "mcp" {
1046+ t .Fatalf ("expected 'mcp', got %s" , cmd .Use )
1047+ }
1048+ }
1049+
1050+ func TestNewProfileCommand (t * testing.T ) {
1051+ var out bytes.Buffer
1052+ w := output .New (& out , & bytes.Buffer {})
1053+ cmd := NewProfileCommand (& w )
1054+ if cmd .Use != "profile" {
1055+ t .Fatalf ("expected 'profile', got %s" , cmd .Use )
1056+ }
1057+ }
1058+
1059+ func TestNewSchemaCommand (t * testing.T ) {
1060+ var out bytes.Buffer
1061+ w := output .New (& out , & bytes.Buffer {})
1062+ cmd := NewSchemaCommand (& w )
1063+ if cmd .Use != "schema" {
1064+ t .Fatalf ("expected 'schema', got %s" , cmd .Use )
1065+ }
1066+ }
1067+
1068+ func TestResolveWebOptions_NilOpts (t * testing.T ) {
1069+ resolved , xe := resolveWebOptions (nil , config.File {})
1070+ if xe != nil {
1071+ t .Fatalf ("unexpected error: %v" , xe )
1072+ }
1073+ if resolved .addr != "127.0.0.1:8788" {
1074+ t .Fatalf ("expected default addr, got %s" , resolved .addr )
1075+ }
1076+ }
1077+
1078+ func TestResolveWebOptions_ConfigAddr (t * testing.T ) {
1079+ resolved , xe := resolveWebOptions (& webCommandOptions {}, config.File {
1080+ Web : config.WebConfig {
1081+ HTTP : config.WebHTTPConfig {
1082+ Addr : "127.0.0.1:9999" ,
1083+ },
1084+ },
1085+ })
1086+ if xe != nil {
1087+ t .Fatalf ("unexpected error: %v" , xe )
1088+ }
1089+ if resolved .addr != "127.0.0.1:9999" {
1090+ t .Fatalf ("expected 127.0.0.1:9999, got %s" , resolved .addr )
1091+ }
1092+ }
1093+
1094+ func TestResolveWebOptions_NonLoopbackRequiresToken (t * testing.T ) {
1095+ _ , xe := resolveWebOptions (& webCommandOptions {
1096+ addr : "10.0.0.1:8788" ,
1097+ addrSet : true ,
1098+ }, config.File {})
1099+ if xe == nil {
1100+ t .Fatal ("expected error for non-loopback without token" )
1101+ }
1102+ if xe .Code != errors .CodeCfgInvalid {
1103+ t .Fatalf ("expected CodeCfgInvalid, got %s" , xe .Code )
1104+ }
1105+ }
1106+
1107+ func TestRunProxy_InvalidFormat (t * testing.T ) {
1108+ prev := GlobalConfig
1109+ GlobalConfig = & Config {ProfileStr : "dev" , FormatStr : "invalid" }
1110+ t .Cleanup (func () { GlobalConfig = prev })
1111+
1112+ GlobalConfig .Resolved .Profile = config.Profile {DB : "mysql" , SSHConfig : & config.SSHProxy {Host : "h" , Port : 22 , User : "u" }}
1113+
1114+ var out bytes.Buffer
1115+ w := output .New (& out , & bytes.Buffer {})
1116+ err := runProxy (nil , & ProxyFlags {}, & w )
1117+ if err == nil {
1118+ t .Fatal ("expected error for invalid format" )
1119+ }
1120+ }
1121+
1122+ func TestRunProxy_PlaintextNotAllowed (t * testing.T ) {
1123+ prev := GlobalConfig
1124+ GlobalConfig = & Config {ProfileStr : "dev" , FormatStr : "json" }
1125+ t .Cleanup (func () { GlobalConfig = prev })
1126+
1127+ GlobalConfig .Resolved .Profile = config.Profile {
1128+ DB : "mysql" ,
1129+ Password : "plain" ,
1130+ AllowPlaintext : false ,
1131+ SSHConfig : & config.SSHProxy {Host : "h" , Port : 22 , User : "u" },
1132+ }
1133+
1134+ var out bytes.Buffer
1135+ w := output .New (& out , & bytes.Buffer {})
1136+ err := runProxy (nil , & ProxyFlags {}, & w )
1137+ if err == nil {
1138+ t .Fatal ("expected error for plaintext not allowed" )
1139+ }
1140+ }
1141+
1142+ func TestResolveProxyPort_AllPaths (t * testing.T ) {
1143+ // Test with config port and no CLI flag
1144+ cmd := NewProxyCommand (nil )
1145+ port , fromConfig := resolveProxyPort (cmd , & ProxyFlags {}, 5555 )
1146+ if port != 5555 || ! fromConfig {
1147+ t .Errorf ("expected port=5555, fromConfig=true, got port=%d, fromConfig=%v" , port , fromConfig )
1148+ }
1149+
1150+ // Test with zero config port
1151+ port , fromConfig = resolveProxyPort (cmd , & ProxyFlags {}, 0 )
1152+ if port != 0 || fromConfig {
1153+ t .Errorf ("expected port=0, fromConfig=false, got port=%d, fromConfig=%v" , port , fromConfig )
1154+ }
1155+ }
1156+
1157+ func TestNewQueryCommand (t * testing.T ) {
1158+ var out bytes.Buffer
1159+ w := output .New (& out , & bytes.Buffer {})
1160+ cmd := NewQueryCommand (& w )
1161+ if cmd .Use != "query [SQL]" {
1162+ t .Fatalf ("expected 'query [SQL]', got %s" , cmd .Use )
1163+ }
1164+ }
1165+
1166+ func TestNewProxyCommand (t * testing.T ) {
1167+ var out bytes.Buffer
1168+ w := output .New (& out , & bytes.Buffer {})
1169+ cmd := NewProxyCommand (& w )
1170+ if cmd .Use != "proxy [flags]" {
1171+ t .Fatalf ("expected 'proxy [flags]', got %s" , cmd .Use )
1172+ }
1173+ }
0 commit comments