package storage_test import ( "bytes" "testing" "github.com/salvacybersec/keyhunter/pkg/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDBOpen(t *testing.T) { db, err := storage.Open(":memory:") require.NoError(t, err) defer db.Close() // Verify schema tables exist rows, err := db.SQL().Query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") require.NoError(t, err) defer rows.Close() var tables []string for rows.Next() { var name string require.NoError(t, rows.Scan(&name)) tables = append(tables, name) } assert.Contains(t, tables, "findings") assert.Contains(t, tables, "scans") assert.Contains(t, tables, "settings") } func TestEncryptDecryptRoundtrip(t *testing.T) { key := make([]byte, 32) // all-zero key for test for i := range key { key[i] = byte(i) } plaintext := []byte("sk-proj-supersecretapikey1234") ciphertext, err := storage.Encrypt(plaintext, key) require.NoError(t, err) assert.Greater(t, len(ciphertext), len(plaintext), "ciphertext should be longer than plaintext") recovered, err := storage.Decrypt(ciphertext, key) require.NoError(t, err) assert.Equal(t, plaintext, recovered) } func TestEncryptNonDeterministic(t *testing.T) { key := make([]byte, 32) plain := []byte("test-key") ct1, err1 := storage.Encrypt(plain, key) ct2, err2 := storage.Encrypt(plain, key) require.NoError(t, err1) require.NoError(t, err2) assert.NotEqual(t, ct1, ct2, "same plaintext encrypted twice should produce different ciphertext") } func TestDecryptWrongKey(t *testing.T) { key1 := make([]byte, 32) key2 := make([]byte, 32) key2[0] = 0xFF ct, err := storage.Encrypt([]byte("secret"), key1) require.NoError(t, err) _, err = storage.Decrypt(ct, key2) assert.Error(t, err, "decryption with wrong key should fail") } func TestArgon2KeyDerivation(t *testing.T) { passphrase := []byte("my-secure-passphrase") salt := []byte("1234567890abcdef") // 16 bytes key1 := storage.DeriveKey(passphrase, salt) key2 := storage.DeriveKey(passphrase, salt) assert.Equal(t, 32, len(key1), "derived key must be 32 bytes") assert.Equal(t, key1, key2, "same passphrase+salt must produce same key") } func TestNewSalt(t *testing.T) { salt1, err1 := storage.NewSalt() salt2, err2 := storage.NewSalt() require.NoError(t, err1) require.NoError(t, err2) assert.Equal(t, 16, len(salt1)) assert.NotEqual(t, salt1, salt2, "two salts should differ") } func TestSaveFindingEncrypted(t *testing.T) { db, err := storage.Open(":memory:") require.NoError(t, err) defer db.Close() // Derive a test key key := storage.DeriveKey([]byte("testpassphrase"), []byte("testsalt1234xxxx")) plainKey := "sk-proj-test1234567890abcdefghijklmnopqr" f := storage.Finding{ ProviderName: "openai", KeyValue: plainKey, Confidence: "high", SourcePath: "/test/file.env", SourceType: "file", LineNumber: 42, } id, err := db.SaveFinding(f, key) require.NoError(t, err) assert.Greater(t, id, int64(0)) findings, err := db.ListFindings(key) require.NoError(t, err) require.Len(t, findings, 1) assert.Equal(t, plainKey, findings[0].KeyValue) assert.Equal(t, "openai", findings[0].ProviderName) // Verify masking assert.Equal(t, "sk-proj-...opqr", findings[0].KeyMasked) // Verify encryption contract: raw BLOB bytes in the database must NOT contain the plaintext key. // This confirms Encrypt() was called before INSERT, not that the key was stored verbatim. var rawBlob []byte require.NoError(t, db.SQL().QueryRow("SELECT key_value FROM findings WHERE id = ?", id).Scan(&rawBlob)) assert.False(t, bytes.Contains(rawBlob, []byte(plainKey)), "raw database BLOB must not contain plaintext key — encryption was not applied") }