From df92656753540ec4097fc099689e10951edbc127 Mon Sep 17 00:00:00 2001 From: Bretton Date: Sun, 4 Jan 2026 14:44:43 -0800 Subject: [PATCH] feat(communities): add viewer.subscribed state to listCommunities endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the issue where authenticated users couldn't see their subscription status in the community list response, causing "My Communities" tab to show no results and all community tiles to show "Join" instead of "Joined". Changes: - Add CommunityViewerState struct with tri-state *bool semantics - Add GetSubscribedCommunityDIDs batch query to repository - Add PopulateCommunityViewerState helper for viewer enrichment - Update ListHandler to inject repo and populate viewer state - Update route registration to pass repository through The endpoint remains public but now enriches responses with viewer.subscribed when the request is authenticated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/server/main.go | 2 +- internal/api/handlers/common/viewer_state.go | 41 +++ internal/api/handlers/community/list.go | 8 +- internal/api/routes/community.go | 4 +- .../core/comments/comment_service_test.go | 4 + internal/core/communities/community.go | 17 +- internal/core/communities/interfaces.go | 1 + .../postgres/community_repo_subscriptions.go | 48 ++++ tests/integration/community_e2e_test.go | 2 +- .../community_list_viewer_state_test.go | 268 ++++++++++++++++++ tests/integration/community_repo_test.go | 117 ++++++++ tests/integration/user_journey_e2e_test.go | 2 +- 12 files changed, 506 insertions(+), 8 deletions(-) create mode 100644 tests/integration/community_list_viewer_state_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index ecacdfa..8eb61bc 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -606,7 +606,7 @@ func main() { // Register XRPC routes routes.RegisterUserRoutes(r, userService) - routes.RegisterCommunityRoutes(r, communityService, authMiddleware, allowedCommunityCreators) + routes.RegisterCommunityRoutes(r, communityService, communityRepo, authMiddleware, allowedCommunityCreators) log.Println("Community XRPC endpoints registered with OAuth authentication") routes.RegisterPostRoutes(r, postService, dualAuth) diff --git a/internal/api/handlers/common/viewer_state.go b/internal/api/handlers/common/viewer_state.go index b362095..150c1f0 100644 --- a/internal/api/handlers/common/viewer_state.go +++ b/internal/api/handlers/common/viewer_state.go @@ -2,6 +2,7 @@ package common import ( "Coves/internal/api/middleware" + "Coves/internal/core/communities" "Coves/internal/core/posts" "Coves/internal/core/votes" "context" @@ -71,3 +72,43 @@ func PopulateViewerVoteState[T FeedPostProvider]( } } } + +// PopulateCommunityViewerState enriches communities with the authenticated user's subscription state. +// This is a no-op if the request is unauthenticated. +func PopulateCommunityViewerState( + ctx context.Context, + r *http.Request, + repo communities.Repository, + communityList []*communities.Community, +) { + if repo == nil || len(communityList) == 0 { + return + } + + userDID := middleware.GetUserDID(r) + if userDID == "" { + return // Not authenticated, leave viewer state nil + } + + // Collect community DIDs + communityDIDs := make([]string, len(communityList)) + for i, c := range communityList { + communityDIDs[i] = c.DID + } + + // Batch query subscriptions + subscribed, err := repo.GetSubscribedCommunityDIDs(ctx, userDID, communityDIDs) + if err != nil { + log.Printf("Warning: failed to get subscription state for user %s (%d communities): %v", + userDID, len(communityDIDs), err) + return + } + + // Populate viewer state on each community + for _, c := range communityList { + isSubscribed := subscribed[c.DID] + c.Viewer = &communities.CommunityViewerState{ + Subscribed: &isSubscribed, + } + } +} diff --git a/internal/api/handlers/community/list.go b/internal/api/handlers/community/list.go index 4972916..4da2c93 100644 --- a/internal/api/handlers/community/list.go +++ b/internal/api/handlers/community/list.go @@ -1,6 +1,7 @@ package community import ( + "Coves/internal/api/handlers/common" "Coves/internal/core/communities" "encoding/json" "net/http" @@ -10,12 +11,14 @@ import ( // ListHandler handles listing communities type ListHandler struct { service communities.Service + repo communities.Repository } // NewListHandler creates a new list handler -func NewListHandler(service communities.Service) *ListHandler { +func NewListHandler(service communities.Service, repo communities.Repository) *ListHandler { return &ListHandler{ service: service, + repo: repo, } } @@ -100,6 +103,9 @@ func (h *ListHandler) HandleList(w http.ResponseWriter, r *http.Request) { return } + // Populate viewer state if authenticated + common.PopulateCommunityViewerState(r.Context(), r, h.repo, results) + // Build response var cursor string if len(results) == limit { diff --git a/internal/api/routes/community.go b/internal/api/routes/community.go index ec9cab0..80b4c19 100644 --- a/internal/api/routes/community.go +++ b/internal/api/routes/community.go @@ -11,12 +11,12 @@ import ( // RegisterCommunityRoutes registers community-related XRPC endpoints on the router // Implements social.coves.community.* lexicon endpoints // allowedCommunityCreators restricts who can create communities. If empty, anyone can create. -func RegisterCommunityRoutes(r chi.Router, service communities.Service, authMiddleware *middleware.OAuthAuthMiddleware, allowedCommunityCreators []string) { +func RegisterCommunityRoutes(r chi.Router, service communities.Service, repo communities.Repository, authMiddleware *middleware.OAuthAuthMiddleware, allowedCommunityCreators []string) { // Initialize handlers createHandler := community.NewCreateHandler(service, allowedCommunityCreators) getHandler := community.NewGetHandler(service) updateHandler := community.NewUpdateHandler(service) - listHandler := community.NewListHandler(service) + listHandler := community.NewListHandler(service, repo) searchHandler := community.NewSearchHandler(service) subscribeHandler := community.NewSubscribeHandler(service) blockHandler := community.NewBlockHandler(service) diff --git a/internal/core/comments/comment_service_test.go b/internal/core/comments/comment_service_test.go index a72b4c4..d20e18c 100644 --- a/internal/core/comments/comment_service_test.go +++ b/internal/core/comments/comment_service_test.go @@ -315,6 +315,10 @@ func (m *mockCommunityRepo) ListSubscribers(ctx context.Context, communityDID st return nil, nil } +func (m *mockCommunityRepo) GetSubscribedCommunityDIDs(ctx context.Context, userDID string, communityDIDs []string) (map[string]bool, error) { + return map[string]bool{}, nil +} + func (m *mockCommunityRepo) BlockCommunity(ctx context.Context, block *communities.CommunityBlock) (*communities.CommunityBlock, error) { return nil, nil } diff --git a/internal/core/communities/community.go b/internal/core/communities/community.go index 04b5f8d..951bef2 100644 --- a/internal/core/communities/community.go +++ b/internal/core/communities/community.go @@ -41,8 +41,21 @@ type Community struct { PostCount int `json:"postCount" db:"post_count"` SubscriberCount int `json:"subscriberCount" db:"subscriber_count"` MemberCount int `json:"memberCount" db:"member_count"` - ID int `json:"id" db:"id"` - AllowExternalDiscovery bool `json:"allowExternalDiscovery" db:"allow_external_discovery"` + ID int `json:"id" db:"id"` + AllowExternalDiscovery bool `json:"allowExternalDiscovery" db:"allow_external_discovery"` + Viewer *CommunityViewerState `json:"viewer,omitempty" db:"-"` +} + +// CommunityViewerState contains viewer-specific state for community list views. +// This is a simplified version - detailed views use the full viewerState from lexicon. +// +// Fields use *bool to represent three states: +// - nil: State not queried (unauthenticated request) +// - true: User has this relationship +// - false: User does not have this relationship +type CommunityViewerState struct { + Subscribed *bool `json:"subscribed,omitempty"` + Member *bool `json:"member,omitempty"` } // Subscription represents a lightweight feed follow (user subscribes to see posts) diff --git a/internal/core/communities/interfaces.go b/internal/core/communities/interfaces.go index fe2acbc..bda55dd 100644 --- a/internal/core/communities/interfaces.go +++ b/internal/core/communities/interfaces.go @@ -32,6 +32,7 @@ type Repository interface { GetSubscriptionByURI(ctx context.Context, recordURI string) (*Subscription, error) // For Jetstream delete operations ListSubscriptions(ctx context.Context, userDID string, limit, offset int) ([]*Subscription, error) ListSubscribers(ctx context.Context, communityDID string, limit, offset int) ([]*Subscription, error) + GetSubscribedCommunityDIDs(ctx context.Context, userDID string, communityDIDs []string) (map[string]bool, error) // Community Blocks BlockCommunity(ctx context.Context, block *CommunityBlock) (*CommunityBlock, error) diff --git a/internal/db/postgres/community_repo_subscriptions.go b/internal/db/postgres/community_repo_subscriptions.go index 3763d7b..61b8eb5 100644 --- a/internal/db/postgres/community_repo_subscriptions.go +++ b/internal/db/postgres/community_repo_subscriptions.go @@ -344,3 +344,51 @@ func (r *postgresCommunityRepo) ListSubscribers(ctx context.Context, communityDI return result, nil } + +// GetSubscribedCommunityDIDs returns a map of community DIDs that the user is subscribed to +// This is optimized for batch lookups when populating viewer state +func (r *postgresCommunityRepo) GetSubscribedCommunityDIDs(ctx context.Context, userDID string, communityDIDs []string) (map[string]bool, error) { + if len(communityDIDs) == 0 { + return map[string]bool{}, nil + } + + // Build query with placeholders for IN clause + placeholders := make([]string, len(communityDIDs)) + args := make([]interface{}, len(communityDIDs)+1) + args[0] = userDID + for i, did := range communityDIDs { + placeholders[i] = fmt.Sprintf("$%d", i+2) + args[i+1] = did + } + + query := fmt.Sprintf(` + SELECT community_did + FROM community_subscriptions + WHERE user_did = $1 AND community_did IN (%s)`, + strings.Join(placeholders, ", ")) + + rows, err := r.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("failed to get subscribed communities: %w", err) + } + defer func() { + if closeErr := rows.Close(); closeErr != nil { + log.Printf("Failed to close rows: %v", closeErr) + } + }() + + result := make(map[string]bool) + for rows.Next() { + var communityDID string + if err := rows.Scan(&communityDID); err != nil { + return nil, fmt.Errorf("failed to scan community DID: %w", err) + } + result[communityDID] = true + } + + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating subscribed communities: %w", err) + } + + return result, nil +} diff --git a/tests/integration/community_e2e_test.go b/tests/integration/community_e2e_test.go index 9842d16..e801017 100644 --- a/tests/integration/community_e2e_test.go +++ b/tests/integration/community_e2e_test.go @@ -164,7 +164,7 @@ func TestCommunity_E2E(t *testing.T) { // Setup HTTP server with XRPC routes r := chi.NewRouter() - routes.RegisterCommunityRoutes(r, communityService, e2eAuth.OAuthAuthMiddleware, nil) // nil = allow all community creators + routes.RegisterCommunityRoutes(r, communityService, communityRepo, e2eAuth.OAuthAuthMiddleware, nil) // nil = allow all community creators httpServer := httptest.NewServer(r) defer httpServer.Close() diff --git a/tests/integration/community_list_viewer_state_test.go b/tests/integration/community_list_viewer_state_test.go new file mode 100644 index 0000000..827d717 --- /dev/null +++ b/tests/integration/community_list_viewer_state_test.go @@ -0,0 +1,268 @@ +package integration + +import ( + "Coves/internal/api/handlers/community" + "Coves/internal/api/middleware" + "Coves/internal/core/communities" + "Coves/internal/db/postgres" + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/bluesky-social/indigo/atproto/auth/oauth" + "github.com/go-chi/chi/v5" +) + +// TestCommunityList_ViewerState tests that the list communities endpoint +// correctly populates viewer.subscribed field for authenticated users +func TestCommunityList_ViewerState(t *testing.T) { + db := setupTestDB(t) + defer func() { + if err := db.Close(); err != nil { + t.Logf("Failed to close database: %v", err) + } + }() + + repo := postgres.NewCommunityRepository(db) + ctx := context.Background() + + // Create test communities + baseSuffix := time.Now().UnixNano() + communityDIDs := make([]string, 3) + for i := 0; i < 3; i++ { + uniqueSuffix := fmt.Sprintf("%d%d", baseSuffix, i) + communityDID := generateTestDID(uniqueSuffix) + communityDIDs[i] = communityDID + comm := &communities.Community{ + DID: communityDID, + Handle: fmt.Sprintf("c-viewer-test-%d-%d.coves.local", baseSuffix, i), + Name: fmt.Sprintf("viewer-test-%d", i), + DisplayName: fmt.Sprintf("Viewer Test Community %d", i), + OwnerDID: "did:web:coves.local", + CreatedByDID: "did:plc:testcreator", + HostedByDID: "did:web:coves.local", + Visibility: "public", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if _, err := repo.Create(ctx, comm); err != nil { + t.Fatalf("Failed to create community %d: %v", i, err) + } + } + + // Create a test user and subscribe them to community 0 and 2 + testUserDID := fmt.Sprintf("did:plc:viewertestuser%d", baseSuffix) + + sub1 := &communities.Subscription{ + UserDID: testUserDID, + CommunityDID: communityDIDs[0], + ContentVisibility: 3, + SubscribedAt: time.Now(), + } + if _, err := repo.Subscribe(ctx, sub1); err != nil { + t.Fatalf("Failed to subscribe to community 0: %v", err) + } + + sub2 := &communities.Subscription{ + UserDID: testUserDID, + CommunityDID: communityDIDs[2], + ContentVisibility: 3, + SubscribedAt: time.Now(), + } + if _, err := repo.Subscribe(ctx, sub2); err != nil { + t.Fatalf("Failed to subscribe to community 2: %v", err) + } + + // Create mock service that returns our communities + mockService := &mockCommunityService{ + repo: repo, + } + + // Create handler with real repo for viewer state population + listHandler := community.NewListHandler(mockService, repo) + + t.Run("authenticated user sees viewer.subscribed correctly", func(t *testing.T) { + // Setup router with middleware that injects user DID + r := chi.NewRouter() + + // Use test middleware that sets user DID in context + r.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ctx := middleware.SetTestUserDID(req.Context(), testUserDID) + next.ServeHTTP(w, req.WithContext(ctx)) + }) + }) + r.Get("/xrpc/social.coves.community.list", listHandler.HandleList) + + req := httptest.NewRequest("GET", "/xrpc/social.coves.community.list?limit=50", nil) + rec := httptest.NewRecorder() + + r.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("Expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + var response struct { + Communities []struct { + DID string `json:"did"` + Viewer *struct { + Subscribed *bool `json:"subscribed"` + } `json:"viewer"` + } `json:"communities"` + } + + if err := json.NewDecoder(rec.Body).Decode(&response); err != nil { + t.Fatalf("Failed to decode response: %v", err) + } + + // Check that viewer state is populated correctly + subscriptionMap := map[string]bool{ + communityDIDs[0]: true, + communityDIDs[1]: false, + communityDIDs[2]: true, + } + + for _, comm := range response.Communities { + expectedSubscribed, inTestSet := subscriptionMap[comm.DID] + if !inTestSet { + continue // Skip communities not in our test set + } + + if comm.Viewer == nil { + t.Errorf("Community %s has nil Viewer, expected populated", comm.DID) + continue + } + + if comm.Viewer.Subscribed == nil { + t.Errorf("Community %s has nil Viewer.Subscribed, expected populated", comm.DID) + continue + } + + if *comm.Viewer.Subscribed != expectedSubscribed { + t.Errorf("Community %s: expected subscribed=%v, got %v", + comm.DID, expectedSubscribed, *comm.Viewer.Subscribed) + } + } + }) + + t.Run("unauthenticated request has nil viewer state", func(t *testing.T) { + // Setup router WITHOUT middleware that sets user DID + r := chi.NewRouter() + r.Get("/xrpc/social.coves.community.list", listHandler.HandleList) + + req := httptest.NewRequest("GET", "/xrpc/social.coves.community.list?limit=50", nil) + rec := httptest.NewRecorder() + + r.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("Expected status 200, got %d: %s", rec.Code, rec.Body.String()) + } + + var response struct { + Communities []struct { + DID string `json:"did"` + Viewer *struct { + Subscribed *bool `json:"subscribed"` + } `json:"viewer"` + } `json:"communities"` + } + + if err := json.NewDecoder(rec.Body).Decode(&response); err != nil { + t.Fatalf("Failed to decode response: %v", err) + } + + // For unauthenticated requests, viewer should be nil for all communities + for _, comm := range response.Communities { + if comm.Viewer != nil { + t.Errorf("Community %s has non-nil Viewer for unauthenticated request", comm.DID) + } + } + }) +} + +// mockCommunityService implements communities.Service for testing +type mockCommunityService struct { + repo communities.Repository +} + +func (m *mockCommunityService) CreateCommunity(ctx context.Context, req communities.CreateCommunityRequest) (*communities.Community, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) GetCommunity(ctx context.Context, identifier string) (*communities.Community, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) UpdateCommunity(ctx context.Context, req communities.UpdateCommunityRequest) (*communities.Community, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) ListCommunities(ctx context.Context, req communities.ListCommunitiesRequest) ([]*communities.Community, error) { + return m.repo.List(ctx, req) +} + +func (m *mockCommunityService) SearchCommunities(ctx context.Context, req communities.SearchCommunitiesRequest) ([]*communities.Community, int, error) { + return nil, 0, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) SubscribeToCommunity(ctx context.Context, session *oauth.ClientSessionData, communityIdentifier string, contentVisibility int) (*communities.Subscription, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) UnsubscribeFromCommunity(ctx context.Context, session *oauth.ClientSessionData, communityIdentifier string) error { + return fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) GetUserSubscriptions(ctx context.Context, userDID string, limit, offset int) ([]*communities.Subscription, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) GetCommunitySubscribers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*communities.Subscription, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) BlockCommunity(ctx context.Context, session *oauth.ClientSessionData, communityIdentifier string) (*communities.CommunityBlock, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) UnblockCommunity(ctx context.Context, session *oauth.ClientSessionData, communityIdentifier string) error { + return fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) GetBlockedCommunities(ctx context.Context, userDID string, limit, offset int) ([]*communities.CommunityBlock, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) IsBlocked(ctx context.Context, userDID, communityIdentifier string) (bool, error) { + return false, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) GetMembership(ctx context.Context, userDID, communityIdentifier string) (*communities.Membership, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) ListCommunityMembers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*communities.Membership, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockCommunityService) ValidateHandle(handle string) error { + return nil +} + +func (m *mockCommunityService) ResolveCommunityIdentifier(ctx context.Context, identifier string) (string, error) { + return identifier, nil +} + +func (m *mockCommunityService) EnsureFreshToken(ctx context.Context, community *communities.Community) (*communities.Community, error) { + return community, nil +} + +func (m *mockCommunityService) GetByDID(ctx context.Context, did string) (*communities.Community, error) { + return m.repo.GetByDID(ctx, did) +} diff --git a/tests/integration/community_repo_test.go b/tests/integration/community_repo_test.go index ac682ae..b3ef9e7 100644 --- a/tests/integration/community_repo_test.go +++ b/tests/integration/community_repo_test.go @@ -409,6 +409,123 @@ func TestCommunityRepository_List(t *testing.T) { }) } +func TestCommunityRepository_GetSubscribedCommunityDIDs(t *testing.T) { + db := setupTestDB(t) + defer func() { + if err := db.Close(); err != nil { + t.Logf("Failed to close database: %v", err) + } + }() + + repo := postgres.NewCommunityRepository(db) + ctx := context.Background() + + // Create test communities + baseSuffix := time.Now().UnixNano() + communityDIDs := make([]string, 3) + for i := 0; i < 3; i++ { + uniqueSuffix := fmt.Sprintf("%d%d", baseSuffix, i) + communityDID := generateTestDID(uniqueSuffix) + communityDIDs[i] = communityDID + community := &communities.Community{ + DID: communityDID, + Handle: fmt.Sprintf("!batch-sub-test-%d-%d@coves.local", baseSuffix, i), + Name: fmt.Sprintf("batch-sub-test-%d", i), + OwnerDID: "did:web:coves.local", + CreatedByDID: "did:plc:user123", + HostedByDID: "did:web:coves.local", + Visibility: "public", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if _, err := repo.Create(ctx, community); err != nil { + t.Fatalf("Failed to create community %d: %v", i, err) + } + } + + userDID := fmt.Sprintf("did:plc:batchsubuser%d", baseSuffix) + + t.Run("returns empty map when user has no subscriptions", func(t *testing.T) { + result, err := repo.GetSubscribedCommunityDIDs(ctx, userDID, communityDIDs) + if err != nil { + t.Fatalf("Failed to get subscribed community DIDs: %v", err) + } + + if len(result) != 0 { + t.Errorf("Expected empty map, got %d entries", len(result)) + } + }) + + t.Run("returns subscribed communities only", func(t *testing.T) { + // Subscribe to first and third community + sub1 := &communities.Subscription{ + UserDID: userDID, + CommunityDID: communityDIDs[0], + ContentVisibility: 3, + SubscribedAt: time.Now(), + } + if _, err := repo.Subscribe(ctx, sub1); err != nil { + t.Fatalf("Failed to subscribe to community 0: %v", err) + } + + sub3 := &communities.Subscription{ + UserDID: userDID, + CommunityDID: communityDIDs[2], + ContentVisibility: 3, + SubscribedAt: time.Now(), + } + if _, err := repo.Subscribe(ctx, sub3); err != nil { + t.Fatalf("Failed to subscribe to community 2: %v", err) + } + + result, err := repo.GetSubscribedCommunityDIDs(ctx, userDID, communityDIDs) + if err != nil { + t.Fatalf("Failed to get subscribed community DIDs: %v", err) + } + + if len(result) != 2 { + t.Errorf("Expected 2 subscribed communities, got %d", len(result)) + } + + if !result[communityDIDs[0]] { + t.Errorf("Expected community 0 to be subscribed") + } + if result[communityDIDs[1]] { + t.Errorf("Expected community 1 to NOT be subscribed") + } + if !result[communityDIDs[2]] { + t.Errorf("Expected community 2 to be subscribed") + } + }) + + t.Run("returns empty map for empty community DIDs slice", func(t *testing.T) { + result, err := repo.GetSubscribedCommunityDIDs(ctx, userDID, []string{}) + if err != nil { + t.Fatalf("Failed to get subscribed community DIDs: %v", err) + } + + if len(result) != 0 { + t.Errorf("Expected empty map for empty input, got %d entries", len(result)) + } + }) + + t.Run("handles non-existent community DIDs gracefully", func(t *testing.T) { + nonExistentDIDs := []string{ + "did:plc:nonexistent1", + "did:plc:nonexistent2", + } + + result, err := repo.GetSubscribedCommunityDIDs(ctx, userDID, nonExistentDIDs) + if err != nil { + t.Fatalf("Failed to get subscribed community DIDs: %v", err) + } + + if len(result) != 0 { + t.Errorf("Expected empty map for non-existent DIDs, got %d entries", len(result)) + } + }) +} + // TODO: Implement search functionality before re-enabling this test // func TestCommunityRepository_Search(t *testing.T) { // db := setupTestDB(t) diff --git a/tests/integration/user_journey_e2e_test.go b/tests/integration/user_journey_e2e_test.go index 9f55e1e..5d364aa 100644 --- a/tests/integration/user_journey_e2e_test.go +++ b/tests/integration/user_journey_e2e_test.go @@ -141,7 +141,7 @@ func TestFullUserJourney_E2E(t *testing.T) { // Setup HTTP server with all routes using OAuth middleware e2eAuth := NewE2EOAuthMiddleware() r := chi.NewRouter() - routes.RegisterCommunityRoutes(r, communityService, e2eAuth.OAuthAuthMiddleware, nil) // nil = allow all community creators + routes.RegisterCommunityRoutes(r, communityService, communityRepo, e2eAuth.OAuthAuthMiddleware, nil) // nil = allow all community creators routes.RegisterPostRoutes(r, postService, e2eAuth.OAuthAuthMiddleware) routes.RegisterTimelineRoutes(r, timelineService, nil, nil, e2eAuth.OAuthAuthMiddleware) httpServer := httptest.NewServer(r) -- 2.51.2