Files
warmbly/internal/api/handler/admin_tester_test.go
T
Matthew MeszarosandDevin AI f52ef240b4 feat: add audited repeat-safe sample data provisioning for active dedicated Test workspaces in Admin Testers
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-06 09:23:31 +00:00

155 lines
5.9 KiB
Go

package handler
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/mail"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/api/middleware"
"github.com/warmbly/warmbly/internal/app/organization"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/repository"
)
type testerUsers struct {
repository.UserRepository
userID uuid.UUID
until time.Time
}
func (r *testerUsers) GetUserByEmail(context.Context, string) (*models.User, error) { return nil, nil }
func (r *testerUsers) CreateExemptUser(_ context.Context, email *mail.Address, _, _ string, _ *uuid.UUID, until time.Time) (*models.User, error) {
r.until = until
return &models.User{ID: r.userID, Email: email.Address}, nil
}
type testerWorkspaces struct {
organization.OrganizationService
orgID, actor, roleID uuid.UUID
until time.Time
created, joined bool
}
func (s *testerWorkspaces) CreateTesterWorkspace(_ context.Context, _, actor uuid.UUID, _, _ string, until time.Time) (*models.Organization, *errx.Error) {
s.created, s.actor, s.until = true, actor, until
return &models.Organization{ID: s.orgID, Category: models.OrganizationCategoryTest}, nil
}
func (s *testerWorkspaces) AttachTester(_ context.Context, orgID, _, actor, roleID uuid.UUID) (*models.OrganizationMember, *errx.Error) {
s.joined, s.orgID, s.actor, s.roleID = true, orgID, actor, roleID
return &models.OrganizationMember{OrganizationID: orgID}, nil
}
func TestAdminCreateTesterOnlyGrantsDedicatedWorkspaces(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, existing := range []bool{false, true} {
actor, orgID, roleID := uuid.New(), uuid.New(), uuid.New()
users := &testerUsers{userID: uuid.New()}
orgs := &testerWorkspaces{orgID: orgID}
h := &Handler{UserRepo: users, OrganizationService: orgs}
r := gin.New()
r.Use(func(c *gin.Context) { c.Set(middleware.AdminUserIDKey, actor) })
r.POST("/testers", h.AdminCreateTester)
req := adminCreateTesterRequest{Email: "reviewer@example.test", Reason: "OAuth review", PasswordDays: 7}
if existing {
req.OrgID, req.RoleID = &orgID, &roleID
}
raw, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/testers", strings.NewReader(string(raw))))
if w.Code != http.StatusOK {
t.Fatalf("create tester existing=%v: HTTP %d", existing, w.Code)
}
var response adminCreateTesterResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if response.Joined != existing || orgs.created == existing || orgs.joined != existing || orgs.actor != actor {
t.Fatalf("wrong provisioning path for existing=%v", existing)
}
if existing && orgs.roleID != roleID {
t.Fatal("existing workspace role changed")
}
if !existing && !orgs.until.Equal(users.until) {
t.Fatal("test entitlement did not use the reviewer password expiry")
}
if !response.PasswordExpiresAt.Equal(users.until) || !strings.HasPrefix(response.Password, "Tester-") {
t.Fatal("reviewer credentials response is incomplete")
}
}
}
type testerSampleService struct {
organization.OrganizationService
orgID, actor uuid.UUID
ip, agent string
called bool
err *errx.Error
}
func (s *testerSampleService) SeedTesterWorkspace(_ context.Context, orgID, actor uuid.UUID, ip, agent string) (*models.TesterSampleData, *errx.Error) {
s.called, s.orgID, s.actor, s.ip, s.agent = true, orgID, actor, ip, agent
return &models.TesterSampleData{OrganizationID: orgID, Created: true, SeededAt: time.Now()}, s.err
}
func TestAdminSeedTesterWorkspaceRequiresAuthorityAndValidTarget(t *testing.T) {
gin.SetMode(gin.TestMode)
actor, orgID := uuid.New(), uuid.New()
for _, tc := range []struct {
name string
perms models.AdminPermission
actor bool
id string
missing bool
err *errx.Error
status int
called bool
}{
{name: "allowed", perms: models.AdminPermManageTesters, actor: true, id: orgID.String(), status: http.StatusOK, called: true},
{name: "view only", perms: models.AdminPermViewUsers, actor: true, id: orgID.String(), status: http.StatusForbidden},
{name: "no actor", perms: models.AdminPermManageTesters, id: orgID.String(), status: http.StatusUnauthorized},
{name: "invalid target", perms: models.AdminPermManageTesters, actor: true, id: "not-a-uuid", status: http.StatusBadRequest},
{name: "nil target", perms: models.AdminPermManageTesters, actor: true, id: uuid.Nil.String(), status: http.StatusBadRequest},
{name: "inactive workspace", perms: models.AdminPermManageTesters, actor: true, id: orgID.String(), err: errx.New(errx.BadRequest, "inactive"), status: http.StatusBadRequest, called: true},
{name: "unavailable", perms: models.AdminPermManageTesters, actor: true, id: orgID.String(), missing: true, status: http.StatusServiceUnavailable},
} {
t.Run(tc.name, func(t *testing.T) {
s := &testerSampleService{err: tc.err}
h := &Handler{}
if !tc.missing {
h.OrganizationService = s
}
r := gin.New()
r.Use(func(c *gin.Context) {
c.Set(middleware.AdminPermissionsKey, tc.perms)
if tc.actor {
c.Set(middleware.AdminUserIDKey, actor)
}
})
r.POST("/organizations/:id/sample-data", middleware.RequireAdminPermission(models.AdminPermManageTesters), h.AdminSeedTesterWorkspace)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/organizations/"+tc.id+"/sample-data", nil)
req.Header.Set("User-Agent", "sample-review")
r.ServeHTTP(w, req)
if w.Code != tc.status || s.called != tc.called {
t.Fatalf("HTTP %d, called=%v: %s", w.Code, s.called, w.Body.String())
}
if s.called && (s.orgID != orgID || s.actor != actor || s.agent != "sample-review" || s.ip == "") {
t.Fatal("target or audit attribution changed")
}
})
}
}