Files
insights-go/error_tracking_test.go
zeekayandHanzo Dev 8ee8950230 Go 1.27.1, and the code it can say more simply
The module declares go 1.27.1 and the adapter image follows. go fix
applied the modernizers and left ptrString and ptrBool with no callers,
so they are gone; omitzero is not applied, since it changes what the
JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:52:31 -07:00

272 lines
6.9 KiB
Go

package insights
import (
"encoding/json"
"reflect"
"testing"
"time"
)
func TestException_Validate(t *testing.T) {
now := time.Now()
tests := map[string]struct {
msg Exception
expectedError error
}{
"valid: minimal with one valid item": {
msg: Exception{
DistinctId: "user-123",
Timestamp: now,
ExceptionList: []ExceptionItem{
{Type: "title", Value: "desc"},
},
},
expectedError: nil,
},
"error: missing DistinctId": {
msg: Exception{
Timestamp: now,
ExceptionList: []ExceptionItem{
{Type: "title", Value: "desc"},
},
},
expectedError: FieldError{
Type: "insights.Exception",
Name: "DistinctId",
Value: "",
},
},
"error: missing ExceptionList": {
msg: Exception{
DistinctId: "user-123",
Timestamp: now,
},
expectedError: FieldError{
Type: "insights.Exception",
Name: "ExceptionList",
Value: []ExceptionItem{},
},
},
"error: item missing Type": {
msg: Exception{
DistinctId: "user-123",
Timestamp: now,
ExceptionList: []ExceptionItem{
{Type: "", Value: "Bar"},
},
},
expectedError: FieldError{
Type: "insights.Exception",
Name: "Type",
Value: "",
},
},
"error: item missing Value": {
msg: Exception{
DistinctId: "user-123",
Timestamp: now,
ExceptionList: []ExceptionItem{
{Type: "Foo", Value: ""},
},
},
expectedError: FieldError{
Type: "insights.Exception",
Name: "Value",
Value: "",
},
},
"valid: full nested structure": {
msg: Exception{
DistinctId: "user-123",
Timestamp: now,
ExceptionList: []ExceptionItem{
{
Type: "MyError",
Value: "something went wrong",
Mechanism: &ExceptionMechanism{
Handled: new(true),
Synthetic: new(false),
},
Stacktrace: &ExceptionStacktrace{
Type: "raw",
Frames: []StackFrame{
{
Filename: "file.go",
LineNo: 42,
Function: "package.Sample",
InApp: true,
Synthetic: false,
Platform: "go",
},
},
},
},
},
},
expectedError: nil,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
err := tc.msg.Validate()
if !reflect.DeepEqual(err, tc.expectedError) {
t.Errorf("expected error:\n%v \ngot:\n%v", tc.expectedError, err)
}
})
}
}
func TestException_APIfy_WithCustomProperties(t *testing.T) {
now := time.Now()
exList := []ExceptionItem{{Type: "Error", Value: "Something went wrong"}}
fingerprintStr := "custom-fingerprint"
tests := map[string]struct {
exception Exception
expectKeys map[string]any
forbiddenValues []string
}{
"basic without custom properties": {
exception: Exception{DistinctId: "user-123", Timestamp: now, ExceptionList: exList},
expectKeys: map[string]any{
"$lib": SDKName,
"$lib_version": getVersion(),
"distinct_id": "user-123",
},
},
"with custom properties and fingerprint": {
exception: Exception{
DistinctId: "user-123",
Timestamp: now,
ExceptionFingerprint: &fingerprintStr,
Properties: NewProperties().Set("environment", "production").Set("retry_count", 3),
ExceptionList: exList,
},
expectKeys: map[string]any{
"$lib": SDKName,
"$lib_version": getVersion(),
"distinct_id": "user-123",
"$exception_fingerprint": "custom-fingerprint",
"environment": "production",
"retry_count": float64(3),
},
},
"typed fields win on collision with custom": {
exception: Exception{
DistinctId: "user-123",
Timestamp: now,
Properties: NewProperties().Set("$lib", "custom-lib").Set("distinct_id", "custom-id"),
ExceptionList: exList,
},
expectKeys: map[string]any{
"$lib": SDKName,
"distinct_id": "user-123",
},
forbiddenValues: []string{"custom-lib", "custom-id"},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
apiMsg, ok := tc.exception.APIfy().(ExceptionInApi)
if !ok {
t.Fatalf("expected ExceptionInApi, got %T", tc.exception.APIfy())
}
jsonBytes, err := json.Marshal(apiMsg)
if err != nil {
t.Fatalf("marshal failed: %v", err)
}
var wire map[string]any
if err := json.Unmarshal(jsonBytes, &wire); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
props, ok := wire["properties"].(map[string]any)
if !ok {
t.Fatalf("properties field missing or wrong type")
}
for k, want := range tc.expectKeys {
if got := props[k]; !reflect.DeepEqual(got, want) {
t.Errorf("property %q: expected %v (%T), got %v (%T)", k, want, want, got, got)
}
}
for _, forbidden := range tc.forbiddenValues {
for k, v := range props {
if s, ok := v.(string); ok && s == forbidden {
t.Errorf("forbidden value %q leaked into property %q", forbidden, k)
}
}
}
})
}
}
func TestException_JSONSerialization(t *testing.T) {
fingerprint := "custom-fingerprint-123"
exception := Exception{
Type: "exception",
Uuid: "01963f1a-3c12-7b21-8a1d-3f6d1cf49b8e",
DistinctId: "user-123",
Timestamp: time.Date(2024, 11, 17, 10, 30, 0, 0, time.UTC),
Properties: NewProperties().Set("environment", "production").Set("custom_key", "custom_value"),
DisableGeoIP: true,
ExceptionFingerprint: &fingerprint,
ExceptionList: []ExceptionItem{{Type: "RuntimeError", Value: "Database connection failed"}},
}
jsonBytes, err := json.Marshal(exception.APIfy())
if err != nil {
t.Fatalf("marshal failed: %v", err)
}
var result map[string]any
if err := json.Unmarshal(jsonBytes, &result); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
props, ok := result["properties"].(map[string]any)
if !ok {
t.Fatalf("properties field missing or wrong type")
}
tests := []struct {
obj map[string]any
field string
expected any
}{
{result, "type", "exception"},
{result, "uuid", "01963f1a-3c12-7b21-8a1d-3f6d1cf49b8e"},
{result, "event", "$exception"},
{result, "library", SDKName},
{result, "library_version", getVersion()},
{props, "$lib", SDKName},
{props, "$lib_version", getVersion()},
{props, "distinct_id", "user-123"},
{props, "$geoip_disable", true},
{props, "$exception_fingerprint", "custom-fingerprint-123"},
{props, "environment", "production"},
{props, "custom_key", "custom_value"},
}
for _, tc := range tests {
if tc.obj[tc.field] != tc.expected {
t.Errorf("%s: expected %v, got %v", tc.field, tc.expected, tc.obj[tc.field])
}
}
exList, ok := props["$exception_list"].([]any)
if !ok {
t.Errorf("$exception_list: expected []interface{}, got %T", props["$exception_list"])
}
if len(exList) == 0 {
t.Errorf("$exception_list: expected non-empty list, got empty")
}
}