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