Files
zeekay 46c6241c52 Spell every o11y caller /v1/o11y
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
2026-09-24 15:05:09 -07:00

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