Files
keyhunter/pkg/storage/queries_test.go
salvacybersec 9dbb0b87d4 feat(07-04): wire keyhunter import command with dedup and DB persist
- Replace import stub with cmd/import.go dispatching to pkg/importer
  (trufflehog, gitleaks, gitleaks-csv) via --format flag
- Reuse openDBWithKey helper so encryption + path resolution match scan/keys
- engineToStorage converts engine.Finding -> storage.Finding (Source -> SourcePath)
- Add pkg/storage.FindingExistsByKey for idempotent cross-import dedup
  keyed on (provider, masked key, source path, line number)
- cmd/import_test.go: selector table, field conversion, end-to-end trufflehog
  import with re-run duplicate assertion, unknown-format + missing-file errors
- pkg/storage queries_test: FindingExistsByKey hit and four miss cases

Delivers IMP-01/02/03 end-to-end.
2026-04-05 23:59:39 +03:00

189 lines
5.1 KiB
Go

package storage_test
import (
"database/sql"
"errors"
"testing"
"github.com/salvacybersec/keyhunter/pkg/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// seedQueryFindings inserts three findings across two providers with mixed
// verified flags. Returns the DB, encryption key, and the saved IDs in
// insertion order: [openai-live, openai-dead, anthropic-live].
func seedQueryFindings(t *testing.T) (*storage.DB, []byte, []int64) {
t.Helper()
db, err := storage.Open(":memory:")
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
encKey := makeTestKey()
seeds := []storage.Finding{
{
ProviderName: "openai",
KeyValue: "sk-proj-openai-live-1234567890",
Confidence: "high",
SourcePath: "/tmp/a.env",
SourceType: "file",
LineNumber: 1,
Verified: true,
VerifyStatus: "live",
VerifyHTTPCode: 200,
VerifyMetadata: map[string]string{"org": "acme"},
},
{
ProviderName: "openai",
KeyValue: "sk-proj-openai-dead-abcdefghij",
Confidence: "medium",
SourcePath: "/tmp/b.env",
SourceType: "file",
LineNumber: 2,
Verified: false,
},
{
ProviderName: "anthropic",
KeyValue: "sk-ant-api03-livekey-abcdefghij",
Confidence: "high",
SourcePath: "/tmp/c.yaml",
SourceType: "file",
LineNumber: 3,
Verified: true,
VerifyStatus: "live",
VerifyHTTPCode: 200,
},
}
ids := make([]int64, 0, len(seeds))
for _, f := range seeds {
id, err := db.SaveFinding(f, encKey)
require.NoError(t, err)
ids = append(ids, id)
}
return db, encKey, ids
}
func TestListFindingsFiltered_ByProvider(t *testing.T) {
db, encKey, _ := seedQueryFindings(t)
out, err := db.ListFindingsFiltered(encKey, storage.Filters{Provider: "openai"})
require.NoError(t, err)
require.Len(t, out, 2)
for _, f := range out {
assert.Equal(t, "openai", f.ProviderName)
}
}
func TestListFindingsFiltered_Verified(t *testing.T) {
db, encKey, _ := seedQueryFindings(t)
verifiedTrue := true
out, err := db.ListFindingsFiltered(encKey, storage.Filters{Verified: &verifiedTrue})
require.NoError(t, err)
require.Len(t, out, 2)
for _, f := range out {
assert.True(t, f.Verified, "expected only verified findings")
}
verifiedFalse := false
out, err = db.ListFindingsFiltered(encKey, storage.Filters{Verified: &verifiedFalse})
require.NoError(t, err)
require.Len(t, out, 1)
assert.False(t, out[0].Verified)
}
func TestListFindingsFiltered_Pagination(t *testing.T) {
db, encKey, _ := seedQueryFindings(t)
// Unpaginated baseline — should return all 3.
all, err := db.ListFindingsFiltered(encKey, storage.Filters{})
require.NoError(t, err)
require.Len(t, all, 3)
// Limit=1 Offset=1 returns the second row from the baseline order.
page, err := db.ListFindingsFiltered(encKey, storage.Filters{Limit: 1, Offset: 1})
require.NoError(t, err)
require.Len(t, page, 1)
assert.Equal(t, all[1].ID, page[0].ID)
}
func TestGetFinding_Hit(t *testing.T) {
db, encKey, ids := seedQueryFindings(t)
f, err := db.GetFinding(ids[0], encKey)
require.NoError(t, err)
require.NotNil(t, f)
assert.Equal(t, "openai", f.ProviderName)
assert.Equal(t, "sk-proj-openai-live-1234567890", f.KeyValue)
assert.True(t, f.Verified)
}
func TestGetFinding_Miss(t *testing.T) {
db, encKey, _ := seedQueryFindings(t)
f, err := db.GetFinding(9999, encKey)
assert.Nil(t, f)
assert.True(t, errors.Is(err, sql.ErrNoRows), "expected sql.ErrNoRows, got %v", err)
}
func TestDeleteFinding_Hit(t *testing.T) {
db, encKey, ids := seedQueryFindings(t)
n, err := db.DeleteFinding(ids[1])
require.NoError(t, err)
assert.Equal(t, int64(1), n)
f, err := db.GetFinding(ids[1], encKey)
assert.Nil(t, f)
assert.True(t, errors.Is(err, sql.ErrNoRows))
}
func TestDeleteFinding_Miss(t *testing.T) {
db, _, _ := seedQueryFindings(t)
n, err := db.DeleteFinding(9999)
require.NoError(t, err)
assert.Equal(t, int64(0), n)
}
func TestFindingExistsByKey(t *testing.T) {
db, encKey, _ := seedQueryFindings(t)
// Insert a finding with a deterministic masked key we can query against.
masked := "sk-exact...1234"
_, err := db.SaveFinding(storage.Finding{
ProviderName: "openai",
KeyValue: "sk-exact-key-value-1234",
KeyMasked: masked,
Confidence: "high",
SourcePath: "/tmp/exact.env",
SourceType: "import:trufflehog",
LineNumber: 42,
}, encKey)
require.NoError(t, err)
// Exact tuple hits.
exists, err := db.FindingExistsByKey("openai", masked, "/tmp/exact.env", 42)
require.NoError(t, err)
assert.True(t, exists, "exact tuple should be found")
// Any differing field misses.
miss1, err := db.FindingExistsByKey("anthropic", masked, "/tmp/exact.env", 42)
require.NoError(t, err)
assert.False(t, miss1)
miss2, err := db.FindingExistsByKey("openai", "sk-other...9999", "/tmp/exact.env", 42)
require.NoError(t, err)
assert.False(t, miss2)
miss3, err := db.FindingExistsByKey("openai", masked, "/tmp/other.env", 42)
require.NoError(t, err)
assert.False(t, miss3)
miss4, err := db.FindingExistsByKey("openai", masked, "/tmp/exact.env", 7)
require.NoError(t, err)
assert.False(t, miss4)
}