From ebaf7d7c2d197da2d1489b6b4397c8ffd5bde316 Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Sun, 5 Apr 2026 00:03:55 +0300 Subject: [PATCH] test(01-02): add failing tests for provider schema validation and registry --- cmd/root.go | 8 +++++ go.mod | 13 +++++++ go.sum | 10 ++++++ main.go | 7 ++++ pkg/engine/scanner_test.go | 23 ++++++++++++ pkg/providers/registry_test.go | 58 ++++++++++++++++++++++++++++++ pkg/storage/db_test.go | 23 ++++++++++++ testdata/samples/anthropic_key.txt | 2 ++ testdata/samples/multiple_keys.txt | 3 ++ testdata/samples/no_keys.txt | 3 ++ testdata/samples/openai_key.txt | 2 ++ 11 files changed, 152 insertions(+) create mode 100644 cmd/root.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/engine/scanner_test.go create mode 100644 pkg/providers/registry_test.go create mode 100644 pkg/storage/db_test.go create mode 100644 testdata/samples/anthropic_key.txt create mode 100644 testdata/samples/multiple_keys.txt create mode 100644 testdata/samples/no_keys.txt create mode 100644 testdata/samples/openai_key.txt diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..0a22153 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,8 @@ +package cmd + +import "os" + +// Execute is a stub. The real command tree is built in Plan 05. +func Execute() { + _ = os.Args +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e3e9ef2 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/salvacybersec/keyhunter + +go 1.26.1 + +require ( + github.com/stretchr/testify v1.11.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c4c1710 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..436cb62 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/salvacybersec/keyhunter/cmd" + +func main() { + cmd.Execute() +} diff --git a/pkg/engine/scanner_test.go b/pkg/engine/scanner_test.go new file mode 100644 index 0000000..c057fd7 --- /dev/null +++ b/pkg/engine/scanner_test.go @@ -0,0 +1,23 @@ +package engine_test + +import ( + "testing" +) + +// TestShannonEntropy verifies the entropy function returns expected values. +// Stub: will be implemented when entropy.go exists (Plan 04). +func TestShannonEntropy(t *testing.T) { + t.Skip("stub — implement after entropy.go exists") +} + +// TestKeywordPreFilter verifies Aho-Corasick pre-filter rejects files without keywords. +// Stub: will be implemented when filter.go exists (Plan 04). +func TestKeywordPreFilter(t *testing.T) { + t.Skip("stub — implement after filter.go exists") +} + +// TestScannerPipeline verifies end-to-end scan of testdata returns expected findings. +// Stub: will be implemented when engine.go exists (Plan 04). +func TestScannerPipeline(t *testing.T) { + t.Skip("stub — implement after engine.go exists") +} diff --git a/pkg/providers/registry_test.go b/pkg/providers/registry_test.go new file mode 100644 index 0000000..96557f3 --- /dev/null +++ b/pkg/providers/registry_test.go @@ -0,0 +1,58 @@ +package providers_test + +import ( + "testing" + + "github.com/salvacybersec/keyhunter/pkg/providers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestRegistryLoad(t *testing.T) { + reg, err := providers.NewRegistry() + require.NoError(t, err) + assert.GreaterOrEqual(t, len(reg.List()), 3, "expected at least 3 providers") +} + +func TestRegistryGet(t *testing.T) { + reg, err := providers.NewRegistry() + require.NoError(t, err) + + p, ok := reg.Get("openai") + assert.True(t, ok) + assert.Equal(t, "openai", p.Name) + assert.Equal(t, 1, p.Tier) + + _, notOk := reg.Get("nonexistent-provider") + assert.False(t, notOk) +} + +func TestRegistryStats(t *testing.T) { + reg, err := providers.NewRegistry() + require.NoError(t, err) + + stats := reg.Stats() + assert.GreaterOrEqual(t, stats.Total, 3) + assert.GreaterOrEqual(t, stats.ByTier[1], 2) +} + +func TestAhoCorasickBuild(t *testing.T) { + reg, err := providers.NewRegistry() + require.NoError(t, err) + + ac := reg.AC() + matches := ac.FindAll("export OPENAI_API_KEY=sk-proj-abc") + assert.NotEmpty(t, matches) + + noMatches := ac.FindAll("hello world nothing here") + assert.Empty(t, noMatches) +} + +func TestProviderSchemaValidation(t *testing.T) { + invalid := []byte("format_version: 0\nname: invalid\nlast_verified: \"\"\n") + var p providers.Provider + err := yaml.Unmarshal(invalid, &p) + assert.Error(t, err) + assert.Contains(t, err.Error(), "format_version") +} diff --git a/pkg/storage/db_test.go b/pkg/storage/db_test.go new file mode 100644 index 0000000..88259dd --- /dev/null +++ b/pkg/storage/db_test.go @@ -0,0 +1,23 @@ +package storage_test + +import ( + "testing" +) + +// TestDBOpen verifies SQLite database opens and creates schema. +// Stub: will be implemented when db.go exists (Plan 03). +func TestDBOpen(t *testing.T) { + t.Skip("stub — implement after db.go exists") +} + +// TestEncryptDecryptRoundtrip verifies AES-256-GCM encrypt/decrypt roundtrip. +// Stub: will be implemented when encrypt.go exists (Plan 03). +func TestEncryptDecryptRoundtrip(t *testing.T) { + t.Skip("stub — implement after encrypt.go exists") +} + +// TestArgon2KeyDerivation verifies Argon2id produces 32-byte key deterministically. +// Stub: will be implemented when crypto.go exists (Plan 03). +func TestArgon2KeyDerivation(t *testing.T) { + t.Skip("stub — implement after crypto.go exists") +} diff --git a/testdata/samples/anthropic_key.txt b/testdata/samples/anthropic_key.txt new file mode 100644 index 0000000..6b0f1d5 --- /dev/null +++ b/testdata/samples/anthropic_key.txt @@ -0,0 +1,2 @@ +# Test file: synthetic Anthropic key pattern +export ANTHROPIC_API_KEY="sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy01234567890-ABCDE" diff --git a/testdata/samples/multiple_keys.txt b/testdata/samples/multiple_keys.txt new file mode 100644 index 0000000..63b1388 --- /dev/null +++ b/testdata/samples/multiple_keys.txt @@ -0,0 +1,3 @@ +# Multiple providers in one file +OPENAI_API_KEY=sk-proj-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr5678 +ANTHROPIC_API_KEY=sk-ant-api03-XYZabcdefghijklmnopqrstuvwxyz01234567890ABCDEFGH-XYZAB diff --git a/testdata/samples/no_keys.txt b/testdata/samples/no_keys.txt new file mode 100644 index 0000000..0337ca9 --- /dev/null +++ b/testdata/samples/no_keys.txt @@ -0,0 +1,3 @@ +# This file contains no API keys +# Used to verify false-positive rate is zero for clean files +Hello world diff --git a/testdata/samples/openai_key.txt b/testdata/samples/openai_key.txt new file mode 100644 index 0000000..0d4a240 --- /dev/null +++ b/testdata/samples/openai_key.txt @@ -0,0 +1,2 @@ +# Test file: synthetic OpenAI key pattern +OPENAI_API_KEY=sk-proj-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr1234