A community based topic aggregation platform built on atproto

test: update E2E tests for OAuth authentication

- Update test helpers for new OAuth flow
- Adapt aggregator, community, post tests
- Update user journey tests

šŸ¤– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+201 -104
+20 -19
tests/integration/aggregator_e2e_test.go
··· 3 import ( 4 "Coves/internal/api/handlers/aggregator" 5 "Coves/internal/api/handlers/post" 6 - "Coves/internal/api/middleware" 7 "Coves/internal/atproto/identity" 8 "Coves/internal/atproto/jetstream" 9 "Coves/internal/core/aggregators" ··· 82 getAuthorizationsHandler := aggregator.NewGetAuthorizationsHandler(aggregatorService) 83 listForCommunityHandler := aggregator.NewListForCommunityHandler(aggregatorService) 84 createPostHandler := post.NewCreateHandler(postService) 85 - authMiddleware := middleware.NewAtProtoAuthMiddleware(nil, true) // Skip JWT verification for testing 86 - defer authMiddleware.Stop() // Clean up DPoP replay cache goroutine 87 88 ctx := context.Background() 89 ··· 99 // Part 1: Service Declaration via Real PDS 100 // ==================================================================================== 101 // Store DIDs, tokens, and URIs for use across all test parts 102 - var aggregatorDID, aggregatorToken, aggregatorHandle, communityDID, communityToken, authorizationRkey string 103 104 t.Run("1. Service Declaration - PDS Account → Write Record → Jetstream → AppView DB", func(t *testing.T) { 105 t.Log("\nšŸ“ Part 1: Create aggregator account and publish service declaration to PDS...") ··· 118 require.NotEmpty(t, aggregatorDID, "Should receive DID") 119 120 t.Logf("āœ“ Created aggregator account: %s (%s)", aggregatorHandle, aggregatorDID) 121 122 // STEP 2: Write service declaration to aggregator's repository on PDS 123 configSchema := map[string]interface{}{ ··· 335 336 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 337 req.Header.Set("Content-Type", "application/json") 338 - 339 - // Create JWT for aggregator (not a user) 340 - aggregatorJWT := createSimpleTestJWT(aggregatorDID) 341 - req.Header.Set("Authorization", "DPoP "+aggregatorJWT) 342 343 // Execute request through auth middleware + handler 344 rr := httptest.NewRecorder() 345 - handler := authMiddleware.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 346 handler.ServeHTTP(rr, req) 347 348 // STEP 2: Verify post creation succeeded ··· 425 426 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 427 req.Header.Set("Content-Type", "application/json") 428 - req.Header.Set("Authorization", "DPoP "+createSimpleTestJWT(aggregatorDID)) 429 430 rr := httptest.NewRecorder() 431 - handler := authMiddleware.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 432 handler.ServeHTTP(rr, req) 433 434 require.Equal(t, http.StatusOK, rr.Code, "Post %d should succeed", i) ··· 447 448 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 449 req.Header.Set("Content-Type", "application/json") 450 - req.Header.Set("Authorization", "DPoP "+createSimpleTestJWT(aggregatorDID)) 451 452 rr := httptest.NewRecorder() 453 - handler := authMiddleware.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 454 handler.ServeHTTP(rr, req) 455 456 require.Equal(t, http.StatusOK, rr.Code, "10th post should succeed (at limit)") ··· 468 469 req = httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 470 req.Header.Set("Content-Type", "application/json") 471 - req.Header.Set("Authorization", "DPoP "+createSimpleTestJWT(aggregatorDID)) 472 473 rr = httptest.NewRecorder() 474 - handler = authMiddleware.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 475 handler.ServeHTTP(rr, req) 476 477 // Should be rate limited ··· 649 err := aggregatorConsumer.HandleEvent(ctx, &unAuthAggEvent) 650 require.NoError(t, err) 651 652 // Try to create post without authorization 653 reqBody := map[string]interface{}{ 654 "community": communityDID, ··· 660 661 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 662 req.Header.Set("Content-Type", "application/json") 663 - req.Header.Set("Authorization", "DPoP "+createSimpleTestJWT(unauthorizedAggDID)) 664 665 rr := httptest.NewRecorder() 666 - handler := authMiddleware.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 667 handler.ServeHTTP(rr, req) 668 669 // Should be forbidden ··· 784 785 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 786 req.Header.Set("Content-Type", "application/json") 787 - req.Header.Set("Authorization", "DPoP "+createSimpleTestJWT(aggregatorDID)) 788 789 rr := httptest.NewRecorder() 790 - handler := authMiddleware.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 791 handler.ServeHTTP(rr, req) 792 793 assert.Equal(t, http.StatusForbidden, rr.Code, "Should reject post from disabled aggregator")
··· 3 import ( 4 "Coves/internal/api/handlers/aggregator" 5 "Coves/internal/api/handlers/post" 6 "Coves/internal/atproto/identity" 7 "Coves/internal/atproto/jetstream" 8 "Coves/internal/core/aggregators" ··· 81 getAuthorizationsHandler := aggregator.NewGetAuthorizationsHandler(aggregatorService) 82 listForCommunityHandler := aggregator.NewListForCommunityHandler(aggregatorService) 83 createPostHandler := post.NewCreateHandler(postService) 84 + e2eAuth := NewE2EOAuthMiddleware() 85 86 ctx := context.Background() 87 ··· 97 // Part 1: Service Declaration via Real PDS 98 // ==================================================================================== 99 // Store DIDs, tokens, and URIs for use across all test parts 100 + var aggregatorDID, aggregatorToken, aggregatorAPIToken, aggregatorHandle, communityDID, communityToken, authorizationRkey string 101 102 t.Run("1. Service Declaration - PDS Account → Write Record → Jetstream → AppView DB", func(t *testing.T) { 103 t.Log("\nšŸ“ Part 1: Create aggregator account and publish service declaration to PDS...") ··· 116 require.NotEmpty(t, aggregatorDID, "Should receive DID") 117 118 t.Logf("āœ“ Created aggregator account: %s (%s)", aggregatorHandle, aggregatorDID) 119 + 120 + // Register aggregator user with OAuth middleware for API requests 121 + aggregatorAPIToken = e2eAuth.AddUser(aggregatorDID) 122 123 // STEP 2: Write service declaration to aggregator's repository on PDS 124 configSchema := map[string]interface{}{ ··· 336 337 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 338 req.Header.Set("Content-Type", "application/json") 339 + req.Header.Set("Authorization", "Bearer "+aggregatorAPIToken) 340 341 // Execute request through auth middleware + handler 342 rr := httptest.NewRecorder() 343 + handler := e2eAuth.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 344 handler.ServeHTTP(rr, req) 345 346 // STEP 2: Verify post creation succeeded ··· 423 424 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 425 req.Header.Set("Content-Type", "application/json") 426 + req.Header.Set("Authorization", "Bearer "+aggregatorAPIToken) 427 428 rr := httptest.NewRecorder() 429 + handler := e2eAuth.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 430 handler.ServeHTTP(rr, req) 431 432 require.Equal(t, http.StatusOK, rr.Code, "Post %d should succeed", i) ··· 445 446 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 447 req.Header.Set("Content-Type", "application/json") 448 + req.Header.Set("Authorization", "Bearer "+aggregatorAPIToken) 449 450 rr := httptest.NewRecorder() 451 + handler := e2eAuth.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 452 handler.ServeHTTP(rr, req) 453 454 require.Equal(t, http.StatusOK, rr.Code, "10th post should succeed (at limit)") ··· 466 467 req = httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 468 req.Header.Set("Content-Type", "application/json") 469 + req.Header.Set("Authorization", "Bearer "+aggregatorAPIToken) 470 471 rr = httptest.NewRecorder() 472 + handler = e2eAuth.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 473 handler.ServeHTTP(rr, req) 474 475 // Should be rate limited ··· 647 err := aggregatorConsumer.HandleEvent(ctx, &unAuthAggEvent) 648 require.NoError(t, err) 649 650 + // Register unauthorized aggregator with OAuth middleware 651 + unauthorizedAPIToken := e2eAuth.AddUser(unauthorizedAggDID) 652 + 653 // Try to create post without authorization 654 reqBody := map[string]interface{}{ 655 "community": communityDID, ··· 661 662 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 663 req.Header.Set("Content-Type", "application/json") 664 + req.Header.Set("Authorization", "Bearer "+unauthorizedAPIToken) 665 666 rr := httptest.NewRecorder() 667 + handler := e2eAuth.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 668 handler.ServeHTTP(rr, req) 669 670 // Should be forbidden ··· 785 786 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 787 req.Header.Set("Content-Type", "application/json") 788 + req.Header.Set("Authorization", "Bearer "+aggregatorAPIToken) 789 790 rr := httptest.NewRecorder() 791 + handler := e2eAuth.RequireAuth(http.HandlerFunc(createPostHandler.HandleCreate)) 792 handler.ServeHTTP(rr, req) 793 794 assert.Equal(t, http.StatusForbidden, rr.Code, "Should reject post from disabled aggregator")
+16 -20
tests/integration/community_e2e_test.go
··· 1 package integration 2 3 import ( 4 - "Coves/internal/api/middleware" 5 "Coves/internal/api/routes" 6 "Coves/internal/atproto/identity" 7 "Coves/internal/atproto/jetstream" ··· 108 109 t.Logf("āœ… Authenticated - Instance DID: %s", instanceDID) 110 111 - // Initialize auth middleware with skipVerify=true 112 - // IMPORTANT: PDS password authentication returns Bearer tokens (not DPoP-bound tokens). 113 - // E2E tests use these Bearer tokens with the DPoP scheme header, which only works 114 - // because skipVerify=true bypasses signature and DPoP binding verification. 115 - // In production, skipVerify=false requires proper DPoP-bound tokens from OAuth flow. 116 - authMiddleware := middleware.NewAtProtoAuthMiddleware(nil, true) 117 - defer authMiddleware.Stop() // Clean up DPoP replay cache goroutine 118 119 // V2.0: Extract instance domain for community provisioning 120 var instanceDomain string ··· 152 153 // Setup HTTP server with XRPC routes 154 r := chi.NewRouter() 155 - routes.RegisterCommunityRoutes(r, communityService, authMiddleware, nil) // nil = allow all community creators 156 httpServer := httptest.NewServer(r) 157 defer httpServer.Close() 158 ··· 387 t.Fatalf("Failed to create request: %v", err) 388 } 389 req.Header.Set("Content-Type", "application/json") 390 - // Use real PDS access token for E2E authentication 391 - req.Header.Set("Authorization", "DPoP "+accessToken) 392 393 resp, err := http.DefaultClient.Do(req) 394 if err != nil { ··· 773 t.Fatalf("Failed to create request: %v", err) 774 } 775 req.Header.Set("Content-Type", "application/json") 776 - // Use real PDS access token for E2E authentication 777 - req.Header.Set("Authorization", "DPoP "+accessToken) 778 779 resp, err := http.DefaultClient.Do(req) 780 if err != nil { ··· 1008 t.Fatalf("Failed to create request: %v", err) 1009 } 1010 req.Header.Set("Content-Type", "application/json") 1011 - // Use real PDS access token for E2E authentication 1012 - req.Header.Set("Authorization", "DPoP "+accessToken) 1013 1014 resp, err := http.DefaultClient.Do(req) 1015 if err != nil { ··· 1141 t.Fatalf("Failed to create block request: %v", err) 1142 } 1143 req.Header.Set("Content-Type", "application/json") 1144 - req.Header.Set("Authorization", "DPoP "+accessToken) 1145 1146 resp, err := http.DefaultClient.Do(req) 1147 if err != nil { ··· 1261 t.Fatalf("Failed to create block request: %v", err) 1262 } 1263 blockHttpReq.Header.Set("Content-Type", "application/json") 1264 - blockHttpReq.Header.Set("Authorization", "DPoP "+accessToken) 1265 1266 blockResp, err := http.DefaultClient.Do(blockHttpReq) 1267 if err != nil { ··· 1321 t.Fatalf("Failed to create unblock request: %v", err) 1322 } 1323 req.Header.Set("Content-Type", "application/json") 1324 - req.Header.Set("Authorization", "DPoP "+accessToken) 1325 1326 resp, err := http.DefaultClient.Do(req) 1327 if err != nil { ··· 1477 t.Fatalf("Failed to create request: %v", err) 1478 } 1479 req.Header.Set("Content-Type", "application/json") 1480 - // Use real PDS access token for E2E authentication 1481 - req.Header.Set("Authorization", "DPoP "+accessToken) 1482 1483 resp, err := http.DefaultClient.Do(req) 1484 if err != nil {
··· 1 package integration 2 3 import ( 4 "Coves/internal/api/routes" 5 "Coves/internal/atproto/identity" 6 "Coves/internal/atproto/jetstream" ··· 107 108 t.Logf("āœ… Authenticated - Instance DID: %s", instanceDID) 109 110 + // Initialize OAuth auth middleware for E2E testing 111 + e2eAuth := NewE2EOAuthMiddleware() 112 + // Register the instance user for OAuth authentication 113 + token := e2eAuth.AddUser(instanceDID) 114 115 // V2.0: Extract instance domain for community provisioning 116 var instanceDomain string ··· 148 149 // Setup HTTP server with XRPC routes 150 r := chi.NewRouter() 151 + routes.RegisterCommunityRoutes(r, communityService, e2eAuth.OAuthAuthMiddleware, nil) // nil = allow all community creators 152 httpServer := httptest.NewServer(r) 153 defer httpServer.Close() 154 ··· 383 t.Fatalf("Failed to create request: %v", err) 384 } 385 req.Header.Set("Content-Type", "application/json") 386 + // Use OAuth token for Coves API authentication 387 + req.Header.Set("Authorization", "Bearer "+token) 388 389 resp, err := http.DefaultClient.Do(req) 390 if err != nil { ··· 769 t.Fatalf("Failed to create request: %v", err) 770 } 771 req.Header.Set("Content-Type", "application/json") 772 + // Use OAuth token for Coves API authentication 773 + req.Header.Set("Authorization", "Bearer "+token) 774 775 resp, err := http.DefaultClient.Do(req) 776 if err != nil { ··· 1004 t.Fatalf("Failed to create request: %v", err) 1005 } 1006 req.Header.Set("Content-Type", "application/json") 1007 + // Use OAuth token for Coves API authentication 1008 + req.Header.Set("Authorization", "Bearer "+token) 1009 1010 resp, err := http.DefaultClient.Do(req) 1011 if err != nil { ··· 1137 t.Fatalf("Failed to create block request: %v", err) 1138 } 1139 req.Header.Set("Content-Type", "application/json") 1140 + req.Header.Set("Authorization", "Bearer "+token) 1141 1142 resp, err := http.DefaultClient.Do(req) 1143 if err != nil { ··· 1257 t.Fatalf("Failed to create block request: %v", err) 1258 } 1259 blockHttpReq.Header.Set("Content-Type", "application/json") 1260 + blockHttpReq.Header.Set("Authorization", "Bearer "+token) 1261 1262 blockResp, err := http.DefaultClient.Do(blockHttpReq) 1263 if err != nil { ··· 1317 t.Fatalf("Failed to create unblock request: %v", err) 1318 } 1319 req.Header.Set("Content-Type", "application/json") 1320 + req.Header.Set("Authorization", "Bearer "+token) 1321 1322 resp, err := http.DefaultClient.Do(req) 1323 if err != nil { ··· 1473 t.Fatalf("Failed to create request: %v", err) 1474 } 1475 req.Header.Set("Content-Type", "application/json") 1476 + // Use OAuth token for Coves API authentication 1477 + req.Header.Set("Authorization", "Bearer "+token) 1478 1479 resp, err := http.DefaultClient.Do(req) 1480 if err != nil {
+136 -37
tests/integration/helpers.go
··· 1 package integration 2 3 import ( 4 - "Coves/internal/atproto/auth" 5 "Coves/internal/core/users" 6 "bytes" 7 "context" 8 "database/sql" 9 - "encoding/base64" 10 "encoding/json" 11 "fmt" 12 "io" ··· 16 "testing" 17 "time" 18 19 - "github.com/golang-jwt/jwt/v5" 20 ) 21 22 // getTestPDSURL returns the PDS URL for testing from env var or default ··· 113 } 114 115 return sessionResp.AccessJwt, sessionResp.DID, nil 116 - } 117 - 118 - // createSimpleTestJWT creates a minimal JWT for testing (Phase 1 - no signature) 119 - // In production, this would be a real OAuth token from PDS with proper signatures 120 - func createSimpleTestJWT(userDID string) string { 121 - // Create minimal JWT claims using RegisteredClaims 122 - // Use userDID as issuer since we don't have a proper PDS DID for testing 123 - claims := auth.Claims{ 124 - RegisteredClaims: jwt.RegisteredClaims{ 125 - Subject: userDID, 126 - Issuer: userDID, // Use DID as issuer for testing (valid per atProto) 127 - Audience: jwt.ClaimStrings{getTestInstanceDID()}, 128 - IssuedAt: jwt.NewNumericDate(time.Now()), 129 - ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), 130 - }, 131 - Scope: "com.atproto.access", 132 - } 133 - 134 - // For Phase 1 testing, we create an unsigned JWT 135 - // The middleware is configured with skipVerify=true for testing 136 - header := map[string]interface{}{ 137 - "alg": "none", 138 - "typ": "JWT", 139 - } 140 - 141 - headerJSON, _ := json.Marshal(header) 142 - claimsJSON, _ := json.Marshal(claims) 143 - 144 - // Base64url encode (without padding) 145 - headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON) 146 - claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON) 147 - 148 - // For "alg: none", signature is empty 149 - return headerB64 + "." + claimsB64 + "." 150 } 151 152 // generateTID generates a simple timestamp-based identifier for testing ··· 310 311 return uri 312 }
··· 1 package integration 2 3 import ( 4 + "Coves/internal/api/middleware" 5 + "Coves/internal/atproto/oauth" 6 "Coves/internal/core/users" 7 "bytes" 8 "context" 9 "database/sql" 10 "encoding/json" 11 "fmt" 12 "io" ··· 16 "testing" 17 "time" 18 19 + oauthlib "github.com/bluesky-social/indigo/atproto/auth/oauth" 20 + "github.com/bluesky-social/indigo/atproto/syntax" 21 ) 22 23 // getTestPDSURL returns the PDS URL for testing from env var or default ··· 114 } 115 116 return sessionResp.AccessJwt, sessionResp.DID, nil 117 } 118 119 // generateTID generates a simple timestamp-based identifier for testing ··· 277 278 return uri 279 } 280 + 281 + // MockSessionUnsealer is a mock implementation of SessionUnsealer for testing 282 + // It returns predefined sessions based on token value 283 + type MockSessionUnsealer struct { 284 + sessions map[string]*oauth.SealedSession 285 + } 286 + 287 + // NewMockSessionUnsealer creates a new mock unsealer 288 + func NewMockSessionUnsealer() *MockSessionUnsealer { 289 + return &MockSessionUnsealer{ 290 + sessions: make(map[string]*oauth.SealedSession), 291 + } 292 + } 293 + 294 + // AddSession adds a token -> session mapping 295 + func (m *MockSessionUnsealer) AddSession(token, did, sessionID string) { 296 + m.sessions[token] = &oauth.SealedSession{ 297 + DID: did, 298 + SessionID: sessionID, 299 + ExpiresAt: time.Now().Add(1 * time.Hour).Unix(), 300 + } 301 + } 302 + 303 + // UnsealSession returns the predefined session for a token 304 + func (m *MockSessionUnsealer) UnsealSession(token string) (*oauth.SealedSession, error) { 305 + if sess, ok := m.sessions[token]; ok { 306 + return sess, nil 307 + } 308 + return nil, fmt.Errorf("unknown token") 309 + } 310 + 311 + // MockOAuthStore is a mock implementation of ClientAuthStore for testing 312 + type MockOAuthStore struct { 313 + sessions map[string]*oauthlib.ClientSessionData 314 + } 315 + 316 + // NewMockOAuthStore creates a new mock OAuth store 317 + func NewMockOAuthStore() *MockOAuthStore { 318 + return &MockOAuthStore{ 319 + sessions: make(map[string]*oauthlib.ClientSessionData), 320 + } 321 + } 322 + 323 + // AddSession adds a session to the store 324 + func (m *MockOAuthStore) AddSession(did, sessionID, accessToken string) { 325 + key := did + ":" + sessionID 326 + parsedDID, _ := syntax.ParseDID(did) 327 + m.sessions[key] = &oauthlib.ClientSessionData{ 328 + AccountDID: parsedDID, 329 + SessionID: sessionID, 330 + AccessToken: accessToken, 331 + } 332 + } 333 + 334 + // GetSession implements ClientAuthStore 335 + func (m *MockOAuthStore) GetSession(ctx context.Context, did syntax.DID, sessionID string) (*oauthlib.ClientSessionData, error) { 336 + key := did.String() + ":" + sessionID 337 + if sess, ok := m.sessions[key]; ok { 338 + return sess, nil 339 + } 340 + return nil, fmt.Errorf("session not found") 341 + } 342 + 343 + // SaveSession implements ClientAuthStore 344 + func (m *MockOAuthStore) SaveSession(ctx context.Context, sess oauthlib.ClientSessionData) error { 345 + key := sess.AccountDID.String() + ":" + sess.SessionID 346 + m.sessions[key] = &sess 347 + return nil 348 + } 349 + 350 + // DeleteSession implements ClientAuthStore 351 + func (m *MockOAuthStore) DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error { 352 + key := did.String() + ":" + sessionID 353 + delete(m.sessions, key) 354 + return nil 355 + } 356 + 357 + // GetAuthRequestInfo implements ClientAuthStore 358 + func (m *MockOAuthStore) GetAuthRequestInfo(ctx context.Context, state string) (*oauthlib.AuthRequestData, error) { 359 + return nil, fmt.Errorf("not implemented in mock") 360 + } 361 + 362 + // SaveAuthRequestInfo implements ClientAuthStore 363 + func (m *MockOAuthStore) SaveAuthRequestInfo(ctx context.Context, info oauthlib.AuthRequestData) error { 364 + return nil 365 + } 366 + 367 + // DeleteAuthRequestInfo implements ClientAuthStore 368 + func (m *MockOAuthStore) DeleteAuthRequestInfo(ctx context.Context, state string) error { 369 + return nil 370 + } 371 + 372 + // CreateTestOAuthMiddleware creates an OAuth middleware with mock implementations for testing 373 + // The returned middleware accepts a test token that maps to the specified userDID 374 + func CreateTestOAuthMiddleware(userDID string) (*middleware.OAuthAuthMiddleware, string) { 375 + unsealer := NewMockSessionUnsealer() 376 + store := NewMockOAuthStore() 377 + 378 + testToken := "test-token-" + userDID 379 + sessionID := "test-session-123" 380 + 381 + // Add the test session 382 + unsealer.AddSession(testToken, userDID, sessionID) 383 + store.AddSession(userDID, sessionID, "test-access-token") 384 + 385 + authMiddleware := middleware.NewOAuthAuthMiddleware(unsealer, store) 386 + return authMiddleware, testToken 387 + } 388 + 389 + // E2EOAuthMiddleware wraps OAuth middleware for E2E testing with multiple users 390 + type E2EOAuthMiddleware struct { 391 + *middleware.OAuthAuthMiddleware 392 + unsealer *MockSessionUnsealer 393 + store *MockOAuthStore 394 + } 395 + 396 + // NewE2EOAuthMiddleware creates an OAuth middleware for E2E testing 397 + func NewE2EOAuthMiddleware() *E2EOAuthMiddleware { 398 + unsealer := NewMockSessionUnsealer() 399 + store := NewMockOAuthStore() 400 + m := middleware.NewOAuthAuthMiddleware(unsealer, store) 401 + return &E2EOAuthMiddleware{m, unsealer, store} 402 + } 403 + 404 + // AddUser registers a user DID and returns the token to use in Authorization header 405 + func (e *E2EOAuthMiddleware) AddUser(did string) string { 406 + token := "test-token-" + did 407 + sessionID := "session-" + did 408 + e.unsealer.AddSession(token, did, sessionID) 409 + e.store.AddSession(did, sessionID, "access-token-"+did) 410 + return token 411 + }
+7 -9
tests/integration/post_e2e_test.go
··· 2 3 import ( 4 "Coves/internal/api/handlers/post" 5 - "Coves/internal/api/middleware" 6 "Coves/internal/atproto/identity" 7 "Coves/internal/atproto/jetstream" 8 "Coves/internal/core/communities" ··· 405 406 postService := posts.NewPostService(postRepo, communityService, nil, nil, nil, pdsURL) // nil aggregatorService, blobService, unfurlService for user-only tests 407 408 - // Setup auth middleware (skip JWT verification for testing) 409 - authMiddleware := middleware.NewAtProtoAuthMiddleware(nil, true) 410 - defer authMiddleware.Stop() // Clean up DPoP replay cache goroutine 411 412 // Setup HTTP handler 413 createHandler := post.NewCreateHandler(postService) ··· 476 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 477 req.Header.Set("Content-Type", "application/json") 478 479 - // Create a simple JWT for testing (Phase 1: no signature verification) 480 - // In production, this would be a real OAuth token from PDS 481 - testJWT := createSimpleTestJWT(author.DID) 482 - req.Header.Set("Authorization", "DPoP "+testJWT) 483 484 // Execute request through auth middleware + handler 485 rr := httptest.NewRecorder() 486 - handler := authMiddleware.RequireAuth(http.HandlerFunc(createHandler.HandleCreate)) 487 handler.ServeHTTP(rr, req) 488 489 // Check response
··· 2 3 import ( 4 "Coves/internal/api/handlers/post" 5 "Coves/internal/atproto/identity" 6 "Coves/internal/atproto/jetstream" 7 "Coves/internal/core/communities" ··· 404 405 postService := posts.NewPostService(postRepo, communityService, nil, nil, nil, pdsURL) // nil aggregatorService, blobService, unfurlService for user-only tests 406 407 + // Setup OAuth auth middleware for E2E testing 408 + e2eAuth := NewE2EOAuthMiddleware() 409 410 // Setup HTTP handler 411 createHandler := post.NewCreateHandler(postService) ··· 474 req := httptest.NewRequest("POST", "/xrpc/social.coves.community.post.create", bytes.NewReader(reqJSON)) 475 req.Header.Set("Content-Type", "application/json") 476 477 + // Register the author user with OAuth middleware and get test token 478 + // For Coves API handlers, use Bearer scheme with OAuth middleware 479 + token := e2eAuth.AddUser(author.DID) 480 + req.Header.Set("Authorization", "Bearer "+token) 481 482 // Execute request through auth middleware + handler 483 rr := httptest.NewRecorder() 484 + handler := e2eAuth.RequireAuth(http.HandlerFunc(createHandler.HandleCreate)) 485 handler.ServeHTTP(rr, req) 486 487 // Check response
+22 -19
tests/integration/user_journey_e2e_test.go
··· 1 package integration 2 3 import ( 4 - "Coves/internal/api/middleware" 5 "Coves/internal/api/routes" 6 "Coves/internal/atproto/identity" 7 "Coves/internal/atproto/jetstream" ··· 22 "testing" 23 "time" 24 25 - timelineCore "Coves/internal/core/timeline" 26 - 27 "github.com/go-chi/chi/v5" 28 "github.com/gorilla/websocket" 29 _ "github.com/lib/pq" 30 "github.com/pressly/goose/v3" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 // TestFullUserJourney_E2E tests the complete user experience from signup to interaction: ··· 139 commentConsumer := jetstream.NewCommentEventConsumer(commentRepo, db) 140 voteConsumer := jetstream.NewVoteEventConsumer(voteRepo, userService, db) 141 142 - // Setup HTTP server with all routes 143 - // IMPORTANT: skipVerify=true because PDS password auth returns Bearer tokens (not DPoP-bound). 144 - // E2E tests use Bearer tokens with DPoP scheme header, which only works with skipVerify=true. 145 - // In production, skipVerify=false requires proper DPoP-bound tokens from OAuth flow. 146 - authMiddleware := middleware.NewAtProtoAuthMiddleware(nil, true) 147 - defer authMiddleware.Stop() // Clean up DPoP replay cache goroutine 148 r := chi.NewRouter() 149 - routes.RegisterCommunityRoutes(r, communityService, authMiddleware, nil) // nil = allow all community creators 150 - routes.RegisterPostRoutes(r, postService, authMiddleware) 151 - routes.RegisterTimelineRoutes(r, timelineService, authMiddleware) 152 httpServer := httptest.NewServer(r) 153 defer httpServer.Close() 154 ··· 181 var ( 182 userAHandle string 183 userADID string 184 - userAToken string 185 userBHandle string 186 userBDID string 187 - userBToken string 188 communityDID string 189 communityHandle string 190 postURI string ··· 217 userA := createTestUser(t, db, userAHandle, userADID) 218 require.NotNil(t, userA) 219 220 t.Logf("āœ… User A indexed in AppView") 221 }) 222 ··· 244 httpServer.URL+"/xrpc/social.coves.community.create", 245 bytes.NewBuffer(reqBody)) 246 req.Header.Set("Content-Type", "application/json") 247 - req.Header.Set("Authorization", "DPoP "+userAToken) 248 249 resp, err := http.DefaultClient.Do(req) 250 require.NoError(t, err) ··· 328 httpServer.URL+"/xrpc/social.coves.community.post.create", 329 bytes.NewBuffer(reqBody)) 330 req.Header.Set("Content-Type", "application/json") 331 - req.Header.Set("Authorization", "DPoP "+userAToken) 332 333 resp, err := http.DefaultClient.Do(req) 334 require.NoError(t, err) ··· 413 userB := createTestUser(t, db, userBHandle, userBDID) 414 require.NotNil(t, userB) 415 416 t.Logf("āœ… User B indexed in AppView") 417 }) 418 ··· 437 httpServer.URL+"/xrpc/social.coves.community.subscribe", 438 bytes.NewBuffer(reqBody)) 439 req.Header.Set("Content-Type", "application/json") 440 - req.Header.Set("Authorization", "DPoP "+userBToken) 441 442 resp, err := http.DefaultClient.Do(req) 443 require.NoError(t, err) ··· 669 t.Run("9. User B - Verify Timeline Feed Shows Subscribed Community Posts", func(t *testing.T) { 670 t.Log("\nšŸ“° Part 9: User B checks timeline feed...") 671 672 - // Use HTTP client to properly go through auth middleware with DPoP token 673 req, _ := http.NewRequest(http.MethodGet, 674 httpServer.URL+"/xrpc/social.coves.feed.getTimeline?sort=new&limit=10", nil) 675 - req.Header.Set("Authorization", "DPoP "+userBToken) 676 677 resp, err := http.DefaultClient.Do(req) 678 require.NoError(t, err)
··· 1 package integration 2 3 import ( 4 "Coves/internal/api/routes" 5 "Coves/internal/atproto/identity" 6 "Coves/internal/atproto/jetstream" ··· 21 "testing" 22 "time" 23 24 "github.com/go-chi/chi/v5" 25 "github.com/gorilla/websocket" 26 _ "github.com/lib/pq" 27 "github.com/pressly/goose/v3" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 + 31 + timelineCore "Coves/internal/core/timeline" 32 ) 33 34 // TestFullUserJourney_E2E tests the complete user experience from signup to interaction: ··· 138 commentConsumer := jetstream.NewCommentEventConsumer(commentRepo, db) 139 voteConsumer := jetstream.NewVoteEventConsumer(voteRepo, userService, db) 140 141 + // Setup HTTP server with all routes using OAuth middleware 142 + e2eAuth := NewE2EOAuthMiddleware() 143 r := chi.NewRouter() 144 + routes.RegisterCommunityRoutes(r, communityService, e2eAuth.OAuthAuthMiddleware, nil) // nil = allow all community creators 145 + routes.RegisterPostRoutes(r, postService, e2eAuth.OAuthAuthMiddleware) 146 + routes.RegisterTimelineRoutes(r, timelineService, e2eAuth.OAuthAuthMiddleware) 147 httpServer := httptest.NewServer(r) 148 defer httpServer.Close() 149 ··· 176 var ( 177 userAHandle string 178 userADID string 179 + userAToken string // PDS access token for direct PDS requests 180 + userAAPIToken string // Coves API token for Coves API requests 181 userBHandle string 182 userBDID string 183 + userBToken string // PDS access token for direct PDS requests 184 + userBAPIToken string // Coves API token for Coves API requests 185 communityDID string 186 communityHandle string 187 postURI string ··· 214 userA := createTestUser(t, db, userAHandle, userADID) 215 require.NotNil(t, userA) 216 217 + // Register user with OAuth middleware for Coves API requests 218 + userAAPIToken = e2eAuth.AddUser(userADID) 219 + 220 t.Logf("āœ… User A indexed in AppView") 221 }) 222 ··· 244 httpServer.URL+"/xrpc/social.coves.community.create", 245 bytes.NewBuffer(reqBody)) 246 req.Header.Set("Content-Type", "application/json") 247 + req.Header.Set("Authorization", "Bearer "+userAAPIToken) 248 249 resp, err := http.DefaultClient.Do(req) 250 require.NoError(t, err) ··· 328 httpServer.URL+"/xrpc/social.coves.community.post.create", 329 bytes.NewBuffer(reqBody)) 330 req.Header.Set("Content-Type", "application/json") 331 + req.Header.Set("Authorization", "Bearer "+userAAPIToken) 332 333 resp, err := http.DefaultClient.Do(req) 334 require.NoError(t, err) ··· 413 userB := createTestUser(t, db, userBHandle, userBDID) 414 require.NotNil(t, userB) 415 416 + // Register user with OAuth middleware for Coves API requests 417 + userBAPIToken = e2eAuth.AddUser(userBDID) 418 + 419 t.Logf("āœ… User B indexed in AppView") 420 }) 421 ··· 440 httpServer.URL+"/xrpc/social.coves.community.subscribe", 441 bytes.NewBuffer(reqBody)) 442 req.Header.Set("Content-Type", "application/json") 443 + req.Header.Set("Authorization", "Bearer "+userBAPIToken) 444 445 resp, err := http.DefaultClient.Do(req) 446 require.NoError(t, err) ··· 672 t.Run("9. User B - Verify Timeline Feed Shows Subscribed Community Posts", func(t *testing.T) { 673 t.Log("\nšŸ“° Part 9: User B checks timeline feed...") 674 675 + // Use HTTP client to properly go through auth middleware with Bearer token 676 req, _ := http.NewRequest(http.MethodGet, 677 httpServer.URL+"/xrpc/social.coves.feed.getTimeline?sort=new&limit=10", nil) 678 + req.Header.Set("Authorization", "Bearer "+userBAPIToken) 679 680 resp, err := http.DefaultClient.Do(req) 681 require.NoError(t, err)