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// }