Skip to main content
Glama

Keeper Secrets Manager - MCP

confirm_test.go10 kB
package ui import ( "context" "fmt" "strings" "testing" "time" "github.com/keeper-security/ksm-mcp/pkg/types" "github.com/stretchr/testify/assert" ) func TestNewConfirmer(t *testing.T) { config := types.Confirmation{ BatchMode: false, AutoApprove: false, Timeout: 30 * time.Second, DefaultDeny: false, } confirmer := NewConfirmer(config) if confirmer == nil { t.Fatal("NewConfirmer returned nil") } if confirmer.config.Timeout != config.Timeout { t.Errorf("Expected timeout %v, got %v", config.Timeout, confirmer.config.Timeout) } } func TestConfirmBatchMode(t *testing.T) { tests := []struct { name string batchMode bool autoApprove bool defaultDeny bool expected bool }{ {"batch mode approve", true, false, false, true}, {"batch mode deny", true, false, true, false}, {"auto approve", false, true, false, true}, {"auto approve with deny", false, true, true, false}, {"batch + auto approve", true, true, false, true}, {"batch + auto with deny", true, true, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := types.Confirmation{ BatchMode: tt.batchMode, AutoApprove: tt.autoApprove, DefaultDeny: tt.defaultDeny, Timeout: 5 * time.Second, } confirmer := NewConfirmer(config) ctx := context.Background() result := confirmer.Confirm(ctx, "Test confirmation") if result.Approved != tt.expected { t.Errorf("Expected approved=%v, got %v", tt.expected, result.Approved) } if result.TimedOut { t.Error("Should not have timed out in batch mode") } if result.Error != nil { t.Errorf("Unexpected error: %v", result.Error) } }) } } func TestConfirm_InteractiveModeReturnsError(t *testing.T) { config := types.Confirmation{ BatchMode: false, AutoApprove: false, Timeout: 100 * time.Millisecond, DefaultDeny: false, } confirmer := NewConfirmer(config) ctx := context.Background() result := confirmer.Confirm(ctx, "Test interactive prompt") assert.Error(t, result.Error, "Expected an error in interactive mode due to MCP prompt flow") assert.False(t, result.Approved, "Should not be approved if error occurred") assert.False(t, result.TimedOut, "Should not timeout if error occurs before promptUser") if result.Error != nil { assert.Contains(t, result.Error.Error(), "interactive confirmation via terminal is not supported for this operation", "Error message mismatch") } } func TestParseResponse(t *testing.T) { tests := []struct { name string response string defaultDeny bool expected bool }{ // Explicit approvals {"yes", "yes", false, true}, {"y", "y", false, true}, {"Y", "Y", false, true}, {"YES", "YES", false, true}, {"true", "true", false, true}, {"1", "1", false, true}, // Explicit denials {"no", "no", false, false}, {"n", "n", false, false}, {"N", "N", false, false}, {"NO", "NO", false, false}, {"false", "false", false, false}, {"0", "0", false, false}, // Empty responses (use default) {"empty default approve", "", false, true}, {"empty default deny", "", true, false}, {"whitespace default approve", " ", false, true}, {"whitespace default deny", " ", true, false}, // Invalid responses (use default) {"invalid default approve", "maybe", false, true}, {"invalid default deny", "perhaps", true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := types.Confirmation{ DefaultDeny: tt.defaultDeny, } confirmer := NewConfirmer(config) result := confirmer.parseResponse(tt.response) if result != tt.expected { t.Errorf("Expected %v, got %v for response '%s'", tt.expected, result, tt.response) } }) } } func TestConfirmOperation(t *testing.T) { config := types.Confirmation{ BatchMode: true, // Use batch mode for predictable testing AutoApprove: true, DefaultDeny: false, } confirmer := NewConfirmer(config) ctx := context.Background() details := map[string]interface{}{ "uid": "test-uid-123", "field": "password", "password": "secret123", // This should be masked in display } result := confirmer.ConfirmOperation(ctx, "retrieve", "Production DB", details) if !result.Approved { t.Error("Expected operation to be approved") } if result.Error != nil { t.Errorf("Unexpected error: %v", result.Error) } } func TestConfirmSensitiveOperation(t *testing.T) { tests := []struct { name string masked bool expected bool }{ {"masked sensitive operation", true, true}, {"unmasked sensitive operation", false, false}, // Should default to deny } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := types.Confirmation{ BatchMode: true, AutoApprove: false, // Don't auto-approve to test default behavior DefaultDeny: false, } confirmer := NewConfirmer(config) ctx := context.Background() result := confirmer.ConfirmSensitiveOperation(ctx, "retrieve", "secret-key", tt.masked) if result.Approved != tt.expected { t.Errorf("Expected approved=%v, got %v for masked=%v", tt.expected, result.Approved, tt.masked) } }) } } func TestIsSensitiveKey(t *testing.T) { confirmer := NewConfirmer(types.Confirmation{}) tests := []struct { key string sensitive bool }{ {"password", true}, {"secret", true}, {"api_key", true}, {"token", true}, {"auth_token", true}, {"private_key", true}, {"passphrase", true}, {"PIN", true}, {"access_code", true}, // Non-sensitive {"username", false}, {"email", false}, {"title", false}, {"url", false}, {"notes", false}, {"uid", false}, } for _, tt := range tests { t.Run(tt.key, func(t *testing.T) { result := confirmer.isSensitiveKey(tt.key) if result != tt.sensitive { t.Errorf("Expected %v for key '%s', got %v", tt.sensitive, tt.key, result) } }) } } func TestBuildOperationMessage(t *testing.T) { confirmer := NewConfirmer(types.Confirmation{}) details := map[string]interface{}{ "uid": "test-uid", "password": "secret123", "username": "testuser", "api_key": "key123", } message := confirmer.buildOperationMessage("retrieve", "Test Secret", details) // Should contain operation and resource if !strings.Contains(message, "retrieve") { t.Error("Message should contain operation") } if !strings.Contains(message, "Test Secret") { t.Error("Message should contain resource") } // Should mask sensitive fields if strings.Contains(message, "secret123") { t.Error("Message should not contain sensitive password") } if strings.Contains(message, "key123") { t.Error("Message should not contain sensitive API key") } // Should show non-sensitive fields if !strings.Contains(message, "testuser") { t.Error("Message should contain non-sensitive username") } if !strings.Contains(message, "test-uid") { t.Error("Message should contain non-sensitive UID") } // Should contain masked indicators if !strings.Contains(message, "[MASKED]") { t.Error("Message should contain masking indicators") } } func TestConfirmBatchOperation(t *testing.T) { config := types.Confirmation{ BatchMode: true, AutoApprove: true, DefaultDeny: false, } confirmer := NewConfirmer(config) ctx := context.Background() tests := []struct { name string items []string wantErr bool approved bool }{ { "normal batch", []string{"item1", "item2", "item3"}, false, true, }, { "large batch", make([]string, 10), // 10 items false, true, }, { "empty batch", []string{}, true, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Fill items for large batch test if len(tt.items) == 10 { for i := range tt.items { tt.items[i] = fmt.Sprintf("item%d", i+1) } } result := confirmer.ConfirmBatchOperation(ctx, "delete", tt.items) if tt.wantErr && result.Error == nil { t.Error("Expected error for empty batch") } if !tt.wantErr && result.Error != nil { t.Errorf("Unexpected error: %v", result.Error) } if result.Approved != tt.approved { t.Errorf("Expected approved=%v, got %v", tt.approved, result.Approved) } }) } } func TestConfigurationMethods(t *testing.T) { originalConfig := types.Confirmation{ BatchMode: false, AutoApprove: false, Timeout: 30 * time.Second, DefaultDeny: true, } confirmer := NewConfirmer(originalConfig) // Test GetConfig currentConfig := confirmer.GetConfig() if currentConfig.DefaultDeny != originalConfig.DefaultDeny { t.Error("GetConfig returned incorrect configuration") } // Test SetConfig newConfig := types.Confirmation{ BatchMode: true, AutoApprove: true, Timeout: 60 * time.Second, DefaultDeny: false, } confirmer.SetConfig(newConfig) updatedConfig := confirmer.GetConfig() if updatedConfig.BatchMode != newConfig.BatchMode { t.Error("SetConfig did not update configuration") } // Test IsInteractive if confirmer.IsInteractive() { t.Error("Should not be interactive in batch mode with auto-approve") } // Test interactive mode interactiveConfig := types.Confirmation{ BatchMode: false, AutoApprove: false, } confirmer.SetConfig(interactiveConfig) if !confirmer.IsInteractive() { t.Error("Should be interactive when not in batch mode and not auto-approve") } } func TestContextCancellation(t *testing.T) { config := types.Confirmation{ BatchMode: false, AutoApprove: false, Timeout: 5 * time.Second, // Long timeout DefaultDeny: true, } confirmer := NewConfirmer(config) ctx, cancel := context.WithCancel(context.Background()) // Cancel context immediately cancel() result := confirmer.Confirm(ctx, "Test cancellation") if !result.TimedOut { t.Error("Expected timeout on cancelled context") } if result.Approved != false { // Should use default deny t.Error("Expected denial on cancelled context") } }

Latest Blog Posts

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/Keeper-Security/keeper-mcp-golang-docker'

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