feat(08-06): add dorks run/add/delete with injectable executor
- Add run subcommand dispatching via dorks.Runner (github live, other sources wrapped into friendly ErrSourceNotImplemented) - Add add subcommand with source/category validation and embedded ID collision guard - Add delete subcommand that refuses embedded dork ids - Expose newGitHubExecutor as package var for test injection - cmd/dorks_test.go covers list filtering, add persistence + list merge marker, invalid source rejection, embedded collision, embedded delete refusal, custom delete, shodan not-implemented path, GitHub missing-token auth hint, fake executor run, yaml export merge, and info for both origins Completes DORK-03 (list/run/add/export/info/delete) and DORK-04 (--source/--category filtering).
This commit is contained in:
231
cmd/dorks_test.go
Normal file
231
cmd/dorks_test.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/salvacybersec/keyhunter/pkg/dorks"
|
||||||
|
"github.com/salvacybersec/keyhunter/pkg/storage"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupDorksTest isolates viper state, points database.path at a tempdir
|
||||||
|
// DB (auto-migrated by storage.Open), and returns the DB path plus a
|
||||||
|
// t.Cleanup-registered flag reset.
|
||||||
|
func setupDorksTest(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
dbPath := filepath.Join(dir, "dorks.db")
|
||||||
|
|
||||||
|
viper.Reset()
|
||||||
|
viper.Set("database.path", dbPath)
|
||||||
|
resetDorksFlags()
|
||||||
|
|
||||||
|
// Touch the DB once so schema migrations run deterministically even
|
||||||
|
// when the subcommand under test doesn't reach the DB code path.
|
||||||
|
db, err := storage.Open(dbPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
resetDorksFlags()
|
||||||
|
viper.Reset()
|
||||||
|
})
|
||||||
|
return dbPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// execDorks runs rootCmd with the given args and returns captured stdout
|
||||||
|
// (which Cobra wires for both Out and Err when SetOut is used) alongside
|
||||||
|
// the terminal error from Execute.
|
||||||
|
func execDorks(t *testing.T, args ...string) (string, error) {
|
||||||
|
t.Helper()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
rootCmd.SetOut(&buf)
|
||||||
|
rootCmd.SetErr(&buf)
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
return buf.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeExecutor is an in-memory dorks.Executor used to exercise `dorks run`
|
||||||
|
// without touching the real GitHub API.
|
||||||
|
type fakeExecutor struct {
|
||||||
|
source string
|
||||||
|
matches []dorks.Match
|
||||||
|
err error
|
||||||
|
called int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeExecutor) Source() string { return f.source }
|
||||||
|
|
||||||
|
func (f *fakeExecutor) Execute(ctx context.Context, d dorks.Dork, limit int) ([]dorks.Match, error) {
|
||||||
|
f.called++
|
||||||
|
if f.err != nil {
|
||||||
|
return nil, f.err
|
||||||
|
}
|
||||||
|
out := make([]dorks.Match, len(f.matches))
|
||||||
|
for i, m := range f.matches {
|
||||||
|
m.DorkID = d.ID
|
||||||
|
out[i] = m
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksList_FilterBySourceAndCategory(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
out, err := execDorks(t, "dorks", "list", "--source=github", "--category=frontier")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out, "openai-github-envfile")
|
||||||
|
assert.Contains(t, out, "anthropic-github-envfile")
|
||||||
|
// google-ai dorks are frontier github too; shodan must be filtered out.
|
||||||
|
assert.NotContains(t, out, "shodan-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksAdd_PersistsCustomDorkAndShowsInList(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
|
||||||
|
_, err := execDorks(t, "dorks", "add",
|
||||||
|
"--source=github", "--category=frontier",
|
||||||
|
"--id=my-custom-dork", "--name=Custom", "--query=foo extension:env",
|
||||||
|
"--description=test dork")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
out, err := execDorks(t, "dorks", "list", "--source=github")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out, "*my-custom-dork")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksAdd_RejectsInvalidSource(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
_, err := execDorks(t, "dorks", "add",
|
||||||
|
"--source=notreal", "--category=frontier",
|
||||||
|
"--id=x", "--query=foo")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not one of")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksAdd_RejectsEmbeddedIDCollision(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
_, err := execDorks(t, "dorks", "add",
|
||||||
|
"--source=github", "--category=frontier",
|
||||||
|
"--id=openai-github-envfile", "--query=foo")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "collides with an embedded dork")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksDelete_EmbeddedRefused(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
_, err := execDorks(t, "dorks", "delete", "openai-github-envfile")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "embedded dorks cannot be deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksDelete_RemovesCustomDork(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
|
||||||
|
_, err := execDorks(t, "dorks", "add",
|
||||||
|
"--source=github", "--category=frontier",
|
||||||
|
"--id=to-delete", "--query=bar")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
out, err := execDorks(t, "dorks", "delete", "to-delete")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out, "Deleted custom dork")
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
out, err = execDorks(t, "dorks", "list", "--source=github")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotContains(t, out, "*to-delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksRun_ShodanReturnsNotImplemented(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
// Use a shodan dork id if one exists; if none, add one via add first.
|
||||||
|
// Shodan live execution is not wired in Phase 8.
|
||||||
|
_, err := execDorks(t, "dorks", "add",
|
||||||
|
"--source=shodan", "--category=infrastructure",
|
||||||
|
"--id=shodan-test", "--query=foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
_, err = execDorks(t, "dorks", "run", "--source=shodan", "--id=shodan-test")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not yet implemented")
|
||||||
|
assert.Contains(t, err.Error(), "Phase 9-16")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksRun_GitHubMissingTokenReturnsAuthHint(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
// No GITHUB_TOKEN set, no dorks.github.token — executor should yield
|
||||||
|
// ErrMissingAuth wrapped with a setup hint.
|
||||||
|
viper.Set("dorks.github.token", "")
|
||||||
|
_, err := execDorks(t, "dorks", "run",
|
||||||
|
"--source=github", "--id=openai-github-envfile", "--limit=1")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksRun_GitHubWithInjectedFakeExecutor(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
|
||||||
|
fake := &fakeExecutor{
|
||||||
|
source: "github",
|
||||||
|
matches: []dorks.Match{
|
||||||
|
{Source: "github", URL: "https://example.com/x", Path: "repo/x.env", Snippet: "sk-proj-XXXX"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
original := newGitHubExecutor
|
||||||
|
newGitHubExecutor = func() dorks.Executor { return fake }
|
||||||
|
t.Cleanup(func() { newGitHubExecutor = original })
|
||||||
|
|
||||||
|
out, err := execDorks(t, "dorks", "run",
|
||||||
|
"--source=github", "--id=openai-github-envfile", "--limit=1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, fake.called)
|
||||||
|
assert.Contains(t, out, "openai-github-envfile")
|
||||||
|
assert.Contains(t, out, "https://example.com/x")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksExport_YAMLContainsEmbeddedAndCustom(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
|
||||||
|
_, err := execDorks(t, "dorks", "add",
|
||||||
|
"--source=github", "--category=frontier",
|
||||||
|
"--id=export-custom", "--query=baz")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
out, err := execDorks(t, "dorks", "export", "--format=yaml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out, "openai-github-envfile")
|
||||||
|
assert.Contains(t, out, "export-custom")
|
||||||
|
// yaml decoded output should contain `id:` keys.
|
||||||
|
assert.True(t, strings.Contains(out, "id:"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDorksInfo_EmbeddedAndCustom(t *testing.T) {
|
||||||
|
setupDorksTest(t)
|
||||||
|
|
||||||
|
out, err := execDorks(t, "dorks", "info", "openai-github-envfile")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out, "Origin: embedded")
|
||||||
|
assert.Contains(t, out, "Query:")
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
_, err = execDorks(t, "dorks", "add",
|
||||||
|
"--source=github", "--category=frontier",
|
||||||
|
"--id=info-custom", "--query=qux")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resetDorksFlags()
|
||||||
|
out, err = execDorks(t, "dorks", "info", "info-custom")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out, "Origin: custom")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user