···
12
+
"Coves/internal/api/middleware"
13
+
"Coves/internal/core/communities"
16
+
// mockCommunityService implements communities.Service for testing
17
+
type mockCommunityService struct {
18
+
createFunc func(ctx context.Context, req communities.CreateCommunityRequest) (*communities.Community, error)
21
+
func (m *mockCommunityService) CreateCommunity(ctx context.Context, req communities.CreateCommunityRequest) (*communities.Community, error) {
22
+
if m.createFunc != nil {
23
+
return m.createFunc(ctx, req)
25
+
return &communities.Community{
26
+
DID: "did:plc:test123",
27
+
Handle: "test.community.coves.social",
28
+
RecordURI: "at://did:plc:test123/social.coves.community.profile/self",
29
+
RecordCID: "bafytest123",
30
+
DisplayName: req.DisplayName,
31
+
Description: req.Description,
32
+
Visibility: req.Visibility,
33
+
CreatedAt: time.Now(),
37
+
func (m *mockCommunityService) GetCommunity(ctx context.Context, identifier string) (*communities.Community, error) {
41
+
func (m *mockCommunityService) UpdateCommunity(ctx context.Context, req communities.UpdateCommunityRequest) (*communities.Community, error) {
45
+
func (m *mockCommunityService) ListCommunities(ctx context.Context, req communities.ListCommunitiesRequest) ([]*communities.Community, int, error) {
49
+
func (m *mockCommunityService) SearchCommunities(ctx context.Context, req communities.SearchCommunitiesRequest) ([]*communities.Community, int, error) {
53
+
func (m *mockCommunityService) SubscribeToCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string, contentVisibility int) (*communities.Subscription, error) {
57
+
func (m *mockCommunityService) UnsubscribeFromCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string) error {
61
+
func (m *mockCommunityService) GetUserSubscriptions(ctx context.Context, userDID string, limit, offset int) ([]*communities.Subscription, error) {
65
+
func (m *mockCommunityService) GetCommunitySubscribers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*communities.Subscription, error) {
69
+
func (m *mockCommunityService) BlockCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string) (*communities.CommunityBlock, error) {
73
+
func (m *mockCommunityService) UnblockCommunity(ctx context.Context, userDID, accessToken, communityIdentifier string) error {
77
+
func (m *mockCommunityService) GetBlockedCommunities(ctx context.Context, userDID string, limit, offset int) ([]*communities.CommunityBlock, error) {
81
+
func (m *mockCommunityService) IsBlocked(ctx context.Context, userDID, communityIdentifier string) (bool, error) {
85
+
func (m *mockCommunityService) GetMembership(ctx context.Context, userDID, communityIdentifier string) (*communities.Membership, error) {
89
+
func (m *mockCommunityService) ListCommunityMembers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*communities.Membership, error) {
93
+
func (m *mockCommunityService) ValidateHandle(handle string) error {
97
+
func (m *mockCommunityService) ResolveCommunityIdentifier(ctx context.Context, identifier string) (string, error) {
98
+
return identifier, nil
101
+
func (m *mockCommunityService) EnsureFreshToken(ctx context.Context, community *communities.Community) (*communities.Community, error) {
102
+
return community, nil
105
+
func (m *mockCommunityService) GetByDID(ctx context.Context, did string) (*communities.Community, error) {
109
+
func TestCreateHandler_AllowlistRestriction(t *testing.T) {
110
+
mockService := &mockCommunityService{}
112
+
tests := []struct {
114
+
allowedDIDs []string
117
+
expectedError string
120
+
name: "allowed DID can create community",
121
+
allowedDIDs: []string{"did:plc:allowed123"},
122
+
requestDID: "did:plc:allowed123",
123
+
expectedStatus: http.StatusOK,
126
+
name: "non-allowed DID is forbidden",
127
+
allowedDIDs: []string{"did:plc:allowed123"},
128
+
requestDID: "did:plc:notallowed456",
129
+
expectedStatus: http.StatusForbidden,
130
+
expectedError: "CommunityCreationRestricted",
133
+
name: "empty allowlist allows anyone",
135
+
requestDID: "did:plc:anyuser789",
136
+
expectedStatus: http.StatusOK,
139
+
name: "multiple allowed DIDs - first DID",
140
+
allowedDIDs: []string{"did:plc:admin1", "did:plc:admin2", "did:plc:admin3"},
141
+
requestDID: "did:plc:admin1",
142
+
expectedStatus: http.StatusOK,
145
+
name: "multiple allowed DIDs - last DID",
146
+
allowedDIDs: []string{"did:plc:admin1", "did:plc:admin2", "did:plc:admin3"},
147
+
requestDID: "did:plc:admin3",
148
+
expectedStatus: http.StatusOK,
151
+
name: "multiple allowed DIDs - not in list",
152
+
allowedDIDs: []string{"did:plc:admin1", "did:plc:admin2"},
153
+
requestDID: "did:plc:randomuser",
154
+
expectedStatus: http.StatusForbidden,
155
+
expectedError: "CommunityCreationRestricted",
158
+
name: "allowlist with empty strings filtered - valid DID works",
159
+
allowedDIDs: []string{"did:plc:admin1", "", "did:plc:admin2"},
160
+
requestDID: "did:plc:admin1",
161
+
expectedStatus: http.StatusOK,
164
+
name: "allowlist with empty strings filtered - invalid DID blocked",
165
+
allowedDIDs: []string{"did:plc:admin1", "", "did:plc:admin2"},
166
+
requestDID: "did:plc:notallowed",
167
+
expectedStatus: http.StatusForbidden,
168
+
expectedError: "CommunityCreationRestricted",
171
+
name: "all empty strings allows anyone",
172
+
allowedDIDs: []string{"", "", ""},
173
+
requestDID: "did:plc:anyuser",
174
+
expectedStatus: http.StatusOK,
178
+
for _, tc := range tests {
179
+
t.Run(tc.name, func(t *testing.T) {
180
+
handler := NewCreateHandler(mockService, tc.allowedDIDs)
182
+
// Create request body
183
+
reqBody := map[string]interface{}{
184
+
"name": "testcommunity",
185
+
"displayName": "Test Community",
186
+
"description": "Test description",
187
+
"visibility": "public",
188
+
"allowExternalDiscovery": true,
190
+
bodyBytes, err := json.Marshal(reqBody)
192
+
t.Fatalf("Failed to marshal request: %v", err)
195
+
// Create HTTP request
196
+
req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.create", bytes.NewBuffer(bodyBytes))
197
+
req.Header.Set("Content-Type", "application/json")
199
+
// Inject user DID into context (simulates auth middleware)
200
+
ctx := context.WithValue(req.Context(), middleware.UserDIDKey, tc.requestDID)
201
+
req = req.WithContext(ctx)
204
+
w := httptest.NewRecorder()
205
+
handler.HandleCreate(w, req)
207
+
// Check status code
208
+
if w.Code != tc.expectedStatus {
209
+
t.Errorf("Expected status %d, got %d. Body: %s", tc.expectedStatus, w.Code, w.Body.String())
212
+
// Check error response if expected
213
+
if tc.expectedError != "" {
214
+
var errResp struct {
215
+
Error string `json:"error"`
216
+
Message string `json:"message"`
218
+
if err := json.NewDecoder(w.Body).Decode(&errResp); err != nil {
219
+
t.Fatalf("Failed to decode error response: %v", err)
221
+
if errResp.Error != tc.expectedError {
222
+
t.Errorf("Expected error %s, got %s", tc.expectedError, errResp.Error)
229
+
func TestCreateHandler_RequiresAuth(t *testing.T) {
230
+
mockService := &mockCommunityService{}
231
+
handler := NewCreateHandler(mockService, nil)
233
+
// Create request without auth context
234
+
reqBody := map[string]interface{}{
235
+
"name": "testcommunity",
236
+
"displayName": "Test Community",
237
+
"description": "Test description",
238
+
"visibility": "public",
240
+
bodyBytes, err := json.Marshal(reqBody)
242
+
t.Fatalf("Failed to marshal request: %v", err)
245
+
req := httptest.NewRequest(http.MethodPost, "/xrpc/social.coves.community.create", bytes.NewBuffer(bodyBytes))
246
+
req.Header.Set("Content-Type", "application/json")
247
+
// No user DID in context
249
+
w := httptest.NewRecorder()
250
+
handler.HandleCreate(w, req)
252
+
if w.Code != http.StatusUnauthorized {
253
+
t.Errorf("Expected status 401, got %d. Body: %s", w.Code, w.Body.String())
256
+
var errResp struct {
257
+
Error string `json:"error"`
259
+
if err := json.NewDecoder(w.Body).Decode(&errResp); err != nil {
260
+
t.Fatalf("Failed to decode error response: %v", err)
262
+
if errResp.Error != "AuthRequired" {
263
+
t.Errorf("Expected error AuthRequired, got %s", errResp.Error)