mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
The server moved off /api/vN; the integration suite, handler tests, install.sh's health wait and the docs still called the old tree. Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
206 lines
6.4 KiB
Go
206 lines
6.4 KiB
Go
package impltracefunnel
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/o11y/pkg/http/routing"
|
|
"github.com/hanzoai/o11y/pkg/types"
|
|
"github.com/hanzoai/o11y/pkg/types/authtypes"
|
|
"github.com/hanzoai/o11y/pkg/types/coretypes"
|
|
traceFunnels "github.com/hanzoai/o11y/pkg/types/tracefunneltypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type MockModule struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockModule) Create(ctx context.Context, timestamp int64, name string, userID valuer.UUID, orgID valuer.UUID) (*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 valuer.UUID, orgID valuer.UUID) (*traceFunnels.StorableFunnel, error) {
|
|
args := m.Called(ctx, funnelID, orgID)
|
|
return args.Get(0).(*traceFunnels.StorableFunnel), args.Error(1)
|
|
}
|
|
|
|
func (m *MockModule) Update(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID) error {
|
|
args := m.Called(ctx, funnel, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockModule) List(ctx context.Context, orgID valuer.UUID) ([]*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 valuer.UUID, orgID valuer.UUID) error {
|
|
args := m.Called(ctx, funnelID, orgID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockModule) Save(ctx context.Context, funnel *traceFunnels.StorableFunnel, userID valuer.UUID, orgID valuer.UUID) error {
|
|
args := m.Called(ctx, funnel, userID, orgID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockModule) GetFunnelMetadata(ctx context.Context, funnelID valuer.UUID, orgID valuer.UUID) (int64, int64, string, error) {
|
|
args := m.Called(ctx, funnelID, orgID)
|
|
return args.Get(0).(int64), args.Get(1).(int64), args.String(2), args.Error(3)
|
|
}
|
|
|
|
func TestHandler_List(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
handler := NewHandler(mockModule)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/o11y/trace-funnels/list", nil)
|
|
|
|
orgID := valuer.GenerateUUID()
|
|
claims := authtypes.Claims{
|
|
OrgID: orgID.String(),
|
|
}
|
|
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims))
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
funnel1ID := valuer.GenerateUUID()
|
|
funnel2ID := valuer.GenerateUUID()
|
|
expectedFunnels := []*traceFunnels.StorableFunnel{
|
|
{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnel1ID,
|
|
},
|
|
Name: "funnel-1",
|
|
OrgID: orgID,
|
|
},
|
|
{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnel2ID,
|
|
},
|
|
Name: "funnel-2",
|
|
OrgID: 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()
|
|
orgID := valuer.GenerateUUID()
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/o11y/trace-funnels/"+funnelID.String(), nil)
|
|
req = coretypes.SetRoute(req, "/v1/o11y/trace-funnels/{funnel_id}")
|
|
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), authtypes.Claims{
|
|
OrgID: orgID.String(),
|
|
}))
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
expectedFunnel := &traceFunnels.StorableFunnel{
|
|
Identifiable: types.Identifiable{
|
|
ID: funnelID,
|
|
},
|
|
Name: "test-funnel",
|
|
OrgID: orgID,
|
|
}
|
|
|
|
mockModule.On("Get", req.Context(), funnelID, orgID).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()
|
|
orgID := valuer.GenerateUUID()
|
|
req := httptest.NewRequest(http.MethodDelete, "/v1/o11y/trace-funnels/"+funnelID.String(), nil)
|
|
req = coretypes.SetRoute(req, "/v1/o11y/trace-funnels/{funnel_id}")
|
|
req = req.WithContext(authtypes.NewContextWithClaims(req.Context(), authtypes.Claims{
|
|
OrgID: orgID.String(),
|
|
}))
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
mockModule.On("Delete", req.Context(), funnelID, orgID).Return(nil)
|
|
|
|
handler.Delete(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|
|
|
|
// The two tests above INJECT the funnel id; this one lets a REAL router match it
|
|
// on the REAL template. Both halves are needed: injection alone would prove only
|
|
// that the writer and the reader of coretypes agree with each other, never that
|
|
// the handler acts on what the router actually put there.
|
|
func TestHandler_Get_ActsOnTheFunnelTheRouterMatched(t *testing.T) {
|
|
mockModule := new(MockModule)
|
|
funnelID := valuer.GenerateUUID()
|
|
orgID := valuer.GenerateUUID()
|
|
|
|
router := routing.Serve(http.MethodGet, "/v1/o11y/trace-funnels/{funnel_id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
NewHandler(mockModule).Get(w, r.WithContext(authtypes.NewContextWithClaims(r.Context(), authtypes.Claims{OrgID: orgID.String()})))
|
|
}))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/o11y/trace-funnels/"+funnelID.String(), nil)
|
|
|
|
mockModule.On("Get", mock.Anything, funnelID, orgID).Return(&traceFunnels.StorableFunnel{
|
|
Identifiable: types.Identifiable{ID: funnelID},
|
|
Name: "test-funnel",
|
|
OrgID: orgID,
|
|
}, nil)
|
|
|
|
rr := httptest.NewRecorder()
|
|
router.ServeHTTP(rr, req)
|
|
|
|
require.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
|
|
assert.Contains(t, rr.Body.String(), "test-funnel")
|
|
|
|
mockModule.AssertExpectations(t)
|
|
}
|