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}