Files
trufflehog/pkg/analyzer/analyzers/errors_test.go
John Elliott 761be8877b Add AnalysisError type and wrap all analyzer error paths (#4779)
* 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
2026-04-14 17:18:48 -07:00

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)
}
}