A community based topic aggregation platform built on atproto
1package integration
2
3import (
4 "Coves/internal/core/communities"
5 "Coves/internal/db/postgres"
6 "context"
7 "fmt"
8 "testing"
9 "time"
10)
11
12func TestCommunityRepository_Create(t *testing.T) {
13 db := setupTestDB(t)
14 defer func() {
15 if err := db.Close(); err != nil {
16 t.Logf("Failed to close database: %v", err)
17 }
18 }()
19
20 repo := postgres.NewCommunityRepository(db)
21 ctx := context.Background()
22
23 t.Run("creates community successfully", func(t *testing.T) {
24 // Generate unique handle and DID using timestamp to avoid collisions
25 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
26 communityDID := generateTestDID(uniqueSuffix)
27 community := &communities.Community{
28 DID: communityDID,
29 Handle: fmt.Sprintf("!test-gaming-%s@coves.local", uniqueSuffix),
30 Name: "test-gaming",
31 DisplayName: "Test Gaming Community",
32 Description: "A community for testing",
33 OwnerDID: "did:web:coves.local",
34 CreatedByDID: "did:plc:user123",
35 HostedByDID: "did:web:coves.local",
36 Visibility: "public",
37 AllowExternalDiscovery: true,
38 CreatedAt: time.Now(),
39 UpdatedAt: time.Now(),
40 }
41
42 created, err := repo.Create(ctx, community)
43 if err != nil {
44 t.Fatalf("Failed to create community: %v", err)
45 }
46
47 if created.ID == 0 {
48 t.Error("Expected non-zero ID")
49 }
50 if created.DID != communityDID {
51 t.Errorf("Expected DID %s, got %s", communityDID, created.DID)
52 }
53 })
54
55 t.Run("returns error for duplicate DID", func(t *testing.T) {
56 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
57 communityDID := generateTestDID(uniqueSuffix)
58 community := &communities.Community{
59 DID: communityDID,
60 Handle: fmt.Sprintf("!duplicate-test-%s@coves.local", uniqueSuffix),
61 Name: "duplicate-test",
62 OwnerDID: "did:web:coves.local",
63 CreatedByDID: "did:plc:user123",
64 HostedByDID: "did:web:coves.local",
65 Visibility: "public",
66 CreatedAt: time.Now(),
67 UpdatedAt: time.Now(),
68 }
69
70 // Create first time
71 if _, err := repo.Create(ctx, community); err != nil {
72 t.Fatalf("First create failed: %v", err)
73 }
74
75 // Try to create again with same DID
76 if _, err := repo.Create(ctx, community); err != communities.ErrCommunityAlreadyExists {
77 t.Errorf("Expected ErrCommunityAlreadyExists, got: %v", err)
78 }
79 })
80
81 t.Run("returns error for duplicate handle", func(t *testing.T) {
82 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
83 handle := fmt.Sprintf("!unique-handle-%s@coves.local", uniqueSuffix)
84
85 // First community
86 did1 := generateTestDID(uniqueSuffix + "1")
87 community1 := &communities.Community{
88 DID: did1,
89 Handle: handle,
90 Name: "unique-handle",
91 OwnerDID: "did:web:coves.local",
92 CreatedByDID: "did:plc:user123",
93 HostedByDID: "did:web:coves.local",
94 Visibility: "public",
95 CreatedAt: time.Now(),
96 UpdatedAt: time.Now(),
97 }
98
99 if _, err := repo.Create(ctx, community1); err != nil {
100 t.Fatalf("First create failed: %v", err)
101 }
102
103 // Second community with different DID but same handle
104 did2 := generateTestDID(uniqueSuffix + "2")
105 community2 := &communities.Community{
106 DID: did2,
107 Handle: handle, // Same handle!
108 Name: "unique-handle",
109 OwnerDID: "did:web:coves.local",
110 CreatedByDID: "did:plc:user456",
111 HostedByDID: "did:web:coves.local",
112 Visibility: "public",
113 CreatedAt: time.Now(),
114 UpdatedAt: time.Now(),
115 }
116
117 if _, err := repo.Create(ctx, community2); err != communities.ErrHandleTaken {
118 t.Errorf("Expected ErrHandleTaken, got: %v", err)
119 }
120 })
121}
122
123func TestCommunityRepository_GetByDID(t *testing.T) {
124 db := setupTestDB(t)
125 defer func() {
126 if err := db.Close(); err != nil {
127 t.Logf("Failed to close database: %v", err)
128 }
129 }()
130
131 repo := postgres.NewCommunityRepository(db)
132 ctx := context.Background()
133
134 t.Run("retrieves existing community", func(t *testing.T) {
135 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
136 communityDID := generateTestDID(uniqueSuffix)
137 community := &communities.Community{
138 DID: communityDID,
139 Handle: fmt.Sprintf("!getbyid-test-%s@coves.local", uniqueSuffix),
140 Name: "getbyid-test",
141 DisplayName: "Get By ID Test",
142 Description: "Testing retrieval",
143 OwnerDID: "did:web:coves.local",
144 CreatedByDID: "did:plc:user123",
145 HostedByDID: "did:web:coves.local",
146 Visibility: "public",
147 CreatedAt: time.Now(),
148 UpdatedAt: time.Now(),
149 }
150
151 created, err := repo.Create(ctx, community)
152 if err != nil {
153 t.Fatalf("Failed to create community: %v", err)
154 }
155
156 retrieved, err := repo.GetByDID(ctx, communityDID)
157 if err != nil {
158 t.Fatalf("Failed to get community: %v", err)
159 }
160
161 if retrieved.DID != created.DID {
162 t.Errorf("Expected DID %s, got %s", created.DID, retrieved.DID)
163 }
164 if retrieved.Handle != created.Handle {
165 t.Errorf("Expected Handle %s, got %s", created.Handle, retrieved.Handle)
166 }
167 if retrieved.DisplayName != created.DisplayName {
168 t.Errorf("Expected DisplayName %s, got %s", created.DisplayName, retrieved.DisplayName)
169 }
170 })
171
172 t.Run("returns error for non-existent community", func(t *testing.T) {
173 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
174 fakeDID := generateTestDID(uniqueSuffix)
175 if _, err := repo.GetByDID(ctx, fakeDID); err != communities.ErrCommunityNotFound {
176 t.Errorf("Expected ErrCommunityNotFound, got: %v", err)
177 }
178 })
179}
180
181func TestCommunityRepository_GetByHandle(t *testing.T) {
182 db := setupTestDB(t)
183 defer func() {
184 if err := db.Close(); err != nil {
185 t.Logf("Failed to close database: %v", err)
186 }
187 }()
188
189 repo := postgres.NewCommunityRepository(db)
190 ctx := context.Background()
191
192 t.Run("retrieves community by handle", func(t *testing.T) {
193 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
194 communityDID := generateTestDID(uniqueSuffix)
195 handle := fmt.Sprintf("!handle-lookup-%s@coves.local", uniqueSuffix)
196
197 community := &communities.Community{
198 DID: communityDID,
199 Handle: handle,
200 Name: "handle-lookup",
201 OwnerDID: "did:web:coves.local",
202 CreatedByDID: "did:plc:user123",
203 HostedByDID: "did:web:coves.local",
204 Visibility: "public",
205 CreatedAt: time.Now(),
206 UpdatedAt: time.Now(),
207 }
208
209 if _, err := repo.Create(ctx, community); err != nil {
210 t.Fatalf("Failed to create community: %v", err)
211 }
212
213 retrieved, err := repo.GetByHandle(ctx, handle)
214 if err != nil {
215 t.Fatalf("Failed to get community by handle: %v", err)
216 }
217
218 if retrieved.Handle != handle {
219 t.Errorf("Expected handle %s, got %s", handle, retrieved.Handle)
220 }
221 if retrieved.DID != communityDID {
222 t.Errorf("Expected DID %s, got %s", communityDID, retrieved.DID)
223 }
224 })
225}
226
227func TestCommunityRepository_Subscriptions(t *testing.T) {
228 db := setupTestDB(t)
229 defer func() {
230 if err := db.Close(); err != nil {
231 t.Logf("Failed to close database: %v", err)
232 }
233 }()
234
235 repo := postgres.NewCommunityRepository(db)
236 ctx := context.Background()
237
238 // Create a community for subscription tests
239 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
240 communityDID := generateTestDID(uniqueSuffix)
241 community := &communities.Community{
242 DID: communityDID,
243 Handle: fmt.Sprintf("!subscription-test-%s@coves.local", uniqueSuffix),
244 Name: "subscription-test",
245 OwnerDID: "did:web:coves.local",
246 CreatedByDID: "did:plc:user123",
247 HostedByDID: "did:web:coves.local",
248 Visibility: "public",
249 CreatedAt: time.Now(),
250 UpdatedAt: time.Now(),
251 }
252
253 if _, err := repo.Create(ctx, community); err != nil {
254 t.Fatalf("Failed to create community: %v", err)
255 }
256
257 t.Run("creates subscription successfully", func(t *testing.T) {
258 sub := &communities.Subscription{
259 UserDID: "did:plc:subscriber1",
260 CommunityDID: communityDID,
261 ContentVisibility: 3, // Default visibility
262 SubscribedAt: time.Now(),
263 }
264
265 created, err := repo.Subscribe(ctx, sub)
266 if err != nil {
267 t.Fatalf("Failed to subscribe: %v", err)
268 }
269
270 if created.ID == 0 {
271 t.Error("Expected non-zero subscription ID")
272 }
273 })
274
275 t.Run("prevents duplicate subscriptions", func(t *testing.T) {
276 sub := &communities.Subscription{
277 UserDID: "did:plc:duplicate-sub",
278 CommunityDID: communityDID,
279 ContentVisibility: 3, // Default visibility
280 SubscribedAt: time.Now(),
281 }
282
283 if _, err := repo.Subscribe(ctx, sub); err != nil {
284 t.Fatalf("First subscription failed: %v", err)
285 }
286
287 // Try to subscribe again
288 _, err := repo.Subscribe(ctx, sub)
289 if err != communities.ErrSubscriptionAlreadyExists {
290 t.Errorf("Expected ErrSubscriptionAlreadyExists, got: %v", err)
291 }
292 })
293
294 t.Run("unsubscribes successfully", func(t *testing.T) {
295 userDID := "did:plc:unsub-user"
296 sub := &communities.Subscription{
297 UserDID: userDID,
298 CommunityDID: communityDID,
299 ContentVisibility: 3, // Default visibility
300 SubscribedAt: time.Now(),
301 }
302
303 _, err := repo.Subscribe(ctx, sub)
304 if err != nil {
305 t.Fatalf("Failed to subscribe: %v", err)
306 }
307
308 err = repo.Unsubscribe(ctx, userDID, communityDID)
309 if err != nil {
310 t.Fatalf("Failed to unsubscribe: %v", err)
311 }
312
313 // Verify subscription is gone
314 _, err = repo.GetSubscription(ctx, userDID, communityDID)
315 if err != communities.ErrSubscriptionNotFound {
316 t.Errorf("Expected ErrSubscriptionNotFound after unsubscribe, got: %v", err)
317 }
318 })
319}
320
321func TestCommunityRepository_List(t *testing.T) {
322 db := setupTestDB(t)
323 defer func() {
324 if err := db.Close(); err != nil {
325 t.Logf("Failed to close database: %v", err)
326 }
327 }()
328
329 repo := postgres.NewCommunityRepository(db)
330 ctx := context.Background()
331
332 t.Run("lists communities with pagination", func(t *testing.T) {
333 // Create multiple communities
334 baseSuffix := time.Now().UnixNano()
335 for i := 0; i < 5; i++ {
336 uniqueSuffix := fmt.Sprintf("%d%d", baseSuffix, i)
337 communityDID := generateTestDID(uniqueSuffix)
338 community := &communities.Community{
339 DID: communityDID,
340 Handle: fmt.Sprintf("!list-test-%d-%d@coves.local", baseSuffix, i),
341 Name: fmt.Sprintf("list-test-%d", i),
342 OwnerDID: "did:web:coves.local",
343 CreatedByDID: "did:plc:user123",
344 HostedByDID: "did:web:coves.local",
345 Visibility: "public",
346 CreatedAt: time.Now(),
347 UpdatedAt: time.Now(),
348 }
349 if _, err := repo.Create(ctx, community); err != nil {
350 t.Fatalf("Failed to create community %d: %v", i, err)
351 }
352 time.Sleep(10 * time.Millisecond) // Ensure different timestamps
353 }
354
355 // List with limit
356 req := communities.ListCommunitiesRequest{
357 Limit: 3,
358 Offset: 0,
359 }
360
361 results, err := repo.List(ctx, req)
362 if err != nil {
363 t.Fatalf("Failed to list communities: %v", err)
364 }
365
366 if len(results) != 3 {
367 t.Errorf("Expected 3 communities, got %d", len(results))
368 }
369 })
370
371 t.Run("filters by visibility", func(t *testing.T) {
372 // Create an unlisted community
373 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
374 communityDID := generateTestDID(uniqueSuffix)
375 community := &communities.Community{
376 DID: communityDID,
377 Handle: fmt.Sprintf("!unlisted-test-%s@coves.local", uniqueSuffix),
378 Name: "unlisted-test",
379 OwnerDID: "did:web:coves.local",
380 CreatedByDID: "did:plc:user123",
381 HostedByDID: "did:web:coves.local",
382 Visibility: "unlisted",
383 CreatedAt: time.Now(),
384 UpdatedAt: time.Now(),
385 }
386
387 if _, err := repo.Create(ctx, community); err != nil {
388 t.Fatalf("Failed to create unlisted community: %v", err)
389 }
390
391 // List only public communities
392 req := communities.ListCommunitiesRequest{
393 Limit: 100,
394 Offset: 0,
395 Visibility: "public",
396 }
397
398 results, err := repo.List(ctx, req)
399 if err != nil {
400 t.Fatalf("Failed to list public communities: %v", err)
401 }
402
403 // Verify no unlisted communities in results
404 for _, c := range results {
405 if c.Visibility != "public" {
406 t.Errorf("Found non-public community in public-only results: %s", c.Handle)
407 }
408 }
409 })
410}
411
412// TODO: Implement search functionality before re-enabling this test
413// func TestCommunityRepository_Search(t *testing.T) {
414// db := setupTestDB(t)
415// defer func() {
416// if err := db.Close(); err != nil {
417// t.Logf("Failed to close database: %v", err)
418// }
419// }()
420//
421// repo := postgres.NewCommunityRepository(db)
422// ctx := context.Background()
423//
424// t.Run("searches communities by name", func(t *testing.T) {
425// // Create a community with searchable name
426// uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
427// communityDID := generateTestDID(uniqueSuffix)
428// community := &communities.Community{
429// DID: communityDID,
430// Handle: fmt.Sprintf("!golang-search-%s@coves.local", uniqueSuffix),
431// Name: "golang-search",
432// DisplayName: "Go Programming",
433// Description: "A community for Go developers",
434// OwnerDID: "did:web:coves.local",
435// CreatedByDID: "did:plc:user123",
436// HostedByDID: "did:web:coves.local",
437// Visibility: "public",
438// CreatedAt: time.Now(),
439// UpdatedAt: time.Now(),
440// }
441//
442// if _, err := repo.Create(ctx, community); err != nil {
443// t.Fatalf("Failed to create community: %v", err)
444// }
445//
446// // Search for it
447// req := communities.SearchCommunitiesRequest{
448// Query: "golang",
449// Limit: 10,
450// Offset: 0,
451// }
452//
453// results, total, err := repo.Search(ctx, req)
454// if err != nil {
455// t.Fatalf("Failed to search communities: %v", err)
456// }
457//
458// if total == 0 {
459// t.Error("Expected to find at least one result")
460// }
461//
462// // Verify our community is in results
463// found := false
464// for _, c := range results {
465// if c.DID == communityDID {
466// found = true
467// break
468// }
469// }
470//
471// if !found {
472// t.Error("Expected to find created community in search results")
473// }
474// })
475// }