mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-10 23:51:31 +08:00
422 lines
12 KiB
Go
422 lines
12 KiB
Go
package impltracefunnel
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunnel"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/gorilla/mux"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
type MockModule struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockModule) Create(ctx context.Context, timestamp int64, name string, userID string, orgID string) (*traceFunnels.StorableFunnel, error) {
|
|
args := m.Called(ctx, timestamp, name, userID, orgID)
|
|
return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
|
|
}
|
|
|
|
func (m *MockModule) Get(ctx context.Context, funnelID string) (*traceFunnels.StorableFunnel, error) {
|
|
args := m.Called(ctx, funnelID)
|
|
return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
|
|
}
|
|
|
|
func (m *MockModule) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string) error {
|
|
args := m.Called(ctx, funnel, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockModule) List(ctx context.Context, orgID string) ([]*traceFunnels.StorableFunnel, error) {
|
|
args := m.Called(ctx, orgID)
|
|
return args.Get(0).([]*traceFunnels.StorableFunnel), args.Error(1)
|
|
}
|
|
|
|
func (m *MockModule) Delete(ctx context.Context, funnelID string) error {
|
|
args := m.Called(ctx, funnelID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockModule) Save(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID string, orgID string) error {
|
|
args := m.Called(ctx, funnel, userID, orgID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockModule) GetFunnelMetadata(ctx context.Context, funnelID string) (int64, int64, string, error) {
|
|
args := m.Called(ctx, funnelID)
|
|
return args.Get(0).(int64), args.Get(1).(int64), args.String(2), args.Error(3)
|
|
}
|
|
|
|
func TestHandler_New(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
reqBody := traceFunnels.PostableFunnel{
|
|
Name: "test-funnel",
|
|
Timestamp: time.Now().UnixMilli(),
|
|
}
|
|
|
|
jsonBody, _ := json.Marshal(reqBody)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/trace-funnels/new", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
orgID := valuer.GenerateUUID().String()
|
|
claims := authtypes.Claims{
|
|
UserID: "user-123",
|
|
OrgID: orgID,
|
|
Email: "test@example.com",
|
|
}
|
|
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims))
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
funnelID := valuer.GenerateUUID()
|
|
expectedFunnel := &traceFunnels.StorableFunnel{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: reqBody.Name,
|
|
OrgID: valuer.MustNewUUID(orgID),
|
|
},
|
|
}
|
|
|
|
mockModule.On("List", req.Context(), orgID).Return([]*traceFunnels.StorableFunnel{}, nil)
|
|
mockModule.On("Create", req.Context(), reqBody.Timestamp, reqBody.Name, "user-123", orgID).Return(expectedFunnel, nil)
|
|
|
|
handler.New(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Data traceFunnels.GettableFunnel `json:"data"`
|
|
}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "success", response.Status)
|
|
assert.Equal(t, reqBody.Name, response.Data.FunnelName)
|
|
assert.Equal(t, orgID, response.Data.OrgID)
|
|
assert.Equal(t, "test@example.com", response.Data.UserEmail)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|
|
|
|
func TestHandler_Update(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
// Create a valid UUID for the funnel ID
|
|
funnelID := valuer.GenerateUUID()
|
|
orgID := valuer.GenerateUUID().String()
|
|
|
|
reqBody := traceFunnels.PostableFunnel{
|
|
FunnelID: funnelID,
|
|
Name: "updated-funnel",
|
|
Steps: []*traceFunnels.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,
|
|
},
|
|
},
|
|
Timestamp: time.Now().UnixMilli(),
|
|
}
|
|
|
|
body, err := json.Marshal(reqBody)
|
|
assert.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, "/api/v1/trace-funnels/steps/update", bytes.NewBuffer(body))
|
|
assert.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Set up context with claims
|
|
claims := authtypes.Claims{
|
|
UserID: "user-123",
|
|
OrgID: orgID,
|
|
Email: "test@example.com",
|
|
}
|
|
ctx := authtypes.NewContextWithClaims(req.Context(), claims)
|
|
req = req.WithContext(ctx)
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
// Set up mock expectations
|
|
existingFunnel := &traceFunnels.StorableFunnel{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: "test-funnel",
|
|
OrgID: valuer.MustNewUUID(orgID),
|
|
TimeAuditable: types.TimeAuditable{
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
},
|
|
UserAuditable: types.UserAuditable{
|
|
CreatedBy: "user-123",
|
|
UpdatedBy: "user-123",
|
|
},
|
|
},
|
|
CreatedByUser: &types.User{
|
|
ID: "user-123",
|
|
Email: "test@example.com",
|
|
},
|
|
}
|
|
|
|
updatedFunnel := &traceFunnels.StorableFunnel{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: reqBody.Name,
|
|
OrgID: valuer.MustNewUUID(orgID),
|
|
TimeAuditable: types.TimeAuditable{
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Unix(0, reqBody.Timestamp*1000000),
|
|
},
|
|
UserAuditable: types.UserAuditable{
|
|
CreatedBy: "user-123",
|
|
UpdatedBy: "user-123",
|
|
},
|
|
},
|
|
Steps: reqBody.Steps,
|
|
CreatedByUser: &types.User{
|
|
ID: "user-123",
|
|
Email: "test@example.com",
|
|
},
|
|
}
|
|
|
|
// First Get call to validate the funnel exists
|
|
mockModule.On("Get", req.Context(), funnelID.String()).Return(existingFunnel, nil).Once()
|
|
// List call to check for name conflicts
|
|
mockModule.On("List", req.Context(), orgID).Return([]*traceFunnels.StorableFunnel{}, nil).Once()
|
|
// Update call to save the changes
|
|
mockModule.On("Update", req.Context(), mock.MatchedBy(func(f *traceFunnels.StorableFunnel) bool {
|
|
return f.Name == reqBody.Name &&
|
|
f.ID.String() == funnelID.String() &&
|
|
len(f.Steps) == len(reqBody.Steps) &&
|
|
f.Steps[0].Name == reqBody.Steps[0].Name &&
|
|
f.Steps[0].ServiceName == reqBody.Steps[0].ServiceName &&
|
|
f.Steps[0].SpanName == reqBody.Steps[0].SpanName &&
|
|
f.Steps[1].Name == reqBody.Steps[1].Name &&
|
|
f.Steps[1].ServiceName == reqBody.Steps[1].ServiceName &&
|
|
f.Steps[1].SpanName == reqBody.Steps[1].SpanName &&
|
|
f.UpdatedAt.UnixNano()/1000000 == reqBody.Timestamp &&
|
|
f.UpdatedBy == "user-123"
|
|
}), "user-123").Return(nil).Once()
|
|
// Second Get call to get the updated funnel for the response
|
|
mockModule.On("Get", req.Context(), funnelID.String()).Return(updatedFunnel, nil).Once()
|
|
|
|
handler.UpdateSteps(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Data traceFunnels.GettableFunnel `json:"data"`
|
|
}
|
|
err = json.Unmarshal(rr.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "success", response.Status)
|
|
assert.Equal(t, "updated-funnel", response.Data.FunnelName)
|
|
assert.Equal(t, funnelID.String(), response.Data.FunnelID)
|
|
assert.Equal(t, "test@example.com", response.Data.UserEmail)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|
|
|
|
func TestHandler_List(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/trace-funnels/list", nil)
|
|
|
|
orgID := valuer.GenerateUUID().String()
|
|
claims := authtypes.Claims{
|
|
OrgID: orgID,
|
|
}
|
|
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims))
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
funnel1ID := valuer.GenerateUUID()
|
|
funnel2ID := valuer.GenerateUUID()
|
|
expectedFunnels := []*traceFunnels.StorableFunnel{
|
|
{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnel1ID,
|
|
},
|
|
Name: "funnel-1",
|
|
OrgID: valuer.MustNewUUID(orgID),
|
|
},
|
|
},
|
|
{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnel2ID,
|
|
},
|
|
Name: "funnel-2",
|
|
OrgID: valuer.MustNewUUID(orgID),
|
|
},
|
|
},
|
|
}
|
|
|
|
mockModule.On("List", req.Context(), orgID).Return(expectedFunnels, nil)
|
|
|
|
handler.List(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Data []traceFunnels.GettableFunnel `json:"data"`
|
|
}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "success", response.Status)
|
|
assert.Len(t, response.Data, 2)
|
|
assert.Equal(t, "funnel-1", response.Data[0].FunnelName)
|
|
assert.Equal(t, "funnel-2", response.Data[1].FunnelName)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|
|
|
|
func TestHandler_Get(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
funnelID := valuer.GenerateUUID()
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/trace-funnels/"+funnelID.String(), nil)
|
|
req = mux.SetURLVars(req, map[string]string{"funnel_id": funnelID.String()})
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
expectedFunnel := &traceFunnels.StorableFunnel{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: "test-funnel",
|
|
OrgID: valuer.GenerateUUID(),
|
|
},
|
|
}
|
|
|
|
mockModule.On("Get", req.Context(), funnelID.String()).Return(expectedFunnel, nil)
|
|
|
|
handler.Get(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Data traceFunnels.GettableFunnel `json:"data"`
|
|
}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "success", response.Status)
|
|
assert.Equal(t, "test-funnel", response.Data.FunnelName)
|
|
assert.Equal(t, expectedFunnel.OrgID.String(), response.Data.OrgID)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|
|
|
|
func TestHandler_Delete(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
funnelID := valuer.GenerateUUID()
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/trace-funnels/"+funnelID.String(), nil)
|
|
req = mux.SetURLVars(req, map[string]string{"funnel_id": funnelID.String()})
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
mockModule.On("Delete", req.Context(), funnelID.String()).Return(nil)
|
|
|
|
handler.Delete(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|
|
|
|
func TestHandler_Save(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
reqBody := traceFunnels.PostableFunnel{
|
|
FunnelID: valuer.GenerateUUID(),
|
|
Description: "updated description",
|
|
Timestamp: time.Now().UnixMilli(),
|
|
UserID: "user-123",
|
|
}
|
|
|
|
jsonBody, _ := json.Marshal(reqBody)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/trace-funnels/save", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
orgID := valuer.GenerateUUID().String()
|
|
claims := authtypes.Claims{
|
|
UserID: "user-123",
|
|
OrgID: orgID,
|
|
}
|
|
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims))
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
existingFunnel := &traceFunnels.StorableFunnel{
|
|
BaseMetadata: traceFunnels.BaseMetadata{
|
|
Identifiable: types.Identifiable{
|
|
ID: reqBody.FunnelID,
|
|
},
|
|
Name: "test-funnel",
|
|
OrgID: valuer.MustNewUUID(orgID),
|
|
},
|
|
}
|
|
|
|
mockModule.On("Get", req.Context(), reqBody.FunnelID.String()).Return(existingFunnel, nil)
|
|
mockModule.On("Save", req.Context(), mock.MatchedBy(func(f *traceFunnels.StorableFunnel) bool {
|
|
return f.ID.String() == reqBody.FunnelID.String() &&
|
|
f.Name == existingFunnel.Name &&
|
|
f.Description == reqBody.Description &&
|
|
f.UpdatedBy == "user-123" &&
|
|
f.OrgID.String() == orgID
|
|
}), "user-123", orgID).Return(nil)
|
|
mockModule.On("GetFunnelMetadata", req.Context(), reqBody.FunnelID.String()).Return(int64(0), int64(0), reqBody.Description, nil)
|
|
|
|
handler.Save(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
var response struct {
|
|
Status string `json:"status"`
|
|
Data traceFunnels.GettableFunnel `json:"data"`
|
|
}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "success", response.Status)
|
|
assert.Equal(t, reqBody.Description, response.Data.Description)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|