package web import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" "github.com/salvacybersec/keyhunter/pkg/dorks" "github.com/salvacybersec/keyhunter/pkg/providers" "github.com/salvacybersec/keyhunter/pkg/recon" "github.com/salvacybersec/keyhunter/pkg/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // testServer creates a Server with in-memory DB and minimal registries for testing. func testServer(t *testing.T) (*Server, []byte) { t.Helper() db, err := storage.Open(":memory:") require.NoError(t, err) t.Cleanup(func() { db.Close() }) encKey := []byte("0123456789abcdef0123456789abcdef") // 32-byte test key provReg := providers.NewRegistryFromProviders([]providers.Provider{ {Name: "openai", DisplayName: "OpenAI", Tier: 1, Keywords: []string{"sk-"}}, {Name: "anthropic", DisplayName: "Anthropic", Tier: 1, Keywords: []string{"sk-ant-"}}, }) dorkReg := dorks.NewRegistryFromDorks([]dorks.Dork{ {ID: "gh-openai-1", Name: "OpenAI GitHub", Source: "github", Category: "frontier", Query: "sk-proj- in:file", Description: "Find OpenAI keys on GitHub"}, }) reconEng := recon.NewEngine() s := NewServer(ServerConfig{ DB: db, EncKey: encKey, Providers: provReg, Dorks: dorkReg, ReconEngine: reconEng, }) return s, encKey } // seedFinding inserts a test finding and returns its ID. func seedFinding(t *testing.T, db *storage.DB, encKey []byte, provider string) int64 { t.Helper() id, err := db.SaveFinding(storage.Finding{ ProviderName: provider, KeyValue: "sk-test1234567890abcdefghijklmnop", KeyMasked: "sk-test1...mnop", Confidence: "high", SourcePath: "/tmp/test.py", SourceType: "file", LineNumber: 42, }, encKey) require.NoError(t, err) return id } func TestAPIStats(t *testing.T) { s, encKey := testServer(t) seedFinding(t, s.cfg.DB, encKey, "openai") r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/stats", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "application/json", w.Header().Get("Content-Type")) var body map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body)) assert.Contains(t, body, "totalKeys") assert.Contains(t, body, "totalProviders") assert.Contains(t, body, "reconSources") } func TestAPIListKeys(t *testing.T) { s, encKey := testServer(t) seedFinding(t, s.cfg.DB, encKey, "openai") seedFinding(t, s.cfg.DB, encKey, "anthropic") r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/keys", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var keys []map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &keys)) assert.Len(t, keys, 2) // Keys should be masked (no raw key value exposed) for _, k := range keys { val, ok := k["keyValue"] assert.True(t, ok) assert.Equal(t, "", val, "API must not expose raw key values") } } func TestAPIListKeysFilterByProvider(t *testing.T) { s, encKey := testServer(t) seedFinding(t, s.cfg.DB, encKey, "openai") seedFinding(t, s.cfg.DB, encKey, "anthropic") r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/keys?provider=openai", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var keys []map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &keys)) assert.Len(t, keys, 1) } func TestAPIGetKey(t *testing.T) { s, encKey := testServer(t) id := seedFinding(t, s.cfg.DB, encKey, "openai") r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/keys/"+itoa(id), nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var body map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body)) assert.Equal(t, "openai", body["providerName"]) } func TestAPIGetKeyNotFound(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/keys/99999", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestAPIDeleteKey(t *testing.T) { s, encKey := testServer(t) id := seedFinding(t, s.cfg.DB, encKey, "openai") r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodDelete, "/api/v1/keys/"+itoa(id), nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) } func TestAPIDeleteKeyNotFound(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodDelete, "/api/v1/keys/99999", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestAPIListProviders(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/providers", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var provs []map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &provs)) assert.Len(t, provs, 2) } func TestAPIGetProvider(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/providers/openai", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var body map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body)) assert.Equal(t, "openai", body["name"]) } func TestAPIGetProviderNotFound(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/providers/nonexistent", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestAPIScan(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) body := `{"path":"/tmp/test","verify":false,"workers":2}` req := httptest.NewRequest(http.MethodPost, "/api/v1/scan", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusAccepted, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, "started", resp["status"]) } func TestAPIRecon(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) body := `{"query":"openai","sources":["github"],"stealth":false}` req := httptest.NewRequest(http.MethodPost, "/api/v1/recon", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusAccepted, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, "started", resp["status"]) } func TestAPIListDorks(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/dorks", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var d []map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &d)) assert.Len(t, d, 1) } func TestAPIAddDork(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) body := `{"dorkId":"custom-1","name":"Custom Dork","source":"github","category":"custom","query":"custom query","description":"test"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/dorks", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Contains(t, resp, "id") } func TestAPIGetConfig(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) req := httptest.NewRequest(http.MethodGet, "/api/v1/config", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "application/json", w.Header().Get("Content-Type")) } func TestAPIUpdateConfig(t *testing.T) { s, _ := testServer(t) r := chi.NewRouter() s.mountAPI(r) body := `{"scan.workers":"8"}` req := httptest.NewRequest(http.MethodPut, "/api/v1/config", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) }