package activitytemplate import ( "context" "net/http" "net/http/httptest" "strings" "testing" "hyapp-admin-server/internal/integration/activityclient" "hyapp-admin-server/internal/middleware" activityv1 "hyapp.local/api/proto/activity/v1" "github.com/gin-gonic/gin" ) type validatePermissionClient struct { activityclient.Client } func (validatePermissionClient) ValidateActivityTemplate(context.Context, *activityv1.ValidateActivityTemplateRequest) (*activityv1.ValidateActivityTemplateResponse, error) { return &activityv1.ValidateActivityTemplateResponse{Valid: true}, nil } func TestRegisterRoutesExposesActivityTemplateContract(t *testing.T) { gin.SetMode(gin.TestMode) engine := gin.New() RegisterRoutes(engine.Group("/api/v1"), &Handler{}) actual := make(map[string]struct{}) for _, route := range engine.Routes() { actual[route.Method+" "+route.Path] = struct{}{} } expected := []string{ "GET /api/v1/admin/activities/templates", "POST /api/v1/admin/activities/templates", "POST /api/v1/admin/activities/templates/validate", "GET /api/v1/admin/activities/templates/:template_id", "PUT /api/v1/admin/activities/templates/:template_id", "PATCH /api/v1/admin/activities/templates/:template_id/status", "DELETE /api/v1/admin/activities/templates/:template_id", "GET /api/v1/admin/activities/templates/:template_id/versions", "POST /api/v1/admin/activities/templates/:template_id/clone", "GET /api/v1/admin/activities/templates/:template_id/leaderboard", "GET /api/v1/admin/activities/templates/:template_id/data", "GET /api/v1/admin/activities/templates/:template_id/data/export", "GET /api/v1/admin/activities/templates/:template_id/rewards", "POST /api/v1/admin/activities/templates/:template_id/rewards/:reward_job_id/retry", "GET /api/v1/admin/activities/templates/:template_id/task-claims", "POST /api/v1/admin/activities/templates/:template_id/task-claims/:claim_id/retry", } for _, route := range expected { if _, ok := actual[route]; !ok { t.Errorf("missing route %s; actual=%v", route, actual) } } } func TestValidateRouteAcceptsCreateUpdateOrPublishPermission(t *testing.T) { gin.SetMode(gin.TestMode) tests := []struct { permission string wantStatus int }{ {permission: "activity-template:create", wantStatus: http.StatusOK}, {permission: "activity-template:update", wantStatus: http.StatusOK}, {permission: "activity-template:publish", wantStatus: http.StatusOK}, {permission: "activity-template:view", wantStatus: http.StatusForbidden}, } for _, tt := range tests { t.Run(tt.permission, func(t *testing.T) { engine := gin.New() engine.Use(func(c *gin.Context) { c.Set(middleware.ContextPermissions, []string{tt.permission}) c.Next() }) RegisterRoutes(engine.Group("/api/v1"), New(validatePermissionClient{}, nil)) recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/activities/templates/validate", strings.NewReader(`{ "template":{"template_code":"gift_challenge_2026","name":"Gift Challenge"}, "for_publish":false }`)) request.Header.Set("Content-Type", "application/json") engine.ServeHTTP(recorder, request) if recorder.Code != tt.wantStatus { t.Fatalf("status = %d, want %d; body=%s", recorder.Code, tt.wantStatus, recorder.Body.String()) } }) } }