Skip to main content
Glama

go-mcp-postgres

main_test.go16.5 kB
package main import ( "database/sql" "fmt" "strings" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" ) func setupMockDB(t *testing.T) (*sql.DB, sqlmock.Sqlmock, func()) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("Failed to create mock DB: %v", err) } // Save the original DB originalDB := DB // Replace with our mock DB = sqlx.NewDb(db, "sqlmock") // Return a cleanup function cleanup := func() { db.Close() DB = originalDB } return db, mock, cleanup } func TestGetDB(t *testing.T) { // Save the original DB originalDB := DB defer func() { DB = originalDB }() t.Run("returns existing DB if already set", func(t *testing.T) { // Set a mock DB mockDB := &sqlx.DB{} DB = mockDB // Call GetDB db, err := GetDB() // Verify results assert.NoError(t, err) assert.Equal(t, mockDB, db) }) t.Run("creates new DB connection if not set", func(t *testing.T) { // Reset DB to nil DB = nil // Set DSN to a value that will work with sqlmock originalDSN := DSN DSN = "sqlmock" defer func() { DSN = originalDSN }() // This test is more of an integration test and would require a real DB // For unit testing, we'll just verify that it returns an error with an invalid DSN _, err := GetDB() assert.Error(t, err) }) } func TestHandleQuery(t *testing.T) { _, mock, cleanup := setupMockDB(t) defer cleanup() t.Run("successful query", func(t *testing.T) { // Setup mock expectations rows := sqlmock.NewRows([]string{"id", "name"}). AddRow(1, "test1"). AddRow(2, "test2") mock.ExpectQuery("SELECT").WillReturnRows(rows) // Call HandleQuery result, err := HandleQuery("SELECT id, name FROM users", StatementTypeNoExplainCheck) // Verify results assert.NoError(t, err) assert.Contains(t, result, "id,name") assert.Contains(t, result, "1,test1") assert.Contains(t, result, "2,test2") }) t.Run("query error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("SELECT").WillReturnError(fmt.Errorf("query error")) // Call HandleQuery _, err := HandleQuery("SELECT id, name FROM users", StatementTypeNoExplainCheck) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "query error") }) } func TestDoQuery(t *testing.T) { _, mock, cleanup := setupMockDB(t) defer cleanup() t.Run("successful query", func(t *testing.T) { // Setup mock expectations rows := sqlmock.NewRows([]string{"id", "name"}). AddRow(1, "test1"). AddRow(2, "test2") mock.ExpectQuery("SELECT").WillReturnRows(rows) // Call DoQuery result, headers, err := DoQuery("SELECT id, name FROM users", StatementTypeNoExplainCheck) // Verify results assert.NoError(t, err) assert.Equal(t, []string{"id", "name"}, headers) assert.Len(t, result, 2) assert.Equal(t, int64(1), result[0]["id"]) assert.Equal(t, "test1", result[0]["name"]) assert.Equal(t, int64(2), result[1]["id"]) assert.Equal(t, "test2", result[1]["name"]) }) t.Run("with explain check", func(t *testing.T) { // Save original WithExplainCheck value originalWithExplainCheck := WithExplainCheck WithExplainCheck = true defer func() { WithExplainCheck = originalWithExplainCheck }() // Setup mock expectations for EXPLAIN explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}). AddRow("1", "SELECT", "users", nil, "ALL", nil, nil, nil, nil, "2", "100.00", nil) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Setup mock expectations for actual query rows := sqlmock.NewRows([]string{"id", "name"}). AddRow(1, "test1"). AddRow(2, "test2") mock.ExpectQuery("SELECT").WillReturnRows(rows) // Call DoQuery result, headers, err := DoQuery("SELECT id, name FROM users", StatementTypeSelect) // Verify results assert.NoError(t, err) assert.Equal(t, []string{"id", "name"}, headers) assert.Len(t, result, 2) }) t.Run("query error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("SELECT").WillReturnError(fmt.Errorf("query error")) // Call DoQuery _, _, err := DoQuery("SELECT id, name FROM users", StatementTypeNoExplainCheck) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "query error") }) t.Run("columns error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("SELECT").WillReturnError(fmt.Errorf("columns error")) // Call DoQuery _, _, err := DoQuery("SELECT id, name FROM users", StatementTypeNoExplainCheck) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "columns error") }) t.Run("scan error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("SELECT").WillReturnError(fmt.Errorf("scan error")) // Call DoQuery _, _, err := DoQuery("SELECT id, name FROM users", StatementTypeNoExplainCheck) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "scan error") }) t.Run("with byte array conversion", func(t *testing.T) { // Setup mock expectations with a byte array value rows := sqlmock.NewRows([]string{"id", "blob"}). AddRow(1, []byte("binary data")) mock.ExpectQuery("SELECT").WillReturnRows(rows) // Call DoQuery result, headers, err := DoQuery("SELECT id, blob FROM users", StatementTypeNoExplainCheck) // Verify results assert.NoError(t, err) assert.Equal(t, []string{"id", "blob"}, headers) assert.Len(t, result, 1) assert.Equal(t, int64(1), result[0]["id"]) assert.Equal(t, "binary data", result[0]["blob"]) }) } func TestHandleExec(t *testing.T) { _, mock, cleanup := setupMockDB(t) defer cleanup() t.Run("insert statement", func(t *testing.T) { // Setup mock expectations mock.ExpectExec("INSERT").WillReturnResult(sqlmock.NewResult(123, 1)) // Call HandleExec result, err := HandleExec("INSERT INTO users (name) VALUES ('test')", StatementTypeInsert) // Verify results assert.NoError(t, err) assert.Contains(t, result, "1 rows affected") assert.Contains(t, result, "last insert id: 123") }) t.Run("update statement", func(t *testing.T) { // Setup mock expectations mock.ExpectExec("UPDATE").WillReturnResult(sqlmock.NewResult(0, 2)) // Call HandleExec result, err := HandleExec("UPDATE users SET name = 'updated' WHERE id IN (1, 2)", StatementTypeNoExplainCheck) // Verify results assert.NoError(t, err) assert.Equal(t, "2 rows affected", result) }) t.Run("exec error", func(t *testing.T) { // Setup mock expectations mock.ExpectExec("UPDATE").WillReturnError(fmt.Errorf("exec error")) // Call HandleExec _, err := HandleExec("UPDATE users SET name = 'updated'", StatementTypeNoExplainCheck) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "exec error") }) } func TestHandleExplain(t *testing.T) { _, mock, cleanup := setupMockDB(t) defer cleanup() // Save original WithExplainCheck value originalWithExplainCheck := WithExplainCheck defer func() { WithExplainCheck = originalWithExplainCheck }() t.Run("with explain check disabled", func(t *testing.T) { // Disable explain check WithExplainCheck = false // Call HandleExplain - should return nil without querying err := HandleExplain("SELECT * FROM users", StatementTypeSelect) // Verify results assert.NoError(t, err) }) // Enable explain check for the rest of the tests WithExplainCheck = true t.Run("select query", func(t *testing.T) { // Setup mock expectations explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}). AddRow("1", "SIMPLE", "users", nil, "ALL", nil, nil, nil, nil, "2", "100.00", nil) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Call HandleExplain err := HandleExplain("SELECT * FROM users", StatementTypeSelect) // Verify results assert.NoError(t, err) }) t.Run("insert query", func(t *testing.T) { // Setup mock expectations explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}). AddRow("1", "INSERT", "users", nil, "ALL", nil, nil, nil, nil, "1", "100.00", nil) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Call HandleExplain err := HandleExplain("INSERT INTO users (name) VALUES ('test')", StatementTypeInsert) // Verify results assert.NoError(t, err) }) t.Run("update query", func(t *testing.T) { // Setup mock expectations explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}). AddRow("1", "UPDATE", "users", nil, "ALL", nil, nil, nil, nil, "1", "100.00", nil) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Call HandleExplain err := HandleExplain("UPDATE users SET name = 'test' WHERE id = 1", StatementTypeUpdate) // Verify results assert.NoError(t, err) }) t.Run("delete query", func(t *testing.T) { // Setup mock expectations explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}). AddRow("1", "DELETE", "users", nil, "ALL", nil, nil, nil, nil, "1", "100.00", nil) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Call HandleExplain err := HandleExplain("DELETE FROM users WHERE id = 1", StatementTypeDelete) // Verify results assert.NoError(t, err) }) t.Run("explain error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("EXPLAIN").WillReturnError(fmt.Errorf("explain error")) // Call HandleExplain err := HandleExplain("SELECT * FROM users", StatementTypeSelect) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "explain error") }) t.Run("no results", func(t *testing.T) { // Setup mock expectations explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Call HandleExplain err := HandleExplain("SELECT * FROM users", StatementTypeSelect) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "unable to check query plan") }) t.Run("type mismatch", func(t *testing.T) { // Setup mock expectations explainRows := sqlmock.NewRows([]string{"id", "select_type", "table", "partitions", "type", "possible_keys", "key", "key_len", "ref", "rows", "filtered", "Extra"}). AddRow("1", "INSERT", "users", nil, "ALL", nil, nil, nil, nil, "1", "100.00", nil) mock.ExpectQuery("EXPLAIN").WillReturnRows(explainRows) // Call HandleExplain err := HandleExplain("INSERT INTO users (name) VALUES ('test')", StatementTypeUpdate) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "query plan does not match expected pattern") }) t.Run("scan error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("EXPLAIN").WillReturnError(fmt.Errorf("scan error")) // Call HandleExplain err := HandleExplain("SELECT * FROM users", StatementTypeSelect) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "scan error") }) } /* func TestHandleDescTable(t *testing.T) { _, mock, cleanup := setupMockDB(t) defer cleanup() t.Run("successful desc", func(t *testing.T) { // Setup mock expectations rows := sqlmock.NewRows([]string{"Table", "Create Table"}). AddRow("users", "CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB") mock.ExpectQuery("SHOW CREATE TABLE").WillReturnRows(rows) // Call HandleDescTable result, err := HandleDescTable("users") // Verify results assert.NoError(t, err) assert.Contains(t, result, "CREATE TABLE `users`") }) t.Run("table not found", func(t *testing.T) { // Setup mock expectations rows := sqlmock.NewRows([]string{"Table", "Create Table"}) mock.ExpectQuery("SHOW CREATE TABLE").WillReturnRows(rows) // Call HandleDescTable _, err := HandleDescTable("nonexistent") // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "does not exist") }) t.Run("query error", func(t *testing.T) { // Setup mock expectations mock.ExpectQuery("SHOW CREATE TABLE").WillReturnError(fmt.Errorf("query error")) // Call HandleDescTable _, err := HandleDescTable("users") // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "query error") }) } */ func TestMapToCSV(t *testing.T) { t.Run("successful mapping", func(t *testing.T) { // Setup test data data := []map[string]interface{}{ {"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}, } headers := []string{"id", "name"} // Call MapToCSV result, err := MapToCSV(data, headers) // Verify results assert.NoError(t, err) lines := strings.Split(strings.TrimSpace(result), "\n") assert.Len(t, lines, 3) assert.Equal(t, "id,name", lines[0]) assert.Equal(t, "1,test1", lines[1]) assert.Equal(t, "2,test2", lines[2]) }) t.Run("missing key", func(t *testing.T) { // Setup test data data := []map[string]interface{}{ {"id": 1}, // missing "name" } headers := []string{"id", "name"} // Call MapToCSV _, err := MapToCSV(data, headers) // Verify results assert.Error(t, err) assert.Contains(t, err.Error(), "key 'name' not found in map") }) t.Run("empty data", func(t *testing.T) { // Setup test data data := []map[string]interface{}{} headers := []string{"id", "name"} // Call MapToCSV result, err := MapToCSV(data, headers) // Verify results assert.NoError(t, err) lines := strings.Split(strings.TrimSpace(result), "\n") assert.Len(t, lines, 1) assert.Equal(t, "id,name", lines[0]) }) t.Run("handles different types", func(t *testing.T) { // Setup test data data := []map[string]interface{}{ {"id": 1, "name": "test1", "active": true, "score": 3.14}, } headers := []string{"id", "name", "active", "score"} // Call MapToCSV result, err := MapToCSV(data, headers) // Verify results assert.NoError(t, err) lines := strings.Split(strings.TrimSpace(result), "\n") assert.Len(t, lines, 2) assert.Equal(t, "id,name,active,score", lines[0]) assert.Equal(t, "1,test1,true,3.14", lines[1]) }) t.Run("header write error", func(t *testing.T) { // This is hard to test directly since we can't easily mock the csv.Writer // But we can at least ensure our error handling code is covered // by checking that the error message is correctly formatted _ = []map[string]interface{}{} _ = []string{"id", "name"} // Create a mock error mockErr := fmt.Errorf("mock header write error") // Simulate the error by checking the error message format errMsg := fmt.Errorf("failed to write headers: %v", mockErr).Error() assert.Contains(t, errMsg, "failed to write headers") assert.Contains(t, errMsg, "mock header write error") }) t.Run("row write error", func(t *testing.T) { // Similar to the header write error test, we're checking error message format mockErr := fmt.Errorf("mock row write error") errMsg := fmt.Errorf("failed to write row: %v", mockErr).Error() assert.Contains(t, errMsg, "failed to write row") assert.Contains(t, errMsg, "mock row write error") }) t.Run("flush error", func(t *testing.T) { // Similar to the other error tests, we're checking error message format mockErr := fmt.Errorf("mock flush error") errMsg := fmt.Errorf("error flushing CSV writer: %v", mockErr).Error() assert.Contains(t, errMsg, "error flushing CSV writer") assert.Contains(t, errMsg, "mock flush error") }) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/guoling2008/go-mcp-postgres'

If you have feedback or need assistance with the MCP directory API, please join our Discord server