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