Files
bifrost/framework/configstore/tables/encryption_test.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1983 lines
66 KiB
Go

package tables
import (
"os"
"testing"
"time"
bifrost "github.com/maximhq/bifrost/core"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/encrypt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
const testEncryptionKey = "test-encryption-key-for-testing-32bytes"
func init() {
encrypt.Init(testEncryptionKey, bifrost.NewDefaultLogger(schemas.LogLevelInfo))
}
// setupTestDB creates an in-memory SQLite database with all tables migrated.
func setupTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
require.NoError(t, err)
err = db.AutoMigrate(
&TableKey{},
&TableProvider{},
&TableMCPClient{},
&TablePlugin{},
&TableVirtualKey{},
&SessionsTable{},
&TableOauthConfig{},
&TableOauthToken{},
&TableVectorStoreConfig{},
)
require.NoError(t, err)
return db
}
// rawRow reads a single row from the given table without triggering GORM hooks,
// returning the raw column values as a map. This lets tests verify that encrypted
// ciphertext is actually stored in the database.
func rawRow(t *testing.T, db *gorm.DB, table string, id any) map[string]any {
t.Helper()
var row map[string]any
err := db.Table(table).Where("id = ?", id).Take(&row).Error
require.NoError(t, err)
return row
}
// ============================================================================
// TableKey encryption tests
// ============================================================================
func TestTableKey_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
key := &TableKey{
Name: "test-key",
ProviderID: 1,
Provider: "openai",
KeyID: "key-uuid-1",
Value: *schemas.NewEnvVar("sk-secret-api-key"),
}
require.NoError(t, db.Create(key).Error)
// Verify raw DB has encrypted value
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "sk-secret-api-key", raw["value"])
// Verify reading back decrypts
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
assert.Equal(t, "sk-secret-api-key", found.Value.GetValue())
}
func TestTableKey_AzureFieldsEncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
endpoint := schemas.NewEnvVar("https://my-azure.openai.azure.com")
clientSecret := schemas.NewEnvVar("azure-secret-123")
apiVersion := schemas.NewEnvVar("2024-10-21")
key := &TableKey{
Name: "azure-key",
ProviderID: 1,
Provider: "azure",
KeyID: "azure-uuid-1",
Value: *schemas.NewEnvVar("azure-api-key"),
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: *endpoint,
ClientID: schemas.NewEnvVar("azure-client-id-123"),
ClientSecret: clientSecret,
TenantID: schemas.NewEnvVar("azure-tenant-id-456"),
APIVersion: apiVersion,
},
}
require.NoError(t, db.Create(key).Error)
// Verify raw DB has encrypted values
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "https://my-azure.openai.azure.com", raw["azure_endpoint"])
assert.NotEqual(t, "azure-client-id-123", raw["azure_client_id"])
assert.NotEqual(t, "azure-secret-123", raw["azure_client_secret"])
assert.NotEqual(t, "azure-tenant-id-456", raw["azure_tenant_id"])
assert.NotEqual(t, "2024-10-21", raw["azure_api_version"])
// Verify reading back decrypts and reconstructs AzureKeyConfig
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
assert.Equal(t, "azure-api-key", found.Value.GetValue())
require.NotNil(t, found.AzureKeyConfig)
assert.Equal(t, "https://my-azure.openai.azure.com", found.AzureKeyConfig.Endpoint.GetValue())
require.NotNil(t, found.AzureKeyConfig.ClientID)
assert.Equal(t, "azure-client-id-123", found.AzureKeyConfig.ClientID.GetValue())
assert.Equal(t, "azure-secret-123", found.AzureKeyConfig.ClientSecret.GetValue())
require.NotNil(t, found.AzureKeyConfig.TenantID)
assert.Equal(t, "azure-tenant-id-456", found.AzureKeyConfig.TenantID.GetValue())
require.NotNil(t, found.AzureKeyConfig.APIVersion)
assert.Equal(t, "2024-10-21", found.AzureKeyConfig.APIVersion.GetValue())
}
func TestTableKey_VertexFieldsEncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
key := &TableKey{
Name: "vertex-key",
ProviderID: 1,
Provider: "vertex",
KeyID: "vertex-uuid-1",
Value: *schemas.NewEnvVar("vertex-api-key"),
VertexKeyConfig: &schemas.VertexKeyConfig{
ProjectID: *schemas.NewEnvVar("my-project"),
ProjectNumber: *schemas.NewEnvVar("123456789"),
Region: *schemas.NewEnvVar("us-central1"),
AuthCredentials: *schemas.NewEnvVar(`{"type":"service_account"}`),
},
}
require.NoError(t, db.Create(key).Error)
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "my-project", raw["vertex_project_id"])
assert.NotEqual(t, "123456789", raw["vertex_project_number"])
assert.NotEqual(t, "us-central1", raw["vertex_region"])
assert.NotEqual(t, `{"type":"service_account"}`, raw["vertex_auth_credentials"])
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.VertexKeyConfig)
assert.Equal(t, "my-project", found.VertexKeyConfig.ProjectID.GetValue())
assert.Equal(t, "123456789", found.VertexKeyConfig.ProjectNumber.GetValue())
assert.Equal(t, "us-central1", found.VertexKeyConfig.Region.GetValue())
assert.Equal(t, `{"type":"service_account"}`, found.VertexKeyConfig.AuthCredentials.GetValue())
}
func TestTableKey_BedrockFieldsEncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
key := &TableKey{
Name: "bedrock-key",
ProviderID: 1,
Provider: "bedrock",
KeyID: "bedrock-uuid-1",
Value: *schemas.NewEnvVar("bedrock-val"),
Aliases: schemas.KeyAliases{"model-a": "profile-a"},
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: *schemas.NewEnvVar("AKIAIOSFODNN7EXAMPLE"),
SecretKey: *schemas.NewEnvVar("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
Region: schemas.NewEnvVar("us-west-2"),
ARN: schemas.NewEnvVar("arn:aws:iam::123456789:role/test"),
BatchS3Config: &schemas.BatchS3Config{
Buckets: []schemas.S3BucketConfig{
{BucketName: "my-batch-bucket", Prefix: "jobs/", IsDefault: true},
},
},
},
}
require.NoError(t, db.Create(key).Error)
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "AKIAIOSFODNN7EXAMPLE", raw["bedrock_access_key"])
assert.NotEqual(t, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", raw["bedrock_secret_key"])
assert.NotEqual(t, "us-west-2", raw["bedrock_region"])
assert.NotEqual(t, "arn:aws:iam::123456789:role/test", raw["bedrock_arn"])
rawAliasesVal := raw["aliases_json"]
require.NotNil(t, rawAliasesVal, "aliases_json should be present in raw row")
var rawAliasesStr string
switch v := rawAliasesVal.(type) {
case string:
rawAliasesStr = v
case []byte:
rawAliasesStr = string(v)
}
require.NotEmpty(t, rawAliasesStr, "aliases_json should not be empty")
assert.NotContains(t, rawAliasesStr, "profile-a")
if rawBatch, ok := raw["bedrock_batch_s3_config_json"].(string); ok {
assert.NotContains(t, rawBatch, "my-batch-bucket")
}
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.BedrockKeyConfig)
assert.Equal(t, "AKIAIOSFODNN7EXAMPLE", found.BedrockKeyConfig.AccessKey.GetValue())
assert.Equal(t, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", found.BedrockKeyConfig.SecretKey.GetValue())
require.NotNil(t, found.BedrockKeyConfig.Region)
assert.Equal(t, "us-west-2", found.BedrockKeyConfig.Region.GetValue())
require.NotNil(t, found.BedrockKeyConfig.ARN)
assert.Equal(t, "arn:aws:iam::123456789:role/test", found.BedrockKeyConfig.ARN.GetValue())
assert.Equal(t, "profile-a", found.Aliases["model-a"])
require.NotNil(t, found.BedrockKeyConfig.BatchS3Config)
require.Len(t, found.BedrockKeyConfig.BatchS3Config.Buckets, 1)
assert.Equal(t, "my-batch-bucket", found.BedrockKeyConfig.BatchS3Config.Buckets[0].BucketName)
assert.Equal(t, "jobs/", found.BedrockKeyConfig.BatchS3Config.Buckets[0].Prefix)
assert.True(t, found.BedrockKeyConfig.BatchS3Config.Buckets[0].IsDefault)
}
func TestTableKey_EnvVarNotEncrypted(t *testing.T) {
db := setupTestDB(t)
// When the value comes from an env var, it should NOT be encrypted
key := &TableKey{
Name: "env-key",
ProviderID: 1,
Provider: "openai",
KeyID: "env-uuid-1",
Value: *schemas.NewEnvVar("env.OPENAI_API_KEY"),
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
// The value should be readable (either the env var value or empty if not set)
assert.True(t, found.Value.IsFromEnv())
}
// ============================================================================
// TableProvider encryption tests
// ============================================================================
func TestTableProvider_ProxyConfigEncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
proxyConfig := &schemas.ProxyConfig{
URL: "https://proxy.example.com",
}
provider := &TableProvider{
Name: "openai",
ProxyConfig: proxyConfig,
}
require.NoError(t, db.Create(provider).Error)
raw := rawRow(t, db, "config_providers", provider.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
// The proxy config JSON should be encrypted (not valid JSON anymore)
rawProxy, ok := raw["proxy_config_json"].(string)
assert.True(t, ok)
assert.NotContains(t, rawProxy, "proxy.example.com")
var found TableProvider
require.NoError(t, db.First(&found, provider.ID).Error)
require.NotNil(t, found.ProxyConfig)
assert.Equal(t, "https://proxy.example.com", found.ProxyConfig.URL)
}
func TestTableProvider_NoProxyConfig_NoEncryption(t *testing.T) {
db := setupTestDB(t)
provider := &TableProvider{
Name: "anthropic",
}
require.NoError(t, db.Create(provider).Error)
raw := rawRow(t, db, "config_providers", provider.ID)
// Without proxy config, encryption status should remain plain_text
assert.NotEqual(t, "encrypted", raw["encryption_status"])
}
// ============================================================================
// TableMCPClient encryption tests
// ============================================================================
func TestTableMCPClient_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
connStr := schemas.NewEnvVar("https://mcp-server.example.com/sse")
client := &TableMCPClient{
ClientID: "mcp-1",
Name: "test-mcp",
ConnectionType: "sse",
ConnectionString: connStr,
Headers: map[string]schemas.EnvVar{
"Authorization": *schemas.NewEnvVar("Bearer secret-token"),
},
}
require.NoError(t, db.Create(client).Error)
raw := rawRow(t, db, "config_mcp_clients", client.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
// Connection string should be encrypted
rawConnStr, ok := raw["connection_string"].(string)
assert.True(t, ok)
assert.NotContains(t, rawConnStr, "mcp-server.example.com")
// Headers JSON should be encrypted
rawHeaders, ok := raw["headers_json"].(string)
assert.True(t, ok)
assert.NotContains(t, rawHeaders, "secret-token")
var found TableMCPClient
require.NoError(t, db.First(&found, client.ID).Error)
assert.Equal(t, "https://mcp-server.example.com/sse", found.ConnectionString.GetValue())
require.Contains(t, found.Headers, "Authorization")
assert.Equal(t, "Bearer secret-token", found.Headers["Authorization"].Val)
}
func TestTableMCPClient_EnvVarConnectionString_NotEncrypted(t *testing.T) {
db := setupTestDB(t)
connStr := schemas.NewEnvVar("env.MCP_SERVER_URL")
client := &TableMCPClient{
ClientID: "mcp-env",
Name: "env-mcp",
ConnectionType: "sse",
ConnectionString: connStr,
}
require.NoError(t, db.Create(client).Error)
var found TableMCPClient
require.NoError(t, db.First(&found, client.ID).Error)
assert.True(t, found.ConnectionString.IsFromEnv())
}
// ============================================================================
// TablePlugin encryption tests
// ============================================================================
func TestTablePlugin_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
plugin := &TablePlugin{
Name: "test-plugin",
Enabled: true,
Version: 1,
Config: map[string]any{"api_key": "secret-plugin-key", "endpoint": "https://plugin.example.com"},
}
require.NoError(t, db.Create(plugin).Error)
raw := rawRow(t, db, "config_plugins", plugin.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
rawConfig, ok := raw["config_json"].(string)
assert.True(t, ok)
assert.NotContains(t, rawConfig, "secret-plugin-key")
var found TablePlugin
require.NoError(t, db.First(&found, plugin.ID).Error)
configMap, ok := found.Config.(map[string]any)
require.True(t, ok)
assert.Equal(t, "secret-plugin-key", configMap["api_key"])
}
func TestTablePlugin_EmptyConfig_NoEncryption(t *testing.T) {
db := setupTestDB(t)
plugin := &TablePlugin{
Name: "empty-plugin",
Enabled: true,
Version: 1,
// nil Config will serialize to "{}"
}
require.NoError(t, db.Create(plugin).Error)
raw := rawRow(t, db, "config_plugins", plugin.ID)
assert.NotEqual(t, "encrypted", raw["encryption_status"])
}
// ============================================================================
// TableVirtualKey encryption tests
// ============================================================================
func TestTableVirtualKey_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
vk := &TableVirtualKey{
ID: "vk-1",
Name: "test-vk",
Value: "vk-secret-value-xyz",
IsActive: true,
}
require.NoError(t, db.Create(vk).Error)
raw := rawRow(t, db, "governance_virtual_keys", "vk-1")
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "vk-secret-value-xyz", raw["value"])
// Verify hash was computed from plaintext
expectedHash := encrypt.HashSHA256("vk-secret-value-xyz")
assert.Equal(t, expectedHash, raw["value_hash"])
var found TableVirtualKey
require.NoError(t, db.First(&found, "id = ?", "vk-1").Error)
assert.Equal(t, "vk-secret-value-xyz", found.Value)
assert.Equal(t, expectedHash, found.ValueHash)
}
func TestTableVirtualKey_HashComputedBeforeEncryption(t *testing.T) {
db := setupTestDB(t)
vk := &TableVirtualKey{
ID: "vk-hash",
Name: "hash-test",
Value: "plaintext-value",
IsActive: true,
}
require.NoError(t, db.Create(vk).Error)
raw := rawRow(t, db, "governance_virtual_keys", "vk-hash")
// Hash should be of the plaintext, not the ciphertext
assert.Equal(t, encrypt.HashSHA256("plaintext-value"), raw["value_hash"])
}
// ============================================================================
// SessionsTable encryption tests
// ============================================================================
func TestSessionsTable_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
session := &SessionsTable{
Token: "session-secret-token-abc",
ExpiresAt: time.Now().Add(24 * time.Hour),
}
require.NoError(t, db.Create(session).Error)
raw := rawRow(t, db, "sessions", session.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "session-secret-token-abc", raw["token"])
// Verify hash was computed from plaintext
expectedHash := encrypt.HashSHA256("session-secret-token-abc")
assert.Equal(t, expectedHash, raw["token_hash"])
var found SessionsTable
require.NoError(t, db.First(&found, session.ID).Error)
assert.Equal(t, "session-secret-token-abc", found.Token)
}
func TestSessionsTable_HashComputedBeforeEncryption(t *testing.T) {
db := setupTestDB(t)
session := &SessionsTable{
Token: "hash-test-token",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(session).Error)
raw := rawRow(t, db, "sessions", session.ID)
assert.Equal(t, encrypt.HashSHA256("hash-test-token"), raw["token_hash"])
}
// ============================================================================
// TableOauthConfig encryption tests
// ============================================================================
func TestTableOauthConfig_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
config := &TableOauthConfig{
ID: "oauth-cfg-1",
ClientID: "client-id-public",
ClientSecret: "super-secret-client-secret",
RedirectURI: "https://example.com/callback",
State: "csrf-state-token",
CodeVerifier: "pkce-code-verifier-secret",
ExpiresAt: time.Now().Add(15 * time.Minute),
}
require.NoError(t, db.Create(config).Error)
raw := rawRow(t, db, "oauth_configs", "oauth-cfg-1")
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "super-secret-client-secret", raw["client_secret"])
assert.NotEqual(t, "pkce-code-verifier-secret", raw["code_verifier"])
var found TableOauthConfig
require.NoError(t, db.First(&found, "id = ?", "oauth-cfg-1").Error)
assert.Equal(t, "super-secret-client-secret", found.ClientSecret)
assert.Equal(t, "pkce-code-verifier-secret", found.CodeVerifier)
// Non-sensitive fields should be unchanged
assert.Equal(t, "client-id-public", found.ClientID)
assert.Equal(t, "https://example.com/callback", found.RedirectURI)
}
func TestTableOauthConfig_EmptySecret_NoError(t *testing.T) {
db := setupTestDB(t)
config := &TableOauthConfig{
ID: "oauth-cfg-empty",
RedirectURI: "https://example.com/callback",
State: "csrf-state-2",
ExpiresAt: time.Now().Add(15 * time.Minute),
}
require.NoError(t, db.Create(config).Error)
var found TableOauthConfig
require.NoError(t, db.First(&found, "id = ?", "oauth-cfg-empty").Error)
assert.Equal(t, "", found.ClientSecret)
assert.Equal(t, "", found.CodeVerifier)
}
// ============================================================================
// TableOauthToken encryption tests
// ============================================================================
func TestTableOauthToken_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
token := &TableOauthToken{
ID: "oauth-tok-1",
AccessToken: "access-token-secret-value",
RefreshToken: "refresh-token-secret-value",
TokenType: "Bearer",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(token).Error)
raw := rawRow(t, db, "oauth_tokens", "oauth-tok-1")
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "access-token-secret-value", raw["access_token"])
assert.NotEqual(t, "refresh-token-secret-value", raw["refresh_token"])
var found TableOauthToken
require.NoError(t, db.First(&found, "id = ?", "oauth-tok-1").Error)
assert.Equal(t, "access-token-secret-value", found.AccessToken)
assert.Equal(t, "refresh-token-secret-value", found.RefreshToken)
}
func TestTableOauthToken_EmptyRefreshToken(t *testing.T) {
db := setupTestDB(t)
token := &TableOauthToken{
ID: "oauth-tok-norefresh",
AccessToken: "access-only-token",
TokenType: "Bearer",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(token).Error)
var found TableOauthToken
require.NoError(t, db.First(&found, "id = ?", "oauth-tok-norefresh").Error)
assert.Equal(t, "access-only-token", found.AccessToken)
assert.Equal(t, "", found.RefreshToken)
}
// ============================================================================
// TableVectorStoreConfig encryption tests
// ============================================================================
func TestTableVectorStoreConfig_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
configJSON := `{"host":"redis.example.com","port":6379,"password":"redis-secret"}`
vs := &TableVectorStoreConfig{
Enabled: true,
Type: "redis",
Config: &configJSON,
}
require.NoError(t, db.Create(vs).Error)
raw := rawRow(t, db, "config_vector_store", vs.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
rawConfig, ok := raw["config"].(string)
assert.True(t, ok)
assert.NotContains(t, rawConfig, "redis-secret")
var found TableVectorStoreConfig
require.NoError(t, db.First(&found, vs.ID).Error)
require.NotNil(t, found.Config)
assert.Contains(t, *found.Config, "redis-secret")
assert.Contains(t, *found.Config, "redis.example.com")
}
func TestTableVectorStoreConfig_NilConfig_NoEncryption(t *testing.T) {
db := setupTestDB(t)
vs := &TableVectorStoreConfig{
Enabled: false,
Type: "redis",
}
require.NoError(t, db.Create(vs).Error)
raw := rawRow(t, db, "config_vector_store", vs.ID)
assert.NotEqual(t, "encrypted", raw["encryption_status"])
}
// ============================================================================
// Round-trip: save, read, update, read again
// ============================================================================
func TestTableKey_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
key := &TableKey{
Name: "update-key",
ProviderID: 1,
Provider: "openai",
KeyID: "update-uuid",
Value: *schemas.NewEnvVar("original-key"),
}
require.NoError(t, db.Create(key).Error)
// Read back
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
assert.Equal(t, "original-key", found.Value.GetValue())
// Update value
found.Value = *schemas.NewEnvVar("updated-key")
require.NoError(t, db.Save(&found).Error)
// Read again
var found2 TableKey
require.NoError(t, db.First(&found2, key.ID).Error)
assert.Equal(t, "updated-key", found2.Value.GetValue())
// Verify DB still has encrypted value
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "updated-key", raw["value"])
}
func TestSessionsTable_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
session := &SessionsTable{
Token: "original-token",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(session).Error)
var found SessionsTable
require.NoError(t, db.First(&found, session.ID).Error)
assert.Equal(t, "original-token", found.Token)
found.Token = "updated-token"
require.NoError(t, db.Save(&found).Error)
var found2 SessionsTable
require.NoError(t, db.First(&found2, session.ID).Error)
assert.Equal(t, "updated-token", found2.Token)
raw := rawRow(t, db, "sessions", session.ID)
assert.Equal(t, encrypt.HashSHA256("updated-token"), raw["token_hash"])
}
// ============================================================================
// Helper function unit tests
// ============================================================================
func TestEncryptString_NilIsNoop(t *testing.T) {
require.NoError(t, encryptString(nil))
}
func TestEncryptString_EmptyIsNoop(t *testing.T) {
s := ""
require.NoError(t, encryptString(&s))
assert.Equal(t, "", s)
}
func TestDecryptString_NilIsNoop(t *testing.T) {
require.NoError(t, decryptString(nil))
}
func TestDecryptString_EmptyIsNoop(t *testing.T) {
s := ""
require.NoError(t, decryptString(&s))
assert.Equal(t, "", s)
}
func TestEncryptDecryptString_RoundTrip(t *testing.T) {
original := "my-secret-value"
s := original
require.NoError(t, encryptString(&s))
assert.NotEqual(t, original, s, "should be encrypted")
require.NoError(t, decryptString(&s))
assert.Equal(t, original, s, "should match after decrypt")
}
func TestEncryptEnvVar_NilIsNoop(t *testing.T) {
require.NoError(t, encryptEnvVar(nil))
}
func TestEncryptEnvVar_EmptyValueIsNoop(t *testing.T) {
ev := schemas.NewEnvVar("")
require.NoError(t, encryptEnvVar(ev))
assert.Equal(t, "", ev.Val)
}
func TestEncryptEnvVar_EnvRefIsNoop(t *testing.T) {
ev := schemas.NewEnvVar("env.MY_VAR")
originalVal := ev.Val
require.NoError(t, encryptEnvVar(ev))
// Value should not change — env var references are never encrypted
assert.Equal(t, originalVal, ev.Val)
assert.True(t, ev.IsFromEnv())
}
func TestDecryptEnvVar_NilIsNoop(t *testing.T) {
require.NoError(t, decryptEnvVar(nil))
}
func TestDecryptEnvVar_EmptyValueIsNoop(t *testing.T) {
ev := schemas.NewEnvVar("")
require.NoError(t, decryptEnvVar(ev))
assert.Equal(t, "", ev.Val)
}
func TestDecryptEnvVar_EnvRefIsNoop(t *testing.T) {
ev := schemas.NewEnvVar("env.MY_VAR")
originalVal := ev.Val
require.NoError(t, decryptEnvVar(ev))
assert.Equal(t, originalVal, ev.Val)
}
func TestEncryptDecryptEnvVar_RoundTrip(t *testing.T) {
ev := schemas.NewEnvVar("super-secret")
require.NoError(t, encryptEnvVar(ev))
assert.NotEqual(t, "super-secret", ev.Val)
require.NoError(t, decryptEnvVar(ev))
assert.Equal(t, "super-secret", ev.Val)
}
func TestEncryptEnvVarPtr_NilOuterIsNoop(t *testing.T) {
require.NoError(t, encryptEnvVarPtr(nil))
}
func TestEncryptEnvVarPtr_NilInnerIsNoop(t *testing.T) {
var ev *schemas.EnvVar
require.NoError(t, encryptEnvVarPtr(&ev))
}
func TestDecryptEnvVarPtr_NilOuterIsNoop(t *testing.T) {
require.NoError(t, decryptEnvVarPtr(nil))
}
func TestDecryptEnvVarPtr_NilInnerIsNoop(t *testing.T) {
var ev *schemas.EnvVar
require.NoError(t, decryptEnvVarPtr(&ev))
}
func TestEncryptDecryptEnvVarPtr_RoundTrip(t *testing.T) {
ev := schemas.NewEnvVar("ptr-secret")
require.NoError(t, encryptEnvVarPtr(&ev))
assert.NotEqual(t, "ptr-secret", ev.Val)
require.NoError(t, decryptEnvVarPtr(&ev))
assert.Equal(t, "ptr-secret", ev.Val)
}
// ============================================================================
// TableKey — BedrockSessionToken (pointer EnvVar field)
// ============================================================================
func TestTableKey_BedrockSessionTokenEncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
sessionToken := schemas.NewEnvVar("FwoGZXIvYXdzEBYaDH...")
region := schemas.NewEnvVar("us-east-1")
key := &TableKey{
Name: "bedrock-session-key",
ProviderID: 1,
Provider: "bedrock",
KeyID: "bedrock-st-uuid",
Value: *schemas.NewEnvVar("bedrock-val-2"),
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: *schemas.NewEnvVar("AKIA-ST-EXAMPLE"),
SecretKey: *schemas.NewEnvVar("wJalr-ST-EXAMPLE"),
SessionToken: sessionToken,
Region: region,
},
}
require.NoError(t, db.Create(key).Error)
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
// SessionToken and Region should be encrypted in the raw DB
assert.NotEqual(t, "FwoGZXIvYXdzEBYaDH...", raw["bedrock_session_token"])
assert.NotEqual(t, "us-east-1", raw["bedrock_region"])
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.BedrockKeyConfig)
require.NotNil(t, found.BedrockKeyConfig.SessionToken)
assert.Equal(t, "FwoGZXIvYXdzEBYaDH...", found.BedrockKeyConfig.SessionToken.GetValue())
assert.Equal(t, "AKIA-ST-EXAMPLE", found.BedrockKeyConfig.AccessKey.GetValue())
assert.Equal(t, "wJalr-ST-EXAMPLE", found.BedrockKeyConfig.SecretKey.GetValue())
require.NotNil(t, found.BedrockKeyConfig.Region)
assert.Equal(t, "us-east-1", found.BedrockKeyConfig.Region.GetValue())
}
// ============================================================================
// MCP — edge cases for connection string / headers combinations
// ============================================================================
func TestTableMCPClient_DirectConnStr_EmptyHeaders(t *testing.T) {
db := setupTestDB(t)
// Direct connection string (not env var), no headers
connStr := schemas.NewEnvVar("https://mcp-direct.example.com/sse")
client := &TableMCPClient{
ClientID: "mcp-direct-nohdr",
Name: "direct-no-headers",
ConnectionType: "sse",
ConnectionString: connStr,
// No headers — serializes to "{}" which should NOT be encrypted
}
require.NoError(t, db.Create(client).Error)
raw := rawRow(t, db, "config_mcp_clients", client.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
var found TableMCPClient
require.NoError(t, db.First(&found, client.ID).Error)
assert.Equal(t, "https://mcp-direct.example.com/sse", found.ConnectionString.GetValue())
}
func TestTableMCPClient_HeadersOnly_NoConnStr(t *testing.T) {
db := setupTestDB(t)
client := &TableMCPClient{
ClientID: "mcp-hdr-only",
Name: "headers-only",
ConnectionType: "sse",
Headers: map[string]schemas.EnvVar{
"X-Api-Key": *schemas.NewEnvVar("secret-api-key"),
},
}
require.NoError(t, db.Create(client).Error)
raw := rawRow(t, db, "config_mcp_clients", client.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
var found TableMCPClient
require.NoError(t, db.First(&found, client.ID).Error)
require.Contains(t, found.Headers, "X-Api-Key")
assert.Equal(t, "secret-api-key", found.Headers["X-Api-Key"].Val)
}
// ============================================================================
// Round-trip update tests for remaining tables
// ============================================================================
func TestTableVirtualKey_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
vk := &TableVirtualKey{
ID: "vk-update",
Name: "update-vk",
Value: "original-vk-value",
IsActive: true,
}
require.NoError(t, db.Create(vk).Error)
var found TableVirtualKey
require.NoError(t, db.First(&found, "id = ?", "vk-update").Error)
assert.Equal(t, "original-vk-value", found.Value)
found.Value = "updated-vk-value"
require.NoError(t, db.Save(&found).Error)
var found2 TableVirtualKey
require.NoError(t, db.First(&found2, "id = ?", "vk-update").Error)
assert.Equal(t, "updated-vk-value", found2.Value)
raw := rawRow(t, db, "governance_virtual_keys", "vk-update")
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.Equal(t, encrypt.HashSHA256("updated-vk-value"), raw["value_hash"])
}
func TestTableOauthConfig_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
config := &TableOauthConfig{
ID: "oauth-cfg-update",
ClientSecret: "original-secret",
RedirectURI: "https://example.com/callback",
State: "csrf-update",
ExpiresAt: time.Now().Add(15 * time.Minute),
}
require.NoError(t, db.Create(config).Error)
var found TableOauthConfig
require.NoError(t, db.First(&found, "id = ?", "oauth-cfg-update").Error)
assert.Equal(t, "original-secret", found.ClientSecret)
found.ClientSecret = "rotated-secret"
require.NoError(t, db.Save(&found).Error)
var found2 TableOauthConfig
require.NoError(t, db.First(&found2, "id = ?", "oauth-cfg-update").Error)
assert.Equal(t, "rotated-secret", found2.ClientSecret)
}
func TestTableOauthToken_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
token := &TableOauthToken{
ID: "oauth-tok-update",
AccessToken: "original-access",
RefreshToken: "original-refresh",
TokenType: "Bearer",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(token).Error)
var found TableOauthToken
require.NoError(t, db.First(&found, "id = ?", "oauth-tok-update").Error)
found.AccessToken = "refreshed-access"
require.NoError(t, db.Save(&found).Error)
var found2 TableOauthToken
require.NoError(t, db.First(&found2, "id = ?", "oauth-tok-update").Error)
assert.Equal(t, "refreshed-access", found2.AccessToken)
assert.Equal(t, "original-refresh", found2.RefreshToken)
}
func TestTableProvider_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
provider := &TableProvider{
Name: "update-provider",
ProxyConfig: &schemas.ProxyConfig{URL: "https://proxy-v1.example.com"},
}
require.NoError(t, db.Create(provider).Error)
var found TableProvider
require.NoError(t, db.First(&found, provider.ID).Error)
assert.Equal(t, "https://proxy-v1.example.com", found.ProxyConfig.URL)
found.ProxyConfig = &schemas.ProxyConfig{URL: "https://proxy-v2.example.com"}
require.NoError(t, db.Save(&found).Error)
var found2 TableProvider
require.NoError(t, db.First(&found2, provider.ID).Error)
require.NotNil(t, found2.ProxyConfig)
assert.Equal(t, "https://proxy-v2.example.com", found2.ProxyConfig.URL)
}
func TestTablePlugin_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
plugin := &TablePlugin{
Name: "update-plugin",
Enabled: true,
Version: 1,
Config: map[string]any{"key": "original-secret"},
}
require.NoError(t, db.Create(plugin).Error)
var found TablePlugin
require.NoError(t, db.First(&found, plugin.ID).Error)
configMap := found.Config.(map[string]any)
assert.Equal(t, "original-secret", configMap["key"])
found.Config = map[string]any{"key": "updated-secret"}
require.NoError(t, db.Save(&found).Error)
var found2 TablePlugin
require.NoError(t, db.First(&found2, plugin.ID).Error)
configMap2 := found2.Config.(map[string]any)
assert.Equal(t, "updated-secret", configMap2["key"])
}
func TestTableVectorStoreConfig_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
configV1 := `{"host":"redis-v1.example.com","password":"secret-v1"}`
vs := &TableVectorStoreConfig{
Enabled: true,
Type: "redis",
Config: &configV1,
}
require.NoError(t, db.Create(vs).Error)
var found TableVectorStoreConfig
require.NoError(t, db.First(&found, vs.ID).Error)
assert.Contains(t, *found.Config, "secret-v1")
configV2 := `{"host":"redis-v2.example.com","password":"secret-v2"}`
found.Config = &configV2
require.NoError(t, db.Save(&found).Error)
var found2 TableVectorStoreConfig
require.NoError(t, db.First(&found2, vs.ID).Error)
assert.Contains(t, *found2.Config, "secret-v2")
}
func TestTableMCPClient_UpdatePreservesDecryption(t *testing.T) {
db := setupTestDB(t)
connStr := schemas.NewEnvVar("https://mcp-v1.example.com/sse")
client := &TableMCPClient{
ClientID: "mcp-update",
Name: "update-mcp",
ConnectionType: "sse",
ConnectionString: connStr,
Headers: map[string]schemas.EnvVar{
"Authorization": *schemas.NewEnvVar("Bearer token-v1"),
},
}
require.NoError(t, db.Create(client).Error)
var found TableMCPClient
require.NoError(t, db.First(&found, client.ID).Error)
assert.Equal(t, "https://mcp-v1.example.com/sse", found.ConnectionString.GetValue())
found.ConnectionString = schemas.NewEnvVar("https://mcp-v2.example.com/sse")
found.Headers = map[string]schemas.EnvVar{
"Authorization": *schemas.NewEnvVar("Bearer token-v2"),
}
require.NoError(t, db.Save(&found).Error)
var found2 TableMCPClient
require.NoError(t, db.First(&found2, client.ID).Error)
assert.Equal(t, "https://mcp-v2.example.com/sse", found2.ConnectionString.GetValue())
assert.Equal(t, "Bearer token-v2", found2.Headers["Authorization"].Val)
}
// ============================================================================
// Multi-row Find — verify all rows get decrypted
// ============================================================================
func TestTableKey_FindMultipleDecryptsAll(t *testing.T) {
db := setupTestDB(t)
for i, val := range []string{"key-alpha", "key-beta", "key-gamma"} {
key := &TableKey{
Name: val,
ProviderID: 1,
Provider: "openai",
KeyID: val + "-uuid",
Value: *schemas.NewEnvVar("secret-" + val),
Models: []string{"gpt-4"},
}
_ = i
require.NoError(t, db.Create(key).Error)
}
var keys []TableKey
require.NoError(t, db.Find(&keys).Error)
assert.Len(t, keys, 3)
for _, k := range keys {
assert.Contains(t, k.Value.GetValue(), "secret-")
assert.NotContains(t, k.Value.GetValue(), "=") // base64 ciphertext artefact
}
}
func TestSessionsTable_FindMultipleDecryptsAll(t *testing.T) {
db := setupTestDB(t)
for _, tok := range []string{"token-1", "token-2", "token-3"} {
session := &SessionsTable{
Token: tok,
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(session).Error)
}
var sessions []SessionsTable
require.NoError(t, db.Find(&sessions).Error)
assert.Len(t, sessions, 3)
tokens := map[string]bool{}
for _, s := range sessions {
tokens[s.Token] = true
}
assert.True(t, tokens["token-1"])
assert.True(t, tokens["token-2"])
assert.True(t, tokens["token-3"])
}
func TestTableOauthToken_FindMultipleDecryptsAll(t *testing.T) {
db := setupTestDB(t)
for _, id := range []string{"multi-tok-1", "multi-tok-2"} {
token := &TableOauthToken{
ID: id,
AccessToken: "access-" + id,
RefreshToken: "refresh-" + id,
TokenType: "Bearer",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(token).Error)
}
var tokens []TableOauthToken
require.NoError(t, db.Find(&tokens).Error)
assert.Len(t, tokens, 2)
for _, tok := range tokens {
assert.Contains(t, tok.AccessToken, "access-multi-tok-")
assert.Contains(t, tok.RefreshToken, "refresh-multi-tok-")
}
}
// ============================================================================
// Key with all provider configs simultaneously (complex round-trip)
// ============================================================================
func TestTableKey_AllProviderConfigs_EncryptDecrypt(t *testing.T) {
db := setupTestDB(t)
sessionToken := schemas.NewEnvVar("aws-session-token")
key := &TableKey{
Name: "multi-provider-key",
ProviderID: 1,
Provider: "custom",
KeyID: "multi-uuid",
Value: *schemas.NewEnvVar("multi-api-key"),
Aliases: schemas.KeyAliases{"claude-3": "profile-claude"},
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: *schemas.NewEnvVar("https://azure.endpoint.com"),
ClientID: schemas.NewEnvVar("multi-azure-cid"),
ClientSecret: schemas.NewEnvVar("azure-cs"),
TenantID: schemas.NewEnvVar("multi-azure-tid"),
APIVersion: schemas.NewEnvVar("2024-10-21"),
},
VertexKeyConfig: &schemas.VertexKeyConfig{
AuthCredentials: *schemas.NewEnvVar(`{"type":"sa"}`),
ProjectID: *schemas.NewEnvVar("proj-123"),
ProjectNumber: *schemas.NewEnvVar("987654321"),
Region: *schemas.NewEnvVar("us-central1"),
},
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: *schemas.NewEnvVar("AKIA-MULTI"),
SecretKey: *schemas.NewEnvVar("wJalr-MULTI"),
SessionToken: sessionToken,
Region: schemas.NewEnvVar("eu-west-1"),
ARN: schemas.NewEnvVar("arn:aws:bedrock:eu-west-1:123:role"),
},
}
require.NoError(t, db.Create(key).Error)
// Verify raw DB has encrypted values for all new fields
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "encrypted", raw["encryption_status"])
assert.NotEqual(t, "multi-azure-cid", raw["azure_client_id"])
assert.NotEqual(t, "multi-azure-tid", raw["azure_tenant_id"])
assert.NotEqual(t, "2024-10-21", raw["azure_api_version"])
assert.NotEqual(t, "proj-123", raw["vertex_project_id"])
assert.NotEqual(t, "987654321", raw["vertex_project_number"])
assert.NotEqual(t, "us-central1", raw["vertex_region"])
assert.NotEqual(t, "eu-west-1", raw["bedrock_region"])
assert.NotEqual(t, "arn:aws:bedrock:eu-west-1:123:role", raw["bedrock_arn"])
rawAliasesVal2 := raw["aliases_json"]
require.NotNil(t, rawAliasesVal2, "aliases_json should be present in raw row")
var rawAliasesStr2 string
switch v := rawAliasesVal2.(type) {
case string:
rawAliasesStr2 = v
case []byte:
rawAliasesStr2 = string(v)
}
require.NotEmpty(t, rawAliasesStr2, "aliases_json should not be empty")
assert.NotContains(t, rawAliasesStr2, "profile-claude")
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
assert.Equal(t, "multi-api-key", found.Value.GetValue())
require.NotNil(t, found.AzureKeyConfig)
assert.Equal(t, "https://azure.endpoint.com", found.AzureKeyConfig.Endpoint.GetValue())
require.NotNil(t, found.AzureKeyConfig.ClientID)
assert.Equal(t, "multi-azure-cid", found.AzureKeyConfig.ClientID.GetValue())
assert.Equal(t, "azure-cs", found.AzureKeyConfig.ClientSecret.GetValue())
require.NotNil(t, found.AzureKeyConfig.TenantID)
assert.Equal(t, "multi-azure-tid", found.AzureKeyConfig.TenantID.GetValue())
require.NotNil(t, found.AzureKeyConfig.APIVersion)
assert.Equal(t, "2024-10-21", found.AzureKeyConfig.APIVersion.GetValue())
require.NotNil(t, found.VertexKeyConfig)
assert.Equal(t, `{"type":"sa"}`, found.VertexKeyConfig.AuthCredentials.GetValue())
assert.Equal(t, "proj-123", found.VertexKeyConfig.ProjectID.GetValue())
assert.Equal(t, "987654321", found.VertexKeyConfig.ProjectNumber.GetValue())
assert.Equal(t, "us-central1", found.VertexKeyConfig.Region.GetValue())
require.NotNil(t, found.BedrockKeyConfig)
assert.Equal(t, "AKIA-MULTI", found.BedrockKeyConfig.AccessKey.GetValue())
assert.Equal(t, "wJalr-MULTI", found.BedrockKeyConfig.SecretKey.GetValue())
require.NotNil(t, found.BedrockKeyConfig.SessionToken)
assert.Equal(t, "aws-session-token", found.BedrockKeyConfig.SessionToken.GetValue())
require.NotNil(t, found.BedrockKeyConfig.Region)
assert.Equal(t, "eu-west-1", found.BedrockKeyConfig.Region.GetValue())
require.NotNil(t, found.BedrockKeyConfig.ARN)
assert.Equal(t, "arn:aws:bedrock:eu-west-1:123:role", found.BedrockKeyConfig.ARN.GetValue())
assert.Equal(t, "profile-claude", found.Aliases["claude-3"])
}
// ============================================================================
// Encryption disabled — verify hooks are no-ops and data stays plaintext
// ============================================================================
// disableEncryption temporarily disables encryption for the duration of a test
// by reinitializing with an empty key. It registers a cleanup to restore the key.
func disableEncryption(t *testing.T) {
t.Helper()
encrypt.Init("", bifrost.NewDefaultLogger(schemas.LogLevelInfo))
t.Cleanup(func() {
encrypt.Init(testEncryptionKey, bifrost.NewDefaultLogger(schemas.LogLevelInfo))
})
}
func TestTableKey_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
endpoint := schemas.NewEnvVar("https://azure.example.com")
key := &TableKey{
Name: "disabled-key",
ProviderID: 1,
Provider: "azure",
KeyID: "dis-1",
Value: *schemas.NewEnvVar("sk-plaintext-stays"),
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: *endpoint,
},
}
require.NoError(t, db.Create(key).Error)
// Raw DB should have plaintext values
raw := rawRow(t, db, "config_keys", key.ID)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Equal(t, "sk-plaintext-stays", raw["value"])
assert.Equal(t, "https://azure.example.com", raw["azure_endpoint"])
// GORM read should return same plaintext (no decrypt attempt)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
assert.Equal(t, "sk-plaintext-stays", found.Value.GetValue())
require.NotNil(t, found.AzureKeyConfig)
assert.Equal(t, "https://azure.example.com", found.AzureKeyConfig.Endpoint.GetValue())
}
func TestTableMCPClient_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
client := &TableMCPClient{
ClientID: "mcp-dis-1",
Name: "disabled-mcp",
ConnectionType: "sse",
ConnectionString: schemas.NewEnvVar("https://mcp.example.com"),
Headers: map[string]schemas.EnvVar{
"Authorization": *schemas.NewEnvVar("Bearer secret-token"),
},
}
require.NoError(t, db.Create(client).Error)
// Raw DB should have plaintext
raw := rawRow(t, db, "config_mcp_clients", client.ID)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Equal(t, "https://mcp.example.com", raw["connection_string"])
assert.Contains(t, raw["headers_json"], "Bearer secret-token")
// GORM read should return same plaintext
var found TableMCPClient
require.NoError(t, db.First(&found, client.ID).Error)
assert.Equal(t, "https://mcp.example.com", found.ConnectionString.GetValue())
assert.Equal(t, "Bearer secret-token", found.Headers["Authorization"].Val)
}
func TestTableVirtualKey_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
vk := &TableVirtualKey{
ID: "vk-dis-1",
Name: "disabled-vk",
Value: "vk-plaintext-value",
IsActive: true,
}
require.NoError(t, db.Create(vk).Error)
// Raw DB should have plaintext value — hash should still be computed
var raw map[string]any
db.Table("governance_virtual_keys").Where("id = ?", "vk-dis-1").Take(&raw)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Equal(t, "vk-plaintext-value", raw["value"])
assert.NotEmpty(t, raw["value_hash"], "hash should still be computed even without encryption")
// GORM read should return same plaintext
var found TableVirtualKey
require.NoError(t, db.Where("id = ?", "vk-dis-1").First(&found).Error)
assert.Equal(t, "vk-plaintext-value", found.Value)
}
func TestSessionsTable_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
session := &SessionsTable{
Token: "session-plaintext-token",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(session).Error)
// Raw DB should have plaintext token — hash should still be computed
raw := rawRow(t, db, "sessions", session.ID)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Equal(t, "session-plaintext-token", raw["token"])
assert.NotEmpty(t, raw["token_hash"], "hash should still be computed even without encryption")
// GORM read should return same plaintext
var found SessionsTable
require.NoError(t, db.First(&found, session.ID).Error)
assert.Equal(t, "session-plaintext-token", found.Token)
}
func TestTableOauthConfig_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
cfg := &TableOauthConfig{
ID: "cfg-dis-1",
ClientSecret: "client-secret-plain",
CodeVerifier: "verifier-plain",
RedirectURI: "https://example.com/cb",
State: "csrf-state",
Status: "pending",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(cfg).Error)
// Raw DB should have plaintext
var raw map[string]any
db.Table("oauth_configs").Where("id = ?", "cfg-dis-1").Take(&raw)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Equal(t, "client-secret-plain", raw["client_secret"])
assert.Equal(t, "verifier-plain", raw["code_verifier"])
// GORM read should return same plaintext
var found TableOauthConfig
require.NoError(t, db.Where("id = ?", "cfg-dis-1").First(&found).Error)
assert.Equal(t, "client-secret-plain", found.ClientSecret)
assert.Equal(t, "verifier-plain", found.CodeVerifier)
}
func TestTableOauthToken_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
token := &TableOauthToken{
ID: "tok-dis-1",
AccessToken: "access-plain",
RefreshToken: "refresh-plain",
TokenType: "Bearer",
ExpiresAt: time.Now().Add(time.Hour),
}
require.NoError(t, db.Create(token).Error)
// Raw DB should have plaintext
var raw map[string]any
db.Table("oauth_tokens").Where("id = ?", "tok-dis-1").Take(&raw)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Equal(t, "access-plain", raw["access_token"])
assert.Equal(t, "refresh-plain", raw["refresh_token"])
// GORM read should return same plaintext
var found TableOauthToken
require.NoError(t, db.Where("id = ?", "tok-dis-1").First(&found).Error)
assert.Equal(t, "access-plain", found.AccessToken)
assert.Equal(t, "refresh-plain", found.RefreshToken)
}
func TestTableProvider_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
provider := &TableProvider{
Name: "disabled-provider",
ProxyConfig: &schemas.ProxyConfig{
URL: "https://proxy.example.com",
Password: "proxy-secret",
},
}
require.NoError(t, db.Create(provider).Error)
// Raw DB should have plaintext proxy config
raw := rawRow(t, db, "config_providers", provider.ID)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Contains(t, raw["proxy_config_json"], "proxy-secret")
// GORM read should return same plaintext
var found TableProvider
require.NoError(t, db.First(&found, provider.ID).Error)
require.NotNil(t, found.ProxyConfig)
assert.Equal(t, "proxy-secret", found.ProxyConfig.Password)
}
func TestTablePlugin_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
plugin := &TablePlugin{
Name: "disabled-plugin",
Enabled: true,
Version: 1,
Config: map[string]any{"api_key": "plugin-secret"},
}
require.NoError(t, db.Create(plugin).Error)
// Raw DB should have plaintext config
raw := rawRow(t, db, "config_plugins", plugin.ID)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Contains(t, raw["config_json"], "plugin-secret")
// GORM read should return same plaintext
var found TablePlugin
require.NoError(t, db.First(&found, plugin.ID).Error)
assert.Contains(t, found.ConfigJSON, "plugin-secret")
}
func TestTableVectorStoreConfig_EncryptionDisabled_StoresPlaintext(t *testing.T) {
disableEncryption(t)
db := setupTestDB(t)
config := `{"host":"redis.example.com","password":"redis-secret"}`
vs := &TableVectorStoreConfig{
Enabled: true,
Type: "redis",
Config: &config,
}
require.NoError(t, db.Create(vs).Error)
// Raw DB should have plaintext config
raw := rawRow(t, db, "config_vector_store", vs.ID)
assert.Equal(t, "plain_text", raw["encryption_status"])
assert.Contains(t, raw["config"], "redis-secret")
// GORM read should return same plaintext
var found TableVectorStoreConfig
require.NoError(t, db.First(&found, vs.ID).Error)
require.NotNil(t, found.Config)
assert.Contains(t, *found.Config, "redis-secret")
}
// ============================================================================
// Multi-backend helpers — run the same tests on SQLite and Postgres
// ============================================================================
// postgresDSN matches the postgres service in tests/docker-compose.yml and
// framework/docker-compose.yml.
const postgresDSN = "host=localhost user=bifrost password=bifrost_password dbname=bifrost port=5432 sslmode=disable"
// namedDB pairs a backend name with its GORM connection for use in subtests.
type namedDB struct {
name string
db *gorm.DB
}
// createTestProvider inserts a minimal TableProvider and returns its auto-generated ID.
// Postgres enforces the config_keys.provider_id FK; SQLite does not. Using this helper
// ensures both backends stay consistent.
func createTestProvider(t *testing.T, db *gorm.DB, name string) uint {
t.Helper()
provider := &TableProvider{Name: name}
require.NoError(t, db.Create(provider).Error)
return provider.ID
}
// trySetupPostgresDB attempts to connect to Postgres and auto-migrate all tables.
// Returns nil (without skipping the test) if Postgres is unavailable, so callers
// can decide whether to skip or simply omit the Postgres subtest.
func trySetupPostgresDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(postgres.Open(postgresDSN), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil
}
// Verify the connection is actually live before proceeding.
sqlDB, err := db.DB()
if err != nil {
return nil
}
if err := sqlDB.Ping(); err != nil {
return nil
}
// Order matters on Postgres: referenced tables must be created before
// tables that FK-reference them (SQLite defers FK checks; Postgres does not).
// TableProvider must precede TableKey (config_keys.provider_id → config_providers).
// TableOauthConfig must precede TableMCPClient (config_mcp_clients.oauth_config_id → oauth_configs).
err = db.AutoMigrate(
&TableProvider{},
&TableOauthConfig{},
&TableOauthToken{},
&TableKey{},
&TableMCPClient{},
&TablePlugin{},
&TableVirtualKey{},
&SessionsTable{},
&TableVectorStoreConfig{},
)
if err != nil {
return nil
}
// Clean up all rows after the test so each test starts with an empty DB.
t.Cleanup(func() {
db.Exec("DELETE FROM config_keys")
db.Exec("DELETE FROM config_providers")
db.Exec("DELETE FROM config_mcp_clients")
db.Exec("DELETE FROM config_plugins")
db.Exec("DELETE FROM governance_virtual_keys")
db.Exec("DELETE FROM sessions")
db.Exec("DELETE FROM oauth_configs")
db.Exec("DELETE FROM oauth_tokens")
db.Exec("DELETE FROM config_vector_store")
})
return db
}
// setupTestPostgresDB connects to Postgres and skips the test if unavailable.
func setupTestPostgresDB(t *testing.T) *gorm.DB {
t.Helper()
db := trySetupPostgresDB(t)
if db == nil {
t.Skip("Postgres unavailable — skipping Postgres-specific test (start tests/docker-compose.yml to run it)")
}
return db
}
// forEachDB returns a SQLite backend always, and a Postgres backend when available.
// Tests use t.Run(ndb.name, ...) to get per-backend subtest names and failure isolation.
func forEachDB(t *testing.T) []namedDB {
t.Helper()
dbs := []namedDB{{"sqlite", setupTestDB(t)}}
if pgDB := trySetupPostgresDB(t); pgDB != nil {
dbs = append(dbs, namedDB{"postgres", pgDB})
}
return dbs
}
// ============================================================================
// Encrypted column width regression tests (SQLite + Postgres via forEachDB)
//
// These tests guard against the SQLSTATE 22001 overflow that occurred when
// AES-256-GCM encrypted values were stored in varchar columns that were too
// narrow to hold the base64-encoded ciphertext. All three columns are now
// text type which has no length limit.
// ============================================================================
func TestEncryptedColumns_AzureAPIVersion_FitsAfterWidening(t *testing.T) {
// "2024-02-01-preview" is 18 chars — encrypts to ~62 chars.
// This overflowed the old varchar(50) column.
apiVersion := schemas.NewEnvVar("2024-02-01-preview")
for _, ndb := range forEachDB(t) {
ndb := ndb
t.Run(ndb.name, func(t *testing.T) {
providerID := createTestProvider(t, ndb.db, "azure-av-provider-"+ndb.name)
key := &TableKey{
Name: "azure-apiversion-width-" + ndb.name,
ProviderID: providerID,
Provider: "azure",
KeyID: "az-av-width-" + ndb.name,
Value: *schemas.NewEnvVar("sk-azure-key"),
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: *schemas.NewEnvVar("https://my-azure.openai.azure.com"),
APIVersion: apiVersion,
},
}
require.NoError(t, ndb.db.Create(key).Error,
"expected no overflow error — azure_api_version should be text")
var found TableKey
require.NoError(t, ndb.db.First(&found, key.ID).Error)
require.NotNil(t, found.AzureKeyConfig)
require.NotNil(t, found.AzureKeyConfig.APIVersion)
assert.Equal(t, "2024-02-01-preview", found.AzureKeyConfig.APIVersion.GetValue())
})
}
}
func TestEncryptedColumns_VertexRegion_FitsAfterWidening(t *testing.T) {
// "northamerica-northeast1" is 23 chars — encrypts to ~68 chars.
// Longer regions would have overflowed the old varchar(100).
for _, ndb := range forEachDB(t) {
ndb := ndb
t.Run(ndb.name, func(t *testing.T) {
providerID := createTestProvider(t, ndb.db, "vertex-region-provider-"+ndb.name)
key := &TableKey{
Name: "vertex-region-width-" + ndb.name,
ProviderID: providerID,
Provider: "vertex",
KeyID: "vx-region-width-" + ndb.name,
Value: *schemas.NewEnvVar("vertex-api-key"),
VertexKeyConfig: &schemas.VertexKeyConfig{
ProjectID: *schemas.NewEnvVar("my-project"),
Region: *schemas.NewEnvVar("northamerica-northeast1"),
},
}
require.NoError(t, ndb.db.Create(key).Error,
"expected no overflow error — vertex_region should be text")
var found TableKey
require.NoError(t, ndb.db.First(&found, key.ID).Error)
require.NotNil(t, found.VertexKeyConfig)
assert.Equal(t, "northamerica-northeast1", found.VertexKeyConfig.Region.GetValue())
})
}
}
func TestEncryptedColumns_BedrockRegion_FitsAfterWidening(t *testing.T) {
// "ap-southeast-2" is 14 chars — encrypts to ~58 chars.
// Previously borderline against varchar(100).
region := schemas.NewEnvVar("ap-southeast-2")
for _, ndb := range forEachDB(t) {
ndb := ndb
t.Run(ndb.name, func(t *testing.T) {
providerID := createTestProvider(t, ndb.db, "bedrock-region-provider-"+ndb.name)
key := &TableKey{
Name: "bedrock-region-width-" + ndb.name,
ProviderID: providerID,
Provider: "bedrock",
KeyID: "bk-region-width-" + ndb.name,
Value: *schemas.NewEnvVar("bedrock-val"),
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: *schemas.NewEnvVar("AKIAIOSFODNN7EXAMPLE"),
SecretKey: *schemas.NewEnvVar("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
Region: region,
},
}
require.NoError(t, ndb.db.Create(key).Error,
"expected no overflow error — bedrock_region should be text")
var found TableKey
require.NoError(t, ndb.db.First(&found, key.ID).Error)
require.NotNil(t, found.BedrockKeyConfig)
require.NotNil(t, found.BedrockKeyConfig.Region)
assert.Equal(t, "ap-southeast-2", found.BedrockKeyConfig.Region.GetValue())
})
}
}
// ============================================================================
// Postgres-only: verify actual column types via information_schema
// ============================================================================
func TestPostgres_EncryptedColumns_AreText(t *testing.T) {
db := setupTestPostgresDB(t) // skips if Postgres is unavailable
type colInfo struct {
DataType string `gorm:"column:data_type"`
}
columns := []string{"azure_api_version", "vertex_region", "bedrock_region"}
for _, col := range columns {
col := col
t.Run(col, func(t *testing.T) {
var info colInfo
err := db.Raw(`
SELECT data_type
FROM information_schema.columns
WHERE table_name = 'config_keys' AND column_name = ?`, col).
Scan(&info).Error
require.NoError(t, err)
assert.Equal(t, "text", info.DataType,
"column %s should be text", col)
})
}
}
// ============================================================================
// Env-var-reference persistence regression tests
//
// These tests guard against a class of bugs where BeforeSave used GetValue() != ""
// to decide whether to persist a config field. When a field was set via env var
// reference (e.g. "env.AZURE_ENDPOINT") and the env var was not set on the server,
// GetValue() would return "" and the field — including the env reference — would be
// dropped from the DB. On the next reload the entire provider-specific config block
// could vanish.
//
// IsSet() (which checks both Val and EnvVar) is the correct check, and AfterFind
// reconstruction must consider all fields in the config, not just one.
// ============================================================================
// TestTableKey_VertexUnresolvedEnvVar_RoundTrip verifies that a Vertex key configured
// with an env var reference for ProjectID survives the BeforeSave/AfterFind round-trip
// even when the env var is NOT set on the server (so the resolved Val is empty).
func TestTableKey_VertexUnresolvedEnvVar_RoundTrip(t *testing.T) {
// Make sure the env var is NOT set so the resolved Val is empty.
require.NoError(t, os.Unsetenv("FAKE_VERTEX_PROJECT_ID_FOR_TEST"))
db := setupTestDB(t)
key := &TableKey{
Name: "vertex-unresolved-env",
ProviderID: 1,
Provider: "vertex",
KeyID: "vertex-env-uuid-1",
Value: *schemas.NewEnvVar(""),
VertexKeyConfig: &schemas.VertexKeyConfig{
ProjectID: schemas.EnvVar{
Val: "",
EnvVar: "env.FAKE_VERTEX_PROJECT_ID_FOR_TEST",
FromEnv: true,
},
Region: *schemas.NewEnvVar("us-central1"),
},
}
require.NoError(t, db.Create(key).Error)
// Read back through GORM (triggers AfterFind reconstruction).
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
// VertexKeyConfig must NOT be wiped — this was the original bug.
require.NotNil(t, found.VertexKeyConfig, "VertexKeyConfig was wiped on reload")
assert.Equal(t, "env.FAKE_VERTEX_PROJECT_ID_FOR_TEST", found.VertexKeyConfig.ProjectID.EnvVar,
"env var reference for ProjectID lost on round-trip")
assert.True(t, found.VertexKeyConfig.ProjectID.FromEnv,
"FromEnv flag for ProjectID lost on round-trip")
assert.Equal(t, "us-central1", found.VertexKeyConfig.Region.GetValue(),
"Plain Region value should survive round-trip unchanged")
}
// TestTableKey_AzureUnresolvedEnvVar_RoundTrip verifies the same property for Azure.
// This also exercises the broadened AfterFind reconstruction condition: when only the
// endpoint is set (and unresolved), the entire AzureKeyConfig must still be reconstructed.
func TestTableKey_AzureUnresolvedEnvVar_RoundTrip(t *testing.T) {
require.NoError(t, os.Unsetenv("FAKE_AZURE_ENDPOINT_FOR_TEST"))
db := setupTestDB(t)
key := &TableKey{
Name: "azure-unresolved-env",
ProviderID: 1,
Provider: "azure",
KeyID: "azure-env-uuid-1",
Value: *schemas.NewEnvVar(""),
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: schemas.EnvVar{
Val: "",
EnvVar: "env.FAKE_AZURE_ENDPOINT_FOR_TEST",
FromEnv: true,
},
},
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.AzureKeyConfig, "AzureKeyConfig was wiped on reload")
assert.Equal(t, "env.FAKE_AZURE_ENDPOINT_FOR_TEST", found.AzureKeyConfig.Endpoint.EnvVar,
"env var reference for Endpoint lost on round-trip")
assert.True(t, found.AzureKeyConfig.Endpoint.FromEnv,
"FromEnv flag for Endpoint lost on round-trip")
}
// TestTableKey_AzureOnlyApiVersion_AfterFindReconstructs verifies that AzureKeyConfig
// is reconstructed from the DB even when ONLY a non-endpoint Azure field is set.
// Before the fix, AfterFind only checked AzureEndpoint != nil and would silently drop
// the entire Azure config when only api_version (or any other Azure field) was present.
func TestTableKey_AzureOnlyApiVersion_AfterFindReconstructs(t *testing.T) {
db := setupTestDB(t)
apiVersion := schemas.NewEnvVar("2024-10-21")
key := &TableKey{
Name: "azure-only-apiversion",
ProviderID: 1,
Provider: "azure",
KeyID: "azure-apiver-uuid-1",
Value: *schemas.NewEnvVar(""),
AzureKeyConfig: &schemas.AzureKeyConfig{
// No endpoint, no client id — only api_version.
APIVersion: apiVersion,
},
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.AzureKeyConfig,
"AzureKeyConfig should be reconstructed when only api_version is present")
require.NotNil(t, found.AzureKeyConfig.APIVersion)
assert.Equal(t, "2024-10-21", found.AzureKeyConfig.APIVersion.GetValue())
}
// TestTableKey_BedrockUnresolvedEnvVar_RoundTrip verifies the same property for
// Bedrock explicit credentials.
func TestTableKey_BedrockUnresolvedEnvVar_RoundTrip(t *testing.T) {
require.NoError(t, os.Unsetenv("FAKE_AWS_ACCESS_KEY_FOR_TEST"))
require.NoError(t, os.Unsetenv("FAKE_AWS_SECRET_KEY_FOR_TEST"))
db := setupTestDB(t)
key := &TableKey{
Name: "bedrock-unresolved-env",
ProviderID: 1,
Provider: "bedrock",
KeyID: "bedrock-env-uuid-1",
Value: *schemas.NewEnvVar(""),
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: schemas.EnvVar{
Val: "",
EnvVar: "env.FAKE_AWS_ACCESS_KEY_FOR_TEST",
FromEnv: true,
},
SecretKey: schemas.EnvVar{
Val: "",
EnvVar: "env.FAKE_AWS_SECRET_KEY_FOR_TEST",
FromEnv: true,
},
Region: schemas.NewEnvVar("us-west-2"),
},
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.BedrockKeyConfig, "BedrockKeyConfig was wiped on reload")
assert.Equal(t, "env.FAKE_AWS_ACCESS_KEY_FOR_TEST", found.BedrockKeyConfig.AccessKey.EnvVar,
"env var reference for AccessKey lost on round-trip")
assert.Equal(t, "env.FAKE_AWS_SECRET_KEY_FOR_TEST", found.BedrockKeyConfig.SecretKey.EnvVar,
"env var reference for SecretKey lost on round-trip")
require.NotNil(t, found.BedrockKeyConfig.Region)
assert.Equal(t, "us-west-2", found.BedrockKeyConfig.Region.GetValue())
}
// TestTableKey_OllamaUnresolvedEnvVar_RoundTrip and TestTableKey_SGLUnresolvedEnvVar_RoundTrip
// verify the same property for the recently-added providers, which also use env-aware persistence.
func TestTableKey_OllamaUnresolvedEnvVar_RoundTrip(t *testing.T) {
require.NoError(t, os.Unsetenv("FAKE_OLLAMA_URL_FOR_TEST"))
db := setupTestDB(t)
key := &TableKey{
Name: "ollama-unresolved-env",
ProviderID: 1,
Provider: "ollama",
KeyID: "ollama-env-uuid-1",
Value: *schemas.NewEnvVar(""),
OllamaKeyConfig: &schemas.OllamaKeyConfig{
URL: schemas.EnvVar{
Val: "",
EnvVar: "env.FAKE_OLLAMA_URL_FOR_TEST",
FromEnv: true,
},
},
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.OllamaKeyConfig, "OllamaKeyConfig was wiped on reload")
assert.Equal(t, "env.FAKE_OLLAMA_URL_FOR_TEST", found.OllamaKeyConfig.URL.EnvVar)
assert.True(t, found.OllamaKeyConfig.URL.FromEnv)
}
func TestTableKey_SGLUnresolvedEnvVar_RoundTrip(t *testing.T) {
require.NoError(t, os.Unsetenv("FAKE_SGL_URL_FOR_TEST"))
db := setupTestDB(t)
key := &TableKey{
Name: "sgl-unresolved-env",
ProviderID: 1,
Provider: "sgl",
KeyID: "sgl-env-uuid-1",
Value: *schemas.NewEnvVar(""),
SGLKeyConfig: &schemas.SGLKeyConfig{
URL: schemas.EnvVar{
Val: "",
EnvVar: "env.FAKE_SGL_URL_FOR_TEST",
FromEnv: true,
},
},
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.SGLKeyConfig, "SGLKeyConfig was wiped on reload")
assert.Equal(t, "env.FAKE_SGL_URL_FOR_TEST", found.SGLKeyConfig.URL.EnvVar)
assert.True(t, found.SGLKeyConfig.URL.FromEnv)
}
// TestTableKey_VertexPlainValue_RoundTrip is a sanity check ensuring that plain
// (non-env-backed) values still round-trip cleanly through the persistence layer
// after the IsSet() change. Both branches of the BeforeSave check matter.
func TestTableKey_VertexPlainValue_RoundTrip(t *testing.T) {
db := setupTestDB(t)
key := &TableKey{
Name: "vertex-plain",
ProviderID: 1,
Provider: "vertex",
KeyID: "vertex-plain-uuid-1",
Value: *schemas.NewEnvVar(""),
VertexKeyConfig: &schemas.VertexKeyConfig{
ProjectID: *schemas.NewEnvVar("my-gcp-project"),
Region: *schemas.NewEnvVar("us-central1"),
},
}
require.NoError(t, db.Create(key).Error)
var found TableKey
require.NoError(t, db.First(&found, key.ID).Error)
require.NotNil(t, found.VertexKeyConfig)
assert.Equal(t, "my-gcp-project", found.VertexKeyConfig.ProjectID.GetValue())
assert.False(t, found.VertexKeyConfig.ProjectID.FromEnv)
assert.Equal(t, "us-central1", found.VertexKeyConfig.Region.GetValue())
}