A community based topic aggregation platform built on atproto
1package integration 2 3import ( 4 "Coves/internal/atproto/jetstream" 5 "Coves/internal/db/postgres" 6 "context" 7 "fmt" 8 "testing" 9 "time" 10) 11 12// TestHostedByVerification_DomainMatching tests that hostedBy domain must match handle domain 13func TestHostedByVerification_DomainMatching(t *testing.T) { 14 db := setupTestDB(t) 15 defer func() { 16 if err := db.Close(); err != nil { 17 t.Logf("Failed to close database: %v", err) 18 } 19 }() 20 21 repo := postgres.NewCommunityRepository(db) 22 ctx := context.Background() 23 24 t.Run("rejects community with mismatched hostedBy domain", func(t *testing.T) { 25 // Create consumer with verification enabled 26 consumer := jetstream.NewCommunityEventConsumer(repo, "did:web:coves.social", false) 27 28 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano()) 29 communityDID := generateTestDID(uniqueSuffix) 30 uniqueHandle := fmt.Sprintf("gaming%s.community.coves.social", uniqueSuffix) 31 32 // Attempt to create community claiming to be hosted by nintendo.com 33 // but with a coves.social handle (ATTACK!) 34 event := &jetstream.JetstreamEvent{ 35 Did: communityDID, 36 TimeUS: time.Now().UnixMicro(), 37 Kind: "commit", 38 Commit: &jetstream.CommitEvent{ 39 Rev: "rev123", 40 Operation: "create", 41 Collection: "social.coves.community.profile", 42 RKey: "self", 43 CID: "bafy123abc", 44 Record: map[string]interface{}{ 45 "handle": uniqueHandle, // coves.social handle 46 "name": "gaming", 47 "displayName": "Nintendo Gaming", 48 "description": "Fake Nintendo community", 49 "createdBy": "did:plc:attacker123", 50 "hostedBy": "did:web:nintendo.com", // ← SPOOFED! Claiming Nintendo hosting 51 "visibility": "public", 52 "federation": map[string]interface{}{ 53 "allowExternalDiscovery": true, 54 }, 55 "memberCount": 0, 56 "subscriberCount": 0, 57 "createdAt": time.Now().Format(time.RFC3339), 58 }, 59 }, 60 } 61 62 // This should fail verification 63 err := consumer.HandleEvent(ctx, event) 64 if err == nil { 65 t.Fatal("Expected verification error for mismatched hostedBy domain, got nil") 66 } 67 68 // Verify error message mentions domain mismatch 69 errMsg := err.Error() 70 if errMsg == "" { 71 t.Fatal("Expected error message, got empty string") 72 } 73 t.Logf("Got expected error: %v", err) 74 75 // Verify community was NOT indexed 76 _, getErr := repo.GetByDID(ctx, communityDID) 77 if getErr == nil { 78 t.Fatal("Community should not have been indexed, but was found in database") 79 } 80 }) 81 82 t.Run("accepts community with matching hostedBy domain", func(t *testing.T) { 83 // Create consumer with verification enabled 84 consumer := jetstream.NewCommunityEventConsumer(repo, "did:web:coves.social", false) 85 86 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano()) 87 communityDID := generateTestDID(uniqueSuffix) 88 uniqueHandle := fmt.Sprintf("gaming%s.community.coves.social", uniqueSuffix) 89 90 // Create community with matching hostedBy and handle domains 91 event := &jetstream.JetstreamEvent{ 92 Did: communityDID, 93 TimeUS: time.Now().UnixMicro(), 94 Kind: "commit", 95 Commit: &jetstream.CommitEvent{ 96 Rev: "rev123", 97 Operation: "create", 98 Collection: "social.coves.community.profile", 99 RKey: "self", 100 CID: "bafy123abc", 101 Record: map[string]interface{}{ 102 "handle": uniqueHandle, // coves.social handle 103 "name": "gaming", 104 "displayName": "Gaming Community", 105 "description": "Legitimate coves.social community", 106 "createdBy": "did:plc:user123", 107 "hostedBy": "did:web:coves.social", // ✅ Matches handle domain 108 "visibility": "public", 109 "federation": map[string]interface{}{ 110 "allowExternalDiscovery": true, 111 }, 112 "memberCount": 0, 113 "subscriberCount": 0, 114 "createdAt": time.Now().Format(time.RFC3339), 115 }, 116 }, 117 } 118 119 // This should succeed 120 err := consumer.HandleEvent(ctx, event) 121 if err != nil { 122 t.Fatalf("Expected verification to succeed, got error: %v", err) 123 } 124 125 // Verify community was indexed 126 community, getErr := repo.GetByDID(ctx, communityDID) 127 if getErr != nil { 128 t.Fatalf("Community should have been indexed: %v", getErr) 129 } 130 if community.HostedByDID != "did:web:coves.social" { 131 t.Errorf("Expected hostedByDID 'did:web:coves.social', got '%s'", community.HostedByDID) 132 } 133 }) 134 135 t.Run("rejects hostedBy with non-did:web format", func(t *testing.T) { 136 // Create consumer with verification enabled 137 consumer := jetstream.NewCommunityEventConsumer(repo, "did:web:coves.social", false) 138 139 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano()) 140 communityDID := generateTestDID(uniqueSuffix) 141 uniqueHandle := fmt.Sprintf("gaming%s.community.coves.social", uniqueSuffix) 142 143 // Attempt to use did:plc for hostedBy (not allowed) 144 event := &jetstream.JetstreamEvent{ 145 Did: communityDID, 146 TimeUS: time.Now().UnixMicro(), 147 Kind: "commit", 148 Commit: &jetstream.CommitEvent{ 149 Rev: "rev123", 150 Operation: "create", 151 Collection: "social.coves.community.profile", 152 RKey: "self", 153 CID: "bafy123abc", 154 Record: map[string]interface{}{ 155 "handle": uniqueHandle, 156 "name": "gaming", 157 "displayName": "Test Community", 158 "description": "Test", 159 "createdBy": "did:plc:user123", 160 "hostedBy": "did:plc:xyz123", // ← Invalid: must be did:web 161 "visibility": "public", 162 "federation": map[string]interface{}{ 163 "allowExternalDiscovery": true, 164 }, 165 "memberCount": 0, 166 "subscriberCount": 0, 167 "createdAt": time.Now().Format(time.RFC3339), 168 }, 169 }, 170 } 171 172 // This should fail verification 173 err := consumer.HandleEvent(ctx, event) 174 if err == nil { 175 t.Fatal("Expected verification error for non-did:web hostedBy, got nil") 176 } 177 t.Logf("Got expected error: %v", err) 178 }) 179 180 t.Run("skip verification flag bypasses all checks", func(t *testing.T) { 181 // Create consumer with verification DISABLED 182 consumer := jetstream.NewCommunityEventConsumer(repo, "did:web:coves.social", true) 183 184 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano()) 185 communityDID := generateTestDID(uniqueSuffix) 186 uniqueHandle := fmt.Sprintf("gaming%s.community.example.com", uniqueSuffix) 187 188 // Even with mismatched domain, this should succeed with skipVerification=true 189 event := &jetstream.JetstreamEvent{ 190 Did: communityDID, 191 TimeUS: time.Now().UnixMicro(), 192 Kind: "commit", 193 Commit: &jetstream.CommitEvent{ 194 Rev: "rev123", 195 Operation: "create", 196 Collection: "social.coves.community.profile", 197 RKey: "self", 198 CID: "bafy123abc", 199 Record: map[string]interface{}{ 200 "handle": uniqueHandle, 201 "name": "gaming", 202 "displayName": "Test", 203 "description": "Test", 204 "createdBy": "did:plc:user123", 205 "hostedBy": "did:web:nintendo.com", // Mismatched, but verification skipped 206 "visibility": "public", 207 "federation": map[string]interface{}{ 208 "allowExternalDiscovery": true, 209 }, 210 "memberCount": 0, 211 "subscriberCount": 0, 212 "createdAt": time.Now().Format(time.RFC3339), 213 }, 214 }, 215 } 216 217 // Should succeed because verification is skipped 218 err := consumer.HandleEvent(ctx, event) 219 if err != nil { 220 t.Fatalf("Expected success with skipVerification=true, got error: %v", err) 221 } 222 223 // Verify community was indexed 224 _, getErr := repo.GetByDID(ctx, communityDID) 225 if getErr != nil { 226 t.Fatalf("Community should have been indexed: %v", getErr) 227 } 228 }) 229} 230 231// TestExtractDomainFromHandle tests the domain extraction logic for various handle formats 232func TestExtractDomainFromHandle(t *testing.T) { 233 // This is an internal function test - we'll test it through the consumer 234 db := setupTestDB(t) 235 defer func() { 236 if err := db.Close(); err != nil { 237 t.Logf("Failed to close database: %v", err) 238 } 239 }() 240 241 repo := postgres.NewCommunityRepository(db) 242 ctx := context.Background() 243 244 testCases := []struct { 245 name string 246 handle string 247 hostedByDID string 248 shouldSucceed bool 249 }{ 250 { 251 name: "DNS-style handle with subdomain", 252 handle: "gaming.community.coves.social", 253 hostedByDID: "did:web:coves.social", 254 shouldSucceed: true, 255 }, 256 { 257 name: "Simple two-part domain", 258 handle: "gaming.coves.social", 259 hostedByDID: "did:web:coves.social", 260 shouldSucceed: true, 261 }, 262 { 263 name: "Multi-part subdomain", 264 handle: "gaming.test.community.example.com", 265 hostedByDID: "did:web:example.com", 266 shouldSucceed: true, 267 }, 268 { 269 name: "Mismatched domain", 270 handle: "gaming.community.coves.social", 271 hostedByDID: "did:web:example.com", 272 shouldSucceed: false, 273 }, 274 // CRITICAL: Multi-part TLD tests (PR review feedback) 275 { 276 name: "Multi-part TLD: .co.uk", 277 handle: "gaming.community.coves.co.uk", 278 hostedByDID: "did:web:coves.co.uk", 279 shouldSucceed: true, 280 }, 281 { 282 name: "Multi-part TLD: .com.au", 283 handle: "gaming.community.example.com.au", 284 hostedByDID: "did:web:example.com.au", 285 shouldSucceed: true, 286 }, 287 { 288 name: "Multi-part TLD: Reject incorrect .co.uk extraction", 289 handle: "gaming.community.coves.co.uk", 290 hostedByDID: "did:web:co.uk", // Wrong! Should be coves.co.uk 291 shouldSucceed: false, 292 }, 293 { 294 name: "Multi-part TLD: .org.uk", 295 handle: "gaming.community.myinstance.org.uk", 296 hostedByDID: "did:web:myinstance.org.uk", 297 shouldSucceed: true, 298 }, 299 { 300 name: "Multi-part TLD: .ac.uk", 301 handle: "gaming.community.university.ac.uk", 302 hostedByDID: "did:web:university.ac.uk", 303 shouldSucceed: true, 304 }, 305 } 306 307 for _, tc := range testCases { 308 t.Run(tc.name, func(t *testing.T) { 309 consumer := jetstream.NewCommunityEventConsumer(repo, "did:web:coves.social", false) 310 311 uniqueSuffix := fmt.Sprintf("%d", time.Now().UnixNano()) 312 communityDID := generateTestDID(uniqueSuffix) 313 314 event := &jetstream.JetstreamEvent{ 315 Did: communityDID, 316 TimeUS: time.Now().UnixMicro(), 317 Kind: "commit", 318 Commit: &jetstream.CommitEvent{ 319 Rev: "rev123", 320 Operation: "create", 321 Collection: "social.coves.community.profile", 322 RKey: "self", 323 CID: "bafy123abc", 324 Record: map[string]interface{}{ 325 "handle": tc.handle, 326 "name": "test", 327 "displayName": "Test", 328 "description": "Test", 329 "createdBy": "did:plc:user123", 330 "hostedBy": tc.hostedByDID, 331 "visibility": "public", 332 "federation": map[string]interface{}{ 333 "allowExternalDiscovery": true, 334 }, 335 "memberCount": 0, 336 "subscriberCount": 0, 337 "createdAt": time.Now().Format(time.RFC3339), 338 }, 339 }, 340 } 341 342 err := consumer.HandleEvent(ctx, event) 343 if tc.shouldSucceed && err != nil { 344 t.Errorf("Expected success for %s, got error: %v", tc.handle, err) 345 } else if !tc.shouldSucceed && err == nil { 346 t.Errorf("Expected failure for %s, got success", tc.handle) 347 } 348 }) 349 } 350}