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}