mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-11 04:31:29 +08:00
658 lines
15 KiB
Go
658 lines
15 KiB
Go
package tracefunnel
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
tracefunnel "github.com/SigNoz/signoz/pkg/types/tracefunnel"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestValidateTimestamp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
timestamp int64
|
|
fieldName string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid timestamp",
|
|
timestamp: time.Now().UnixMilli(),
|
|
fieldName: "timestamp",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "zero timestamp",
|
|
timestamp: 0,
|
|
fieldName: "timestamp",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "negative timestamp",
|
|
timestamp: -1,
|
|
fieldName: "timestamp",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := ValidateTimestamp(tt.timestamp, tt.fieldName)
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTimestampIsMilliseconds(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
timestamp int64
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "valid millisecond timestamp",
|
|
timestamp: 1700000000000, // 2023-11-14 12:00:00 UTC
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "too small timestamp",
|
|
timestamp: 999999999999,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "too large timestamp",
|
|
timestamp: 10000000000000,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "second precision timestamp",
|
|
timestamp: 1700000000,
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ValidateTimestampIsMilliseconds(tt.timestamp)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateFunnelSteps(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
steps []tracefunnel.FunnelStep
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid steps",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "too few steps",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "missing service name",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "missing span name",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
Order: 1,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "negative order",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: -1,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := ValidateFunnelSteps(tt.steps)
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeFunnelSteps(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
steps []tracefunnel.FunnelStep
|
|
expected []tracefunnel.FunnelStep
|
|
}{
|
|
{
|
|
name: "already normalized steps",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expected: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "unordered steps",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
},
|
|
expected: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "steps with gaps in order",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 3",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-3",
|
|
Order: 3,
|
|
},
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expected: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
{
|
|
Name: "Step 3",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-3",
|
|
Order: 3,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Make a copy of the steps to avoid modifying the original
|
|
steps := make([]tracefunnel.FunnelStep, len(tt.steps))
|
|
copy(steps, tt.steps)
|
|
|
|
result := NormalizeFunnelSteps(steps)
|
|
|
|
// Compare only the relevant fields
|
|
for i := range result {
|
|
assert.Equal(t, tt.expected[i].Name, result[i].Name)
|
|
assert.Equal(t, tt.expected[i].ServiceName, result[i].ServiceName)
|
|
assert.Equal(t, tt.expected[i].SpanName, result[i].SpanName)
|
|
assert.Equal(t, tt.expected[i].Order, result[i].Order)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetClaims(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(*http.Request)
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid claims",
|
|
setup: func(r *http.Request) {
|
|
claims := authtypes.Claims{
|
|
UserID: "user-123",
|
|
OrgID: "org-123",
|
|
Email: "test@example.com",
|
|
}
|
|
*r = *r.WithContext(authtypes.NewContextWithClaims(r.Context(), claims))
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "no claims in context",
|
|
setup: func(r *http.Request) {},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
tt.setup(req)
|
|
|
|
claims, err := GetClaims(req)
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
assert.Nil(t, claims)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, claims)
|
|
assert.Equal(t, "user-123", claims.UserID)
|
|
assert.Equal(t, "org-123", claims.OrgID)
|
|
assert.Equal(t, "test@example.com", claims.Email)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateAndConvertTimestamp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
timestamp int64
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid timestamp",
|
|
timestamp: time.Now().UnixMilli(),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "zero timestamp",
|
|
timestamp: 0,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "negative timestamp",
|
|
timestamp: -1,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := ValidateAndConvertTimestamp(tt.timestamp)
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
assert.True(t, result.IsZero())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.False(t, result.IsZero())
|
|
// Verify the conversion from milliseconds to nanoseconds
|
|
assert.Equal(t, tt.timestamp*1000000, result.UnixNano())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConstructFunnelResponse(t *testing.T) {
|
|
now := time.Now()
|
|
funnelID := valuer.GenerateUUID()
|
|
orgID := valuer.GenerateUUID()
|
|
|
|
tests := []struct {
|
|
name string
|
|
funnel *tracefunnel.Funnel
|
|
claims *authtypes.Claims
|
|
expected tracefunnel.FunnelResponse
|
|
}{
|
|
{
|
|
name: "with user email from funnel",
|
|
funnel: &tracefunnel.Funnel{
|
|
BaseMetadata: tracefunnel.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: "test-funnel",
|
|
OrgID: orgID,
|
|
TimeAuditable: types.TimeAuditable{
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
UserAuditable: types.UserAuditable{
|
|
CreatedBy: "user-123",
|
|
UpdatedBy: "user-123",
|
|
},
|
|
},
|
|
CreatedByUser: &types.User{
|
|
ID: "user-123",
|
|
Email: "funnel@example.com",
|
|
},
|
|
Steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
},
|
|
},
|
|
claims: &authtypes.Claims{
|
|
UserID: "user-123",
|
|
OrgID: orgID.String(),
|
|
Email: "claims@example.com",
|
|
},
|
|
expected: tracefunnel.FunnelResponse{
|
|
FunnelName: "test-funnel",
|
|
FunnelID: funnelID.String(),
|
|
Steps: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
},
|
|
CreatedAt: now.UnixNano() / 1000000,
|
|
CreatedBy: "user-123",
|
|
UpdatedAt: now.UnixNano() / 1000000,
|
|
UpdatedBy: "user-123",
|
|
OrgID: orgID.String(),
|
|
UserEmail: "funnel@example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "with user email from claims",
|
|
funnel: &tracefunnel.Funnel{
|
|
BaseMetadata: tracefunnel.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: "test-funnel",
|
|
OrgID: orgID,
|
|
TimeAuditable: types.TimeAuditable{
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
UserAuditable: types.UserAuditable{
|
|
CreatedBy: "user-123",
|
|
UpdatedBy: "user-123",
|
|
},
|
|
},
|
|
Steps: []tracefunnel.FunnelStep{
|
|
{
|
|
ID: valuer.GenerateUUID(),
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
},
|
|
},
|
|
claims: &authtypes.Claims{
|
|
UserID: "user-123",
|
|
OrgID: orgID.String(),
|
|
Email: "claims@example.com",
|
|
},
|
|
expected: tracefunnel.FunnelResponse{
|
|
FunnelName: "test-funnel",
|
|
FunnelID: funnelID.String(),
|
|
Steps: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
},
|
|
CreatedAt: now.UnixNano() / 1000000,
|
|
CreatedBy: "user-123",
|
|
UpdatedAt: now.UnixNano() / 1000000,
|
|
UpdatedBy: "user-123",
|
|
OrgID: orgID.String(),
|
|
UserEmail: "claims@example.com",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ConstructFunnelResponse(tt.funnel, tt.claims)
|
|
|
|
// Compare top-level fields
|
|
assert.Equal(t, tt.expected.FunnelName, result.FunnelName)
|
|
assert.Equal(t, tt.expected.FunnelID, result.FunnelID)
|
|
assert.Equal(t, tt.expected.CreatedAt, result.CreatedAt)
|
|
assert.Equal(t, tt.expected.CreatedBy, result.CreatedBy)
|
|
assert.Equal(t, tt.expected.UpdatedAt, result.UpdatedAt)
|
|
assert.Equal(t, tt.expected.UpdatedBy, result.UpdatedBy)
|
|
assert.Equal(t, tt.expected.OrgID, result.OrgID)
|
|
assert.Equal(t, tt.expected.UserEmail, result.UserEmail)
|
|
|
|
// Compare steps
|
|
assert.Len(t, result.Steps, len(tt.expected.Steps))
|
|
for i, step := range result.Steps {
|
|
expectedStep := tt.expected.Steps[i]
|
|
assert.Equal(t, expectedStep.Name, step.Name)
|
|
assert.Equal(t, expectedStep.ServiceName, step.ServiceName)
|
|
assert.Equal(t, expectedStep.SpanName, step.SpanName)
|
|
assert.Equal(t, expectedStep.Order, step.Order)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProcessFunnelSteps(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
steps []tracefunnel.FunnelStep
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid steps with missing IDs",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: 0, // Will be normalized to 1
|
|
},
|
|
{
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 0, // Will be normalized to 2
|
|
},
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid steps - missing service name",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
SpanName: "test-span",
|
|
Order: 1,
|
|
},
|
|
{
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "invalid steps - negative order",
|
|
steps: []tracefunnel.FunnelStep{
|
|
{
|
|
Name: "Step 1",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span",
|
|
Order: -1,
|
|
},
|
|
{
|
|
Name: "Step 2",
|
|
ServiceName: "test-service",
|
|
SpanName: "test-span-2",
|
|
Order: 2,
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := ProcessFunnelSteps(tt.steps)
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Len(t, result, len(tt.steps))
|
|
|
|
// Verify IDs are generated
|
|
for _, step := range result {
|
|
assert.False(t, step.ID.IsZero())
|
|
}
|
|
|
|
// Verify orders are normalized
|
|
for i, step := range result {
|
|
assert.Equal(t, int64(i+1), step.Order)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|