A community based topic aggregation platform built on atproto
1package user
2
3import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "net/http/httptest"
8 "testing"
9 "time"
10
11 "Coves/internal/api/middleware"
12 "Coves/internal/core/users"
13
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/mock"
16)
17
18func TestHandleMe_Success(t *testing.T) {
19 mockService := new(MockUserService)
20 handler := NewMeHandler(mockService)
21
22 testDID := "did:plc:testme123"
23 profile := &users.ProfileViewDetailed{
24 DID: testDID,
25 Handle: "alice.test",
26 CreatedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
27 DisplayName: "Alice",
28 Bio: "Hello world",
29 Avatar: "https://cdn.example.com/avatar.jpg",
30 Stats: &users.ProfileStats{
31 PostCount: 10,
32 CommentCount: 5,
33 CommunityCount: 3,
34 Reputation: 42,
35 },
36 }
37 mockService.On("GetProfile", mock.Anything, testDID).Return(profile, nil)
38
39 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
40 ctx := middleware.SetTestUserDID(req.Context(), testDID)
41 req = req.WithContext(ctx)
42
43 w := httptest.NewRecorder()
44 handler.HandleMe(w, req)
45
46 assert.Equal(t, http.StatusOK, w.Code)
47 assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
48
49 // Unmarshal into raw map to verify wire-format JSON key names
50 var raw map[string]json.RawMessage
51 err := json.Unmarshal(w.Body.Bytes(), &raw)
52 assert.NoError(t, err)
53
54 // Bio field has json:"description" tag — verify the wire key is "description", not "bio"
55 assert.Contains(t, raw, "description", "Bio should serialize as 'description' per json tag")
56 assert.NotContains(t, raw, "bio", "'bio' key should not appear in JSON output")
57
58 // Also verify typed deserialization
59 var resp users.ProfileViewDetailed
60 err = json.Unmarshal(w.Body.Bytes(), &resp)
61 assert.NoError(t, err)
62 assert.Equal(t, testDID, resp.DID)
63 assert.Equal(t, "alice.test", resp.Handle)
64 assert.Equal(t, "Alice", resp.DisplayName)
65 assert.Equal(t, "Hello world", resp.Bio)
66 assert.Equal(t, 10, resp.Stats.PostCount)
67
68 mockService.AssertExpectations(t)
69}
70
71func TestHandleMe_NilStats(t *testing.T) {
72 mockService := new(MockUserService)
73 handler := NewMeHandler(mockService)
74
75 testDID := "did:plc:nostats"
76 profile := &users.ProfileViewDetailed{
77 DID: testDID,
78 Handle: "bob.test",
79 CreatedAt: time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC),
80 Stats: nil,
81 }
82 mockService.On("GetProfile", mock.Anything, testDID).Return(profile, nil)
83
84 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
85 ctx := middleware.SetTestUserDID(req.Context(), testDID)
86 req = req.WithContext(ctx)
87
88 w := httptest.NewRecorder()
89 handler.HandleMe(w, req)
90
91 assert.Equal(t, http.StatusOK, w.Code)
92
93 var raw map[string]json.RawMessage
94 err := json.Unmarshal(w.Body.Bytes(), &raw)
95 assert.NoError(t, err)
96
97 // stats should be omitted when nil (omitempty tag)
98 assert.NotContains(t, raw, "stats", "nil Stats should be omitted from JSON")
99
100 mockService.AssertExpectations(t)
101}
102
103func TestHandleMe_Unauthenticated(t *testing.T) {
104 mockService := new(MockUserService)
105 handler := NewMeHandler(mockService)
106
107 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
108
109 w := httptest.NewRecorder()
110 handler.HandleMe(w, req)
111
112 assert.Equal(t, http.StatusUnauthorized, w.Code)
113
114 // Validate the full error JSON structure
115 var errResp map[string]string
116 err := json.Unmarshal(w.Body.Bytes(), &errResp)
117 assert.NoError(t, err)
118 assert.Equal(t, "AuthRequired", errResp["error"])
119 assert.Equal(t, "Authentication required", errResp["message"])
120
121 mockService.AssertNotCalled(t, "GetProfile", mock.Anything, mock.Anything)
122}
123
124func TestHandleMe_UserNotFound(t *testing.T) {
125 mockService := new(MockUserService)
126 handler := NewMeHandler(mockService)
127
128 testDID := "did:plc:nonexistent"
129 mockService.On("GetProfile", mock.Anything, testDID).Return(nil, users.ErrUserNotFound)
130
131 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
132 ctx := middleware.SetTestUserDID(req.Context(), testDID)
133 req = req.WithContext(ctx)
134
135 w := httptest.NewRecorder()
136 handler.HandleMe(w, req)
137
138 assert.Equal(t, http.StatusNotFound, w.Code)
139 assert.Contains(t, w.Body.String(), "AccountNotFound")
140
141 mockService.AssertExpectations(t)
142}
143
144func TestHandleMe_InternalError(t *testing.T) {
145 mockService := new(MockUserService)
146 handler := NewMeHandler(mockService)
147
148 testDID := "did:plc:erroruser"
149 mockService.On("GetProfile", mock.Anything, testDID).Return(nil, assert.AnError)
150
151 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
152 ctx := middleware.SetTestUserDID(req.Context(), testDID)
153 req = req.WithContext(ctx)
154
155 w := httptest.NewRecorder()
156 handler.HandleMe(w, req)
157
158 assert.Equal(t, http.StatusInternalServerError, w.Code)
159 assert.Contains(t, w.Body.String(), "InternalServerError")
160
161 mockService.AssertExpectations(t)
162}
163
164func TestHandleMe_Timeout(t *testing.T) {
165 mockService := new(MockUserService)
166 handler := NewMeHandler(mockService)
167
168 testDID := "did:plc:timeoutuser"
169 mockService.On("GetProfile", mock.Anything, testDID).Return(nil, context.DeadlineExceeded)
170
171 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
172 ctx := middleware.SetTestUserDID(req.Context(), testDID)
173 req = req.WithContext(ctx)
174
175 w := httptest.NewRecorder()
176 handler.HandleMe(w, req)
177
178 assert.Equal(t, http.StatusGatewayTimeout, w.Code)
179 assert.Contains(t, w.Body.String(), "Timeout")
180
181 mockService.AssertExpectations(t)
182}
183
184func TestHandleMe_ContextCanceled(t *testing.T) {
185 mockService := new(MockUserService)
186 handler := NewMeHandler(mockService)
187
188 testDID := "did:plc:canceluser"
189 mockService.On("GetProfile", mock.Anything, testDID).Return(nil, context.Canceled)
190
191 req := httptest.NewRequest(http.MethodGet, "/api/me", nil)
192 ctx := middleware.SetTestUserDID(req.Context(), testDID)
193 req = req.WithContext(ctx)
194
195 w := httptest.NewRecorder()
196 handler.HandleMe(w, req)
197
198 assert.Equal(t, http.StatusBadRequest, w.Code)
199 assert.Contains(t, w.Body.String(), "RequestCanceled")
200
201 mockService.AssertExpectations(t)
202}