Skip to content

Commit 3cd3d34

Browse files
authored
Merge branch 'main' into murrayju/env-output
2 parents 9175597 + e8f0a89 commit 3cd3d34

File tree

10 files changed

+75
-22
lines changed

10 files changed

+75
-22
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
run: go vet ./...
4545

4646
- name: Test
47-
run: go test -p 1 -race -coverprofile=coverage.out -coverpkg=./... ./...
47+
run: go test -race -coverprofile=coverage.out -coverpkg=./... ./...
4848

4949
- name: Generate Coverage Report
5050
run: go tool cover -html=coverage.out -o=coverage.html

CLAUDE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ Tiger CLI is a Go-based command-line interface for managing Tiger, the modern da
161161
- **MCP Server**: `internal/tiger/mcp/` - Model Context Protocol server implementation
162162
- `server.go` - MCP server initialization, tool registration, and lifecycle management
163163
- `service_tools.go` - Service management tools (list, show, create, update-password)
164+
- `db_tools.go` - Database operation tools (execute-query)
164165
- `proxy.go` - Proxy client that forwards tools/resources/prompts from remote docs MCP server
165166
- **Password Storage**: `internal/tiger/password/` - Secure password storage utilities
166167

@@ -191,7 +192,9 @@ The Tiger MCP server provides AI assistants with programmatic access to Tiger re
191192

192193
**Two Types of Tools:**
193194

194-
1. **Direct Tiger Tools** (`service_tools.go`) - Native tools for Tiger service management
195+
1. **Direct Tiger Tools** - Native tools for Tiger operations
196+
- `service_tools.go` - Service management (list, show, create, update-password)
197+
- `db_tools.go` - Database operations (execute-query)
195198
2. **Proxied Documentation Tools** (`proxy.go`) - Tools forwarded from a remote docs MCP server (see `proxy.go` for implementation)
196199

197200
**Tool Definition Pattern:**
@@ -281,6 +284,7 @@ Global flags available on all commands:
281284
- **oapi-codegen**: OpenAPI client generation (build-time dependency)
282285
- **gomock**: Mock generation for testing (build-time dependency)
283286
- **go-sdk (MCP)**: Model Context Protocol SDK for AI assistant integration
287+
- **pgx/v5**: PostgreSQL driver for database operations in MCP tools
284288
- **Go 1.25+**: Required Go version
285289

286290
## Project Structure

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tiger CLI
22

3-
Tiger CLI is the command-line interface for managing databases on Tiger Cloud.
3+
Tiger CLI is the command-line interface for Tiger Cloud. It provides commands for managing and querying database services, as well as an integrated Model Context Protocol (MCP) server for use with AI assistants.
44

55
## Installation
66

@@ -166,6 +166,9 @@ The MCP server exposes the following tools to AI assistants:
166166
- `service_create` - Create new database services with configurable resources
167167
- `service_update_password` - Update the master password for a service
168168

169+
**Database Operations:**
170+
- `db_execute_query` - Execute SQL queries against a database service with support for parameterized queries, custom timeouts, and connection pooling
171+
169172
The MCP server automatically uses your CLI authentication and configuration, so no additional setup is required beyond `tiger auth login`.
170173

171174
#### Proxied Tools

internal/tiger/cmd/auth_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import (
2222
func setupAuthTest(t *testing.T) string {
2323
t.Helper()
2424

25+
// Use a unique service name for this test to avoid conflicts
26+
config.SetTestServiceName(t)
27+
2528
// Mock the API key validation for testing
2629
originalValidator := validateAPIKeyForLogin
2730
validateAPIKeyForLogin = func(apiKey, projectID string) error {
@@ -47,10 +50,6 @@ func setupAuthTest(t *testing.T) string {
4750
t.Fatalf("Failed to use test config: %v", err)
4851
}
4952

50-
// Also ensure config file doesn't exist
51-
configFile := config.GetConfigFile(tmpDir)
52-
os.Remove(configFile)
53-
5453
t.Cleanup(func() {
5554
// Clean up test keyring
5655
config.RemoveAPIKeyFromKeyring()

internal/tiger/cmd/auth_validation_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ func TestAuthLogin_APIKeyValidationFailure(t *testing.T) {
1818
}
1919
defer os.RemoveAll(tmpDir)
2020

21+
// Use a unique service name for this test
22+
config.SetTestServiceName(t)
23+
2124
originalValidator := validateAPIKeyForLogin
2225

2326
// Mock the validator to return an error
@@ -73,6 +76,9 @@ func TestAuthLogin_APIKeyValidationSuccess(t *testing.T) {
7376
}
7477
defer os.RemoveAll(tmpDir)
7578

79+
// Use a unique service name for this test
80+
config.SetTestServiceName(t)
81+
7682
originalValidator := validateAPIKeyForLogin
7783

7884
// Mock the validator to return success

internal/tiger/cmd/db_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,9 @@ func TestLaunchPsqlWithAdditionalFlags(t *testing.T) {
328328
}
329329

330330
func TestBuildPsqlCommand_KeyringPasswordEnvVar(t *testing.T) {
331+
// Use a unique service name for this test to avoid conflicts
332+
config.SetTestServiceName(t)
333+
331334
// Set keyring as the password storage method for this test
332335
originalStorage := viper.GetString("password_storage")
333336
viper.Set("password_storage", "keyring")
@@ -950,6 +953,9 @@ func TestDBConnectionString_WithPassword(t *testing.T) {
950953
// This test verifies the end-to-end --with-password flag functionality
951954
// using direct function testing since full integration would require a real service
952955

956+
// Use a unique service name for this test to avoid conflicts
957+
config.SetTestServiceName(t)
958+
953959
// Set keyring as the password storage method for this test
954960
originalStorage := viper.GetString("password_storage")
955961
viper.Set("password_storage", "keyring")

internal/tiger/config/api_key.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,40 @@ import (
1212

1313
// Keyring parameters
1414
const (
15-
keyringServiceName = "tiger-cli"
16-
keyringTestServiceName = "tiger-cli-test"
17-
keyringUsername = "api-key"
15+
keyringServiceName = "tiger-cli"
16+
keyringUsername = "api-key"
1817
)
1918

19+
// testServiceNameOverride allows tests to override the service name for isolation
20+
var testServiceNameOverride string
21+
2022
// GetServiceName returns the appropriate service name for keyring operations
21-
// Uses a test-specific service name when running in test mode to avoid polluting the real keyring
2223
func GetServiceName() string {
23-
// Use Go's built-in testing detection
24+
// Tests should set a unique service name to avoid conflicts
25+
if testServiceNameOverride != "" {
26+
return testServiceNameOverride
27+
}
28+
29+
// In test mode without an override, panic to catch missing test setup
2430
if testing.Testing() {
25-
return keyringTestServiceName
31+
panic("test must call SetTestServiceName() to set a unique keyring service name")
2632
}
2733

2834
return keyringServiceName
2935
}
3036

37+
// SetTestServiceName sets a unique service name for testing based on the test name
38+
// This allows tests to use unique service names to avoid conflicts when running in parallel
39+
// The cleanup is automatically registered with t.Cleanup()
40+
func SetTestServiceName(t *testing.T) {
41+
testServiceNameOverride = "tiger-test-" + t.Name()
42+
43+
// Automatically clean up when the test finishes
44+
t.Cleanup(func() {
45+
testServiceNameOverride = ""
46+
})
47+
}
48+
3149
// storeAPIKey stores the API key using keyring with file fallback
3250
func StoreAPIKey(apiKey string) error {
3351
// Try keyring first

internal/tiger/config/api_key_test.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import (
44
"os"
55
"path/filepath"
66
"testing"
7-
8-
"github.com/spf13/viper"
97
)
108

119
func setupAPIKeyTest(t *testing.T) string {
1210
t.Helper()
1311

12+
// Use a unique service name for this test to avoid conflicts
13+
SetTestServiceName(t)
14+
1415
// Clean up any existing keyring entries before test
1516
RemoveAPIKeyFromKeyring()
1617

@@ -20,8 +21,11 @@ func setupAPIKeyTest(t *testing.T) string {
2021
t.Fatalf("Failed to create temp dir: %v", err)
2122
}
2223

23-
// Wire up the tmp directory as the viper config directory
24-
viper.SetConfigFile(GetConfigFile(tmpDir))
24+
// Reset viper completely and set up with test directory
25+
// This ensures proper test isolation by resetting all viper state
26+
if _, err := UseTestConfig(tmpDir, map[string]any{}); err != nil {
27+
t.Fatalf("Failed to use test config: %v", err)
28+
}
2529

2630
t.Cleanup(func() {
2731
// Clean up keyring entries
@@ -69,15 +73,15 @@ func TestStoreAPIKeyToFile(t *testing.T) {
6973

7074
func TestGetAPIKeyFromFile(t *testing.T) {
7175
tmpDir := setupAPIKeyTest(t)
72-
viper.SetConfigFile(GetConfigFile(tmpDir))
7376

7477
// Write API key to file
7578
apiKeyFile := filepath.Join(tmpDir, "api-key")
7679
if err := os.WriteFile(apiKeyFile, []byte("file-get-test-key"), 0600); err != nil {
7780
t.Fatalf("Failed to write test API key file: %v", err)
7881
}
7982

80-
// Get API key from file
83+
// Get API key - should get from file since keyring is empty
84+
// (each test uses a unique keyring service name)
8185
apiKey, err := GetAPIKey()
8286
if err != nil {
8387
t.Fatalf("Failed to get API key from file: %v", err)
@@ -89,8 +93,7 @@ func TestGetAPIKeyFromFile(t *testing.T) {
8993
}
9094

9195
func TestGetAPIKeyFromFile_NotExists(t *testing.T) {
92-
tmpDir := setupAPIKeyTest(t)
93-
viper.SetConfigFile(GetConfigFile(tmpDir))
96+
setupAPIKeyTest(t)
9497

9598
// Try to get API key when file doesn't exist
9699
_, err := GetAPIKey()
@@ -105,7 +108,6 @@ func TestGetAPIKeyFromFile_NotExists(t *testing.T) {
105108

106109
func TestRemoveAPIKeyFromFile(t *testing.T) {
107110
tmpDir := setupAPIKeyTest(t)
108-
viper.SetConfigFile(GetConfigFile(tmpDir))
109111

110112
// Write API key to file
111113
apiKeyFile := filepath.Join(tmpDir, "api-key")

internal/tiger/password/connection_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/spf13/viper"
1010

1111
"github.com/timescale/tiger-cli/internal/tiger/api"
12+
"github.com/timescale/tiger-cli/internal/tiger/config"
1213
"github.com/timescale/tiger-cli/internal/tiger/util"
1314
)
1415

@@ -186,6 +187,9 @@ func TestBuildConnectionString_Basic(t *testing.T) {
186187
}
187188

188189
func TestBuildConnectionString_WithPassword_KeyringStorage(t *testing.T) {
190+
// Use a unique service name for this test to avoid conflicts
191+
config.SetTestServiceName(t)
192+
189193
// Set keyring as the password storage method for this test
190194
originalStorage := viper.GetString("password_storage")
191195
viper.Set("password_storage", "keyring")
@@ -328,6 +332,9 @@ func TestBuildConnectionString_WithPassword_NoStorage(t *testing.T) {
328332
}
329333

330334
func TestBuildConnectionString_WithPassword_NoPasswordAvailable(t *testing.T) {
335+
// Use a unique service name for this test to avoid conflicts
336+
config.SetTestServiceName(t)
337+
331338
// Set keyring as the password storage method for this test
332339
originalStorage := viper.GetString("password_storage")
333340
viper.Set("password_storage", "keyring")
@@ -366,6 +373,9 @@ func TestBuildConnectionString_WithPassword_NoPasswordAvailable(t *testing.T) {
366373
}
367374

368375
func TestBuildConnectionString_WithPassword_InvalidServiceEndpoint(t *testing.T) {
376+
// Use a unique service name for this test to avoid conflicts
377+
config.SetTestServiceName(t)
378+
369379
// Set keyring as the password storage method for this test
370380
originalStorage := viper.GetString("password_storage")
371381
viper.Set("password_storage", "keyring")

internal/tiger/password/password_storage_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/spf13/viper"
1111
"github.com/timescale/tiger-cli/internal/tiger/api"
12+
"github.com/timescale/tiger-cli/internal/tiger/config"
1213
"github.com/timescale/tiger-cli/internal/tiger/util"
1314
)
1415

@@ -573,6 +574,10 @@ func TestPasswordStorage_OverwritePreviousValue(t *testing.T) {
573574
{
574575
name: "KeyringStorage",
575576
storage: &KeyringStorage{},
577+
setup: func(t *testing.T) {
578+
// Use a unique service name for this test to avoid conflicts
579+
config.SetTestServiceName(t)
580+
},
576581
cleanup: func(t *testing.T, service api.Service) {
577582
storage := &KeyringStorage{}
578583
storage.Remove(service) // Ignore errors in cleanup

0 commit comments

Comments
 (0)