···
+
"Coves/internal/api/middleware"
+
"Coves/internal/core/communities"
+
// mockCommunityService implements communities.Service for testing
+
type mockCommunityService struct {
+
createFunc func(ctx context.Context, req communities.CreateCommunityRequest) (*communities.Community, error)
+
func (m *mockCommunityService) CreateCommunity(ctx context.Context, req communities.CreateCommunityRequest) (*communities.Community, error) {
+
if m.createFunc != nil {
+
return m.createFunc(ctx, req)
+
return &communities.Community{
+
DID: "did:plc:test123",
+
Handle: "test.community.coves.social",
+
RecordURI: "at://did:plc:test123/social.coves.community.profile/self",
+
RecordCID: "bafytest123",
+
DisplayName: req.DisplayName,
+
Description: req.Description,
+
Visibility: req.Visibility,
+
func (m *mockCommunityService) GetCommunity(ctx context.Context, identifier string) (*communities.Community, error) {
+
func (m *mockCommunityService) UpdateCommunity(ctx context.Context, req communities.UpdateCommunityRequest) (*communities.Community, error) {
+
func (m *mockCommunityService) ListCommunities(ctx context.Context, req communities.ListCommunitiesRequest) ([]*communities.Community, int, error) {
+
func (m *mockCommunityService) SearchCommunities(ctx context.Context, req communities.SearchCommunitiesRequest) ([]*communities.Community, int, error) {
+
func (m *mockCommunityService) SubscribeToCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string, contentVisibility int) (*communities.Subscription, error) {
+
func (m *mockCommunityService) UnsubscribeFromCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string) error {
+
func (m *mockCommunityService) GetUserSubscriptions(ctx context.Context, userDID string, limit, offset int) ([]*communities.Subscription, error) {
+
func (m *mockCommunityService) GetCommunitySubscribers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*communities.Subscription, error) {
+
func (m *mockCommunityService) BlockCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string) (*communities.CommunityBlock, error) {
+
func (m *mockCommunityService) UnblockCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string) error {
+
func (m *mockCommunityService) GetBlockedCommunities(ctx context.Context, userDID string, limit, offset int) ([]*communities.CommunityBlock, error) {
+
func (m *mockCommunityService) IsBlocked(ctx context.Context, userDID, communityIdentifier string) (bool, error) {
+
func (m *mockCommunityService) GetMembership(ctx context.Context, userDID, communityIdentifier string) (*communities.Membership, error) {
+
func (m *mockCommunityService) ListCommunityMembers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*communities.Membership, error) {
+
func (m *mockCommunityService) ValidateHandle(handle string) error {
+
func (m *mockCommunityService) ResolveCommunityIdentifier(ctx context.Context, identifier string) (string, error) {
+
func (m *mockCommunityService) EnsureFreshToken(ctx context.Context, community *communities.Community) (*communities.Community, error) {
+
func (m *mockCommunityService) GetByDID(ctx context.Context, did string) (*communities.Community, error) {
+
func TestCreateHandler_AllowlistRestriction(t *testing.T) {
+
mockService := &mockCommunityService{}
+
name: "allowed DID can create community",
+
allowedDIDs: []string{"did:plc:allowed123"},
+
requestDID: "did:plc:allowed123",
+
expectedStatus: http.StatusOK,
+
name: "non-allowed DID is forbidden",
+
allowedDIDs: []string{"did:plc:allowed123"},
+
requestDID: "did:plc:notallowed456",
+
expectedStatus: http.StatusForbidden,
+
expectedError: "CommunityCreationRestricted",
+
name: "empty allowlist allows anyone",
+
requestDID: "did:plc:anyuser789",
+
expectedStatus: http.StatusOK,
+
name: "multiple allowed DIDs - first DID",
+
allowedDIDs: []string{"did:plc:admin1", "did:plc:admin2", "did:plc:admin3"},
+
requestDID: "did:plc:admin1",
+
expectedStatus: http.StatusOK,
+
name: "multiple allowed DIDs - last DID",
+
allowedDIDs: []string{"did:plc:admin1", "did:plc:admin2", "did:plc:admin3"},
+
requestDID: "did:plc:admin3",
+
expectedStatus: http.StatusOK,
+
name: "multiple allowed DIDs - not in list",
+
allowedDIDs: []string{"did:plc:admin1", "did:plc:admin2"},
+
requestDID: "did:plc:randomuser",
+
expectedStatus: http.StatusForbidden,
+
expectedError: "CommunityCreationRestricted",
+
name: "allowlist with empty strings filtered - valid DID works",
+
allowedDIDs: []string{"did:plc:admin1", "", "did:plc:admin2"},
+
requestDID: "did:plc:admin1",
+
expectedStatus: http.StatusOK,
+
name: "allowlist with empty strings filtered - invalid DID blocked",
+
allowedDIDs: []string{"did:plc:admin1", "", "did:plc:admin2"},
+
requestDID: "did:plc:notallowed",
+
expectedStatus: http.StatusForbidden,
+
expectedError: "CommunityCreationRestricted",
+
name: "all empty strings allows anyone",
+
allowedDIDs: []string{"", "", ""},
+
requestDID: "did:plc:anyuser",
+
expectedStatus: http.StatusOK,
+
for _, tc := range tests {
+
t.Run(tc.name, func(t *testing.T) {
+
handler := NewCreateHandler(mockService, tc.allowedDIDs)
+
reqBody := map[string]interface{}{
+
"name": "testcommunity",
+
"displayName": "Test Community",
+
"description": "Test description",
+
"visibility": "public",
+
"allowExternalDiscovery": true,
+
bodyBytes, err := json.Marshal(reqBody)
+
t.Fatalf("Failed to marshal request: %v", err)
+
req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.create", bytes.NewBuffer(bodyBytes))
+
req.Header.Set("Content-Type", "application/json")
+
// Inject user DID into context (simulates auth middleware)
+
ctx := context.WithValue(req.Context(), middleware.UserDIDKey, tc.requestDID)
+
req = req.WithContext(ctx)
+
w := httptest.NewRecorder()
+
handler.HandleCreate(w, req)
+
if w.Code != tc.expectedStatus {
+
t.Errorf("Expected status %d, got %d. Body: %s", tc.expectedStatus, w.Code, w.Body.String())
+
// Check error response if expected
+
if tc.expectedError != "" {
+
Error string `json:"error"`
+
Message string `json:"message"`
+
if err := json.NewDecoder(w.Body).Decode(&errResp); err != nil {
+
t.Fatalf("Failed to decode error response: %v", err)
+
if errResp.Error != tc.expectedError {
+
t.Errorf("Expected error %s, got %s", tc.expectedError, errResp.Error)
+
func TestCreateHandler_RequiresAuth(t *testing.T) {
+
mockService := &mockCommunityService{}
+
handler := NewCreateHandler(mockService, nil)
+
// Create request without auth context
+
reqBody := map[string]interface{}{
+
"name": "testcommunity",
+
"displayName": "Test Community",
+
"description": "Test description",
+
"visibility": "public",
+
bodyBytes, err := json.Marshal(reqBody)
+
t.Fatalf("Failed to marshal request: %v", err)
+
req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.create", bytes.NewBuffer(bodyBytes))
+
req.Header.Set("Content-Type", "application/json")
+
// No user DID in context
+
w := httptest.NewRecorder()
+
handler.HandleCreate(w, req)
+
if w.Code != http.StatusUnauthorized {
+
t.Errorf("Expected status 401, got %d. Body: %s", w.Code, w.Body.String())
+
Error string `json:"error"`
+
if err := json.NewDecoder(w.Body).Decode(&errResp); err != nil {
+
t.Fatalf("Failed to decode error response: %v", err)
+
if errResp.Error != "AuthRequired" {
+
t.Errorf("Expected error AuthRequired, got %s", errResp.Error)