* Add AnalysisError type and AnalysisErrorInfo interface Introduce a shared error type that provides structured metadata (analyzer type, operation, service, resource) for analysis failures. This allows the scanner to extract context from errors without depending on concrete types. * Wrap errors in simple API analyzers with AnalysisError Batch A: Airbrake, Anthropic, Asana, DigitalOcean, DockerHub, ElevenLabs, Fastly, Groq, HuggingFace, Mailchimp, Mailgun, Mux, Netlify, Ngrok, Notion, OpenAI, Opsgenie, Posthog, Postman, Sendgrid, Sourcegraph. Wraps credential validation errors with operation "validate_credentials" and AnalyzePermissions errors with operation "analyze_permissions". * Wrap errors in remaining analyzers with AnalysisError (Batches B-E) Batch B (OAuth/multi-credential): airtableoauth, airtablepat, datadog, dropbox, figma, launchdarkly, plaid Batch C (Complex): bitbucket, databricks, github, gitlab, jira, monday, planetscale, shopify, slack, square, stripe, twilio Batch D (Database): mysql, postgres (service: Database) Batch E (PrivateKey): privatekey (service: crypto) * Use Type().String() and constants for NewAnalysisError calls Address PR feedback: replace hardcoded analyzer type strings with a.Type().String() and replace raw operation/service strings with package-level constants (OperationValidateCredentials, OperationAnalyzePermissions, ServiceAPI, ServiceConfig, etc.). * Omit empty resource parenthetical from AnalysisError messages Conditionally include "(resource: ...)" only when non-empty, avoiding cluttered messages like "... (resource: ): ..." that appear for the majority of analyzers that don't set a resource. * Wrap no-data error path in GitHub analyzer with AnalysisError
87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package analyzers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
func TestAnalysisErrorImplementsInterface(t *testing.T) {
|
|
var _ AnalysisErrorInfo = (*AnalysisError)(nil)
|
|
}
|
|
|
|
func TestAnalysisErrorFields(t *testing.T) {
|
|
orig := fmt.Errorf("connection refused")
|
|
e := NewAnalysisError("Postgres", "connect", "Database", "localhost:5432", orig)
|
|
|
|
if e.AnalyzerType() != "Postgres" {
|
|
t.Errorf("AnalyzerType() = %q, want %q", e.AnalyzerType(), "Postgres")
|
|
}
|
|
if e.Operation() != "connect" {
|
|
t.Errorf("Operation() = %q, want %q", e.Operation(), "connect")
|
|
}
|
|
if e.Service() != "Database" {
|
|
t.Errorf("Service() = %q, want %q", e.Service(), "Database")
|
|
}
|
|
if e.Resource() != "localhost:5432" {
|
|
t.Errorf("Resource() = %q, want %q", e.Resource(), "localhost:5432")
|
|
}
|
|
}
|
|
|
|
func TestAnalysisErrorUnwrap(t *testing.T) {
|
|
orig := fmt.Errorf("timeout")
|
|
e := NewAnalysisError("GitHub", "authenticate", "API", "", orig)
|
|
|
|
if !errors.Is(e, orig) {
|
|
t.Error("errors.Is should find the original error")
|
|
}
|
|
}
|
|
|
|
func TestAnalysisErrorAs(t *testing.T) {
|
|
orig := fmt.Errorf("bad key")
|
|
e := NewAnalysisError("Airbrake", "validate_credentials", "config", "", orig)
|
|
|
|
// Wrap it further
|
|
wrapped := fmt.Errorf("analyze failed: %w", e)
|
|
|
|
var ae AnalysisErrorInfo
|
|
if !errors.As(wrapped, &ae) {
|
|
t.Fatal("errors.As should find AnalysisErrorInfo in wrapped error")
|
|
}
|
|
if ae.AnalyzerType() != "Airbrake" {
|
|
t.Errorf("AnalyzerType() = %q, want %q", ae.AnalyzerType(), "Airbrake")
|
|
}
|
|
if ae.Operation() != "validate_credentials" {
|
|
t.Errorf("Operation() = %q, want %q", ae.Operation(), "validate_credentials")
|
|
}
|
|
}
|
|
|
|
func TestAnalysisErrorMessage(t *testing.T) {
|
|
orig := fmt.Errorf("401 unauthorized")
|
|
e := NewAnalysisError("Slack", "authenticate", "API", "workspace-123", orig)
|
|
|
|
expected := "Slack analysis failed: authenticate on API (resource: workspace-123): 401 unauthorized"
|
|
if e.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", e.Error(), expected)
|
|
}
|
|
}
|
|
|
|
func TestAnalysisErrorMessageNilError(t *testing.T) {
|
|
e := NewAnalysisError("MySQL", "connect", "Database", "db.example.com", nil)
|
|
|
|
expected := "MySQL analysis failed: connect on Database (resource: db.example.com)"
|
|
if e.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", e.Error(), expected)
|
|
}
|
|
}
|
|
|
|
func TestAnalysisErrorMessageEmptyResource(t *testing.T) {
|
|
orig := fmt.Errorf("invalid token")
|
|
e := NewAnalysisError("OpenAI", "analyze_permissions", "API", "", orig)
|
|
|
|
expected := "OpenAI analysis failed: analyze_permissions on API: invalid token"
|
|
if e.Error() != expected {
|
|
t.Errorf("Error() = %q, want %q", e.Error(), expected)
|
|
}
|
|
}
|