Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
150 lines
5.0 KiB
Go
150 lines
5.0 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type invitedRegistrationUserService struct {
|
|
interfaces.UserService
|
|
registeredMode types.TenantProvisioningMode
|
|
updatedTenant uint64
|
|
updateCalls []uint64
|
|
}
|
|
|
|
func (s *invitedRegistrationUserService) GetUserByEmail(context.Context, string) (*types.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *invitedRegistrationUserService) Register(_ context.Context, req *types.RegisterRequest) (*types.User, error) {
|
|
s.registeredMode = req.TenantProvisioning
|
|
return &types.User{ID: "new-user", Username: req.Username, Email: req.Email, IsActive: true}, nil
|
|
}
|
|
|
|
func (s *invitedRegistrationUserService) UpdateUser(_ context.Context, user *types.User) error {
|
|
s.updatedTenant = user.TenantID
|
|
s.updateCalls = append(s.updateCalls, user.TenantID)
|
|
return nil
|
|
}
|
|
|
|
func (s *invitedRegistrationUserService) GenerateTokens(context.Context, *types.User) (string, string, error) {
|
|
return "access", "refresh", nil
|
|
}
|
|
|
|
type invitedRegistrationInvitationService struct {
|
|
interfaces.TenantInvitationService
|
|
acceptErr error
|
|
}
|
|
|
|
func (s *invitedRegistrationInvitationService) LookupByToken(context.Context, string) (*types.TenantInvitation, error) {
|
|
return &types.TenantInvitation{TenantID: 42, Role: types.TenantRoleViewer}, nil
|
|
}
|
|
|
|
func (s *invitedRegistrationInvitationService) AcceptByToken(context.Context, string, string) (*types.TenantMember, error) {
|
|
if s.acceptErr != nil {
|
|
return nil, s.acceptErr
|
|
}
|
|
return &types.TenantMember{TenantID: 42, Role: types.TenantRoleViewer}, nil
|
|
}
|
|
|
|
type invitedRegistrationTenantService struct {
|
|
interfaces.TenantService
|
|
}
|
|
|
|
func TestRegisterByInviteRestoresTenantlessAccountWhenInviteExpiresDuringRegistration(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
users := &invitedRegistrationUserService{}
|
|
h := &AuthHandler{
|
|
userService: users,
|
|
tenantService: &invitedRegistrationTenantService{},
|
|
invitationSvc: &invitedRegistrationInvitationService{acceptErr: errors.New("expired")},
|
|
}
|
|
r := gin.New()
|
|
r.Use(errorCapture())
|
|
r.POST("/auth/register-by-invite", h.RegisterByInvite)
|
|
|
|
body := []byte(`{"token":"invite-token","email":"alice@example.com","username":"alice","password":"supersecret1"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/auth/register-by-invite", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusGone {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if users.updatedTenant != 0 {
|
|
t.Fatalf("updated tenant=%d, want tenantless rollback", users.updatedTenant)
|
|
}
|
|
if len(users.updateCalls) != 2 || users.updateCalls[0] != 42 || users.updateCalls[1] != 0 {
|
|
t.Fatalf("update calls=%v, want [42 0]", users.updateCalls)
|
|
}
|
|
}
|
|
|
|
func (s *invitedRegistrationTenantService) GetTenantByID(context.Context, uint64) (*types.Tenant, error) {
|
|
return &types.Tenant{ID: 42, Name: "Invited Workspace"}, nil
|
|
}
|
|
|
|
func TestRegisterByInviteUsesInvitedTenantWithoutPersonalTenant(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
users := &invitedRegistrationUserService{}
|
|
h := &AuthHandler{
|
|
userService: users,
|
|
tenantService: &invitedRegistrationTenantService{},
|
|
invitationSvc: &invitedRegistrationInvitationService{},
|
|
}
|
|
r := gin.New()
|
|
r.Use(errorCapture())
|
|
r.POST("/auth/register-by-invite", h.RegisterByInvite)
|
|
|
|
body := []byte(`{"token":"invite-token","email":"alice@example.com","username":"alice","password":"supersecret1"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/auth/register-by-invite", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if users.registeredMode != types.TenantProvisioningTenantless {
|
|
t.Fatalf("register mode=%q, want tenantless", users.registeredMode)
|
|
}
|
|
if users.updatedTenant != 42 {
|
|
t.Fatalf("updated tenant=%d, want 42", users.updatedTenant)
|
|
}
|
|
}
|
|
|
|
func TestRegisterByInviteRejectsSimplePasswordWhenComplexEnabled(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
users := &invitedRegistrationUserService{}
|
|
h := &AuthHandler{
|
|
userService: users,
|
|
tenantService: &invitedRegistrationTenantService{},
|
|
invitationSvc: &invitedRegistrationInvitationService{},
|
|
systemSettingSvc: &tenantPolicySettingService{enabled: true},
|
|
}
|
|
r := gin.New()
|
|
r.Use(errorCapture())
|
|
r.POST("/auth/register-by-invite", h.RegisterByInvite)
|
|
|
|
body := []byte(`{"token":"invite-token","email":"alice@example.com","username":"alice","password":"supersecret1"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/auth/register-by-invite", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("status=%d body=%s, want 400", w.Code, w.Body.String())
|
|
}
|
|
if users.registeredMode != "" {
|
|
t.Fatalf("Register was called with mode=%q", users.registeredMode)
|
|
}
|
|
}
|