From 9b005e78bba1743b5a79685b38aa4e02ea3dd27e Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Mon, 6 Apr 2026 13:03:27 +0300 Subject: [PATCH] test(13-04): add integration test handlers for all 12 Phase 13 sources (40 total) - Add httptest mux handlers for npm, pypi, crates, rubygems, maven, nuget, goproxy, packagist, dockerhub, k8s, terraform, helm - Register all 12 Phase 13 sources with BaseURL prefix routing - Update expected source types and count assertions from 28 to 40 --- pkg/recon/sources/integration_test.go | 131 ++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 7 deletions(-) diff --git a/pkg/recon/sources/integration_test.go b/pkg/recon/sources/integration_test.go index ab4e1bb..5f07a16 100644 --- a/pkg/recon/sources/integration_test.go +++ b/pkg/recon/sources/integration_test.go @@ -15,10 +15,11 @@ import ( // TestIntegration_AllSources_SweepAll spins up a single multiplexed httptest // server that serves canned fixtures for every Phase 10 code-hosting source, -// Phase 11 search engine / paste site source, Phase 12 IoT scanner, and -// Phase 12 cloud storage source, registers the sources (with BaseURL overrides -// pointing at the test server) onto a fresh recon.Engine, runs SweepAll, and -// asserts at least one Finding was emitted per SourceType across all 28 sources. +// Phase 11 search engine / paste site source, Phase 12 IoT scanner / cloud +// storage source, and Phase 13 package registry / container / IaC source, +// registers the sources (with BaseURL overrides pointing at the test server) +// onto a fresh recon.Engine, runs SweepAll, and asserts at least one Finding +// was emitted per SourceType across all 40 sources. // // RegisterAll cannot be used directly because it wires production URLs; the // test exercises the same code paths by constructing each source identically @@ -239,6 +240,78 @@ func TestIntegration_AllSources_SweepAll(t *testing.T) { `)) }) + // ---- Phase 13: npm /-/v1/search (prefix /npm) ---- + mux.HandleFunc("/npm/-/v1/search", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"objects":[{"package":{"name":"leak-pkg","links":{"npm":"https://npmjs.com/package/leak-pkg"}}}]}`)) + }) + + // ---- Phase 13: pypi /search/ (prefix /pypi) ---- + mux.HandleFunc("/pypi/search/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + _, _ = w.Write([]byte(`leaked-pkg`)) + }) + + // ---- Phase 13: crates /api/v1/crates (prefix /crates) ---- + mux.HandleFunc("/crates/api/v1/crates", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"crates":[{"id":"leaked-crate","name":"leaked-crate","repository":"https://github.com/example/leaked-crate"}]}`)) + }) + + // ---- Phase 13: rubygems /api/v1/search.json (prefix /rubygems) ---- + mux.HandleFunc("/rubygems/api/v1/search.json", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[{"name":"leaked-gem","project_uri":"https://rubygems.org/gems/leaked-gem"}]`)) + }) + + // ---- Phase 13: maven /solrsearch/select (prefix /maven) ---- + mux.HandleFunc("/maven/solrsearch/select", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"response":{"numFound":1,"docs":[{"g":"com.leak","a":"sdk","latestVersion":"1.0"}]}}`)) + }) + + // ---- Phase 13: nuget /query (prefix /nuget) ---- + mux.HandleFunc("/nuget/query", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"data":[{"id":"LeakedPkg","version":"1.0","projectUrl":"https://nuget.org/packages/LeakedPkg"}]}`)) + }) + + // ---- Phase 13: goproxy /search (prefix /goproxy) ---- + mux.HandleFunc("/goproxy/search", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + _, _ = w.Write([]byte(`module`)) + }) + + // ---- Phase 13: packagist /search.json (prefix /packagist) ---- + mux.HandleFunc("/packagist/search.json", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"results":[{"name":"vendor/leaked","url":"https://packagist.org/packages/vendor/leaked"}]}`)) + }) + + // ---- Phase 13: dockerhub /v2/search/repositories/ (prefix /dockerhub) ---- + mux.HandleFunc("/dockerhub/v2/search/repositories/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"results":[{"repo_name":"user/leaked-image","description":"leaked"}]}`)) + }) + + // ---- Phase 13: k8s /api/v1/packages/search (prefix /k8s) ---- + mux.HandleFunc("/k8s/api/v1/packages/search", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"packages":[{"package_id":"pkg-1","name":"leaked-operator","normalized_name":"leaked-operator","repository":{"name":"community","kind":6}}]}`)) + }) + + // ---- Phase 13: terraform /v1/modules (prefix /terraform) ---- + mux.HandleFunc("/terraform/v1/modules", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"modules":[{"id":"hashicorp/leaked/aws","namespace":"hashicorp","name":"leaked","provider":"aws"}]}`)) + }) + + // ---- Phase 13: helm /api/v1/packages/search (prefix /helm) ---- + mux.HandleFunc("/helm/api/v1/packages/search", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"packages":[{"package_id":"chart-1","name":"leaked-chart","normalized_name":"leaked-chart","repository":{"name":"bitnami","kind":0}}]}`)) + }) + srv := httptest.NewServer(mux) defer srv.Close() @@ -447,9 +520,39 @@ func TestIntegration_AllSources_SweepAll(t *testing.T) { client: NewClient(), }) - // Sanity: all 28 sources registered. - if n := len(eng.List()); n != 28 { - t.Fatalf("expected 28 sources on engine, got %d: %v", n, eng.List()) + // --- Phase 13: Package registry sources --- + + // npm + eng.Register(&NpmSource{BaseURL: srv.URL + "/npm", Registry: reg, Limiters: lim, Client: NewClient()}) + // pypi + eng.Register(&PyPISource{BaseURL: srv.URL + "/pypi", Registry: reg, Limiters: lim, Client: NewClient()}) + // crates + eng.Register(&CratesIOSource{BaseURL: srv.URL + "/crates", Registry: reg, Limiters: lim, Client: NewClient()}) + // rubygems + eng.Register(&RubyGemsSource{BaseURL: srv.URL + "/rubygems", Registry: reg, Limiters: lim, Client: NewClient()}) + // maven + eng.Register(&MavenSource{BaseURL: srv.URL + "/maven", Registry: reg, Limiters: lim, Client: NewClient()}) + // nuget + eng.Register(&NuGetSource{BaseURL: srv.URL + "/nuget", Registry: reg, Limiters: lim, Client: NewClient()}) + // goproxy + eng.Register(&GoProxySource{BaseURL: srv.URL + "/goproxy", Registry: reg, Limiters: lim, Client: NewClient()}) + // packagist + eng.Register(&PackagistSource{BaseURL: srv.URL + "/packagist", Registry: reg, Limiters: lim, Client: NewClient()}) + + // --- Phase 13: Container & IaC sources --- + + // dockerhub + eng.Register(&DockerHubSource{BaseURL: srv.URL + "/dockerhub", Registry: reg, Limiters: lim, Client: NewClient()}) + // k8s + eng.Register(&KubernetesSource{BaseURL: srv.URL + "/k8s", Registry: reg, Limiters: lim, Client: NewClient()}) + // terraform + eng.Register(&TerraformSource{BaseURL: srv.URL + "/terraform", Registry: reg, Limiters: lim, Client: NewClient()}) + // helm + eng.Register(&HelmSource{BaseURL: srv.URL + "/helm", Registry: reg, Limiters: lim, Client: NewClient()}) + + // Sanity: all 40 sources registered. + if n := len(eng.List()); n != 40 { + t.Fatalf("expected 40 sources on engine, got %d: %v", n, eng.List()) } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) @@ -499,6 +602,20 @@ func TestIntegration_AllSources_SweepAll(t *testing.T) { "recon:gcs", "recon:azureblob", "recon:spaces", + // Phase 13: Package registries + "recon:npm", + "recon:pypi", + "recon:crates", + "recon:rubygems", + "recon:maven", + "recon:nuget", + "recon:goproxy", + "recon:packagist", + // Phase 13: Container & IaC + "recon:dockerhub", + "recon:k8s", + "recon:terraform", + "recon:helm", } for _, st := range wantTypes { if byType[st] == 0 {