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}