package sources import ( "context" "net/http" "net/http/httptest" "testing" "time" "github.com/salvacybersec/keyhunter/pkg/providers" "github.com/salvacybersec/keyhunter/pkg/recon" ) func azureTestServer() *httptest.Server { mux := http.NewServeMux() // Respond to any request path that contains "testprov-keys" account + "config" container. mux.HandleFunc("/testprov-keys/config", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/xml") _, _ = w.Write([]byte(` .env credentials.json photo.png `)) }) // All other containers return error. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) }) return httptest.NewServer(mux) } func TestAzureBlob_Sweep(t *testing.T) { srv := azureTestServer() defer srv.Close() // BaseURL format: server/{account}/{container}?params // We use a simplified format for tests. src := &AzureBlobScanner{ Registry: cloudTestRegistry(), BaseURL: srv.URL + "/%s/%s", client: NewClient(), } out := make(chan recon.Finding, 64) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := src.Sweep(ctx, "", out); err != nil { t.Fatalf("Sweep error: %v", err) } close(out) var findings []recon.Finding for f := range out { findings = append(findings, f) } // .env and credentials.json match; photo.png does not. // Only the "config" container returns results; others 404. if len(findings) != 2 { t.Fatalf("expected 2 findings, got %d: %+v", len(findings), findings) } for _, f := range findings { if f.SourceType != "recon:azureblob" { t.Errorf("unexpected SourceType: %s", f.SourceType) } if f.Confidence != "medium" { t.Errorf("unexpected Confidence: %s", f.Confidence) } } } func TestAzureBlob_EmptyRegistry(t *testing.T) { src := &AzureBlobScanner{ Registry: providers.NewRegistryFromProviders(nil), Limiters: recon.NewLimiterRegistry(), client: NewClient(), } out := make(chan recon.Finding, 4) if err := src.Sweep(context.Background(), "", out); err != nil { t.Fatalf("Sweep error: %v", err) } close(out) if len(out) != 0 { t.Fatal("expected 0 findings") } } func TestAzureBlob_CtxCancelled(t *testing.T) { srv := azureTestServer() defer srv.Close() src := &AzureBlobScanner{ Registry: cloudTestRegistry(), BaseURL: srv.URL + "/%s/%s", client: NewClient(), } ctx, cancel := context.WithCancel(context.Background()) cancel() out := make(chan recon.Finding, 4) if err := src.Sweep(ctx, "", out); err == nil { t.Fatal("expected ctx error") } } func TestAzureBlob_EnabledAndMeta(t *testing.T) { a := &AzureBlobScanner{} if a.Name() != "azureblob" { t.Fatalf("unexpected name: %s", a.Name()) } if !a.Enabled(recon.Config{}) { t.Fatal("expected Enabled=true") } if a.RespectsRobots() { t.Fatal("expected RespectsRobots=false") } if a.Burst() != 3 { t.Fatal("expected Burst=3") } }