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 SubscribedAt: time.Now(),
262 }
263
264 created, err := repo.Subscribe(ctx, sub)
265 if err != nil {
266 t.Fatalf("Failed to subscribe: %v", err)
267 }
268
269 if created.ID == 0 {
270 t.Error("Expected non-zero subscription ID")
271 }
272 })
273
274 t.Run("prevents duplicate subscriptions", func(t *testing.T) {
275 sub := &communities.Subscription{
276 UserDID: "did:plc:duplicate-sub",
277 CommunityDID: communityDID,
278 SubscribedAt: time.Now(),
279 }
280
281 if _, err := repo.Subscribe(ctx, sub); err != nil {
282 t.Fatalf("First subscription failed: %v", err)
283 }
284
285 // Try to subscribe again
286 _, err := repo.Subscribe(ctx, sub)
287 if err != communities.ErrSubscriptionAlreadyExists {
288 t.Errorf("Expected ErrSubscriptionAlreadyExists, got: %v", err)
289 }
290 })
291
292 t.Run("unsubscribes successfully", func(t *testing.T) {
293 userDID := "did:plc:unsub-user"
294 sub := &communities.Subscription{
295 UserDID: userDID,
296 CommunityDID: communityDID,
297 SubscribedAt: time.Now(),
298 }
299
300 _, err := repo.Subscribe(ctx, sub)
301 if err != nil {
302 t.Fatalf("Failed to subscribe: %v", err)
303 }
304
305 err = repo.Unsubscribe(ctx, userDID, communityDID)
306 if err != nil {
307 t.Fatalf("Failed to unsubscribe: %v", err)
308 }
309
310 // Verify subscription is gone
311 _, err = repo.GetSubscription(ctx, userDID, communityDID)
312 if err != communities.ErrSubscriptionNotFound {
313 t.Errorf("Expected ErrSubscriptionNotFound after unsubscribe, got: %v", err)
314 }
315 })
316}
317
318func TestCommunityRepository_List(t *testing.T) {
319 db := setupTestDB(t)
320 defer func() {
321 if err := db.Close(); err != nil {
322 t.Logf("Failed to close database: %v", err)
323 }
324 }()
325
326 repo := postgres.NewCommunityRepository(db)
327 ctx := context.Background()
328
329 t.Run("lists communities with pagination", func(t *testing.T) {
330 // Create multiple communities
331 baseSuffix := time.Now().UnixNano()
332 for i := 0; i < 5; i++ {
333 uniqueSuffix := fmt.Sprintf("%d%d", baseSuffix, i)
334 communityDID := generateTestDID(uniqueSuffix)
335 community := &communities.Community{
336 DID: communityDID,
337 Handle: fmt.Sprintf("!list-test-%d-%d@coves.local", baseSuffix, i),
338 Name: fmt.Sprintf("list-test-%d", i),
339 OwnerDID: "did:web:coves.local",
340 CreatedByDID: "did:plc:user123",
341 HostedByDID: "did:web:coves.local",
342 Visibility: "public",
343 CreatedAt: time.Now(),
344 UpdatedAt: time.Now(),
345 }
346 if _, err := repo.Create(ctx, community); err != nil {
347 t.Fatalf("Failed to create community %d: %v", i, err)
348 }
349 time.Sleep(10 * time.Millisecond) // Ensure different timestamps
350 }
351
352 // List with limit
353 req := communities.ListCommunitiesRequest{
354 Limit: 3,
355 Offset: 0,
356 }
357
358 results, total, err := repo.List(ctx, req)
359 if err != nil {
360 t.Fatalf("Failed to list communities: %v", err)
361 }
362
363 if len(results) != 3 {
364 t.Errorf("Expected 3 communities, got %d", len(results))
365 }
366
367 if total < 5 {
368 t.Errorf("Expected total >= 5, got %d", total)
369 }
370 })
371
372 t.Run("filters by visibility", func(t *testing.T) {
373 // Create an unlisted community
374 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano())
375 communityDID := generateTestDID(uniqueSuffix)
376 community := &communities.Community{
377 DID: communityDID,
378 Handle: fmt.Sprintf("!unlisted-test-%s@coves.local", uniqueSuffix),
379 Name: "unlisted-test",
380 OwnerDID: "did:web:coves.local",
381 CreatedByDID: "did:plc:user123",
382 HostedByDID: "did:web:coves.local",
383 Visibility: "unlisted",
384 CreatedAt: time.Now(),
385 UpdatedAt: time.Now(),
386 }
387
388 if _, err := repo.Create(ctx, community); err != nil {
389 t.Fatalf("Failed to create unlisted community: %v", err)
390 }
391
392 // List only public communities
393 req := communities.ListCommunitiesRequest{
394 Limit: 100,
395 Offset: 0,
396 Visibility: "public",
397 }
398
399 results, _, err := repo.List(ctx, req)
400 if err != nil {
401 t.Fatalf("Failed to list public communities: %v", err)
402 }
403
404 // Verify no unlisted communities in results
405 for _, c := range results {
406 if c.Visibility != "public" {
407 t.Errorf("Found non-public community in public-only results: %s", c.Handle)
408 }
409 }
410 })
411}
412
413func 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}