···
4
-
"Coves/internal/core/users"
5
-
"Coves/internal/db/postgres"
"github.com/go-chi/chi/v5"
···
"github.com/pressly/goose/v3"
"Coves/internal/api/routes"
19
+
"Coves/internal/core/users"
20
+
"Coves/internal/db/postgres"
func setupTestDB(t *testing.T) *sql.DB {
// Build connection string from environment variables (set by .env.dev)
24
-
// These are loaded by the Makefile when running tests
testUser := os.Getenv("POSTGRES_TEST_USER")
testPassword := os.Getenv("POSTGRES_TEST_PASSWORD")
testPort := os.Getenv("POSTGRES_TEST_PORT")
···
// Clean up any existing test data
65
-
_, err = db.Exec("DELETE FROM users WHERE email LIKE '%@example.com'")
65
+
_, err = db.Exec("DELETE FROM users WHERE handle LIKE '%.test'")
t.Logf("Warning: Failed to clean up test data: %v", err)
···
73
-
func TestCreateUser(t *testing.T) {
73
+
func TestUserCreationAndRetrieval(t *testing.T) {
74
+
db := setupTestDB(t)
77
+
// Wire up dependencies
78
+
userRepo := postgres.NewUserRepository(db)
79
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
81
+
ctx := context.Background()
83
+
// Test 1: Create a user
84
+
t.Run("Create User", func(t *testing.T) {
85
+
req := users.CreateUserRequest{
86
+
DID: "did:plc:test123456",
87
+
Handle: "alice.test",
90
+
user, err := userService.CreateUser(ctx, req)
92
+
t.Fatalf("Failed to create user: %v", err)
95
+
if user.DID != req.DID {
96
+
t.Errorf("Expected DID %s, got %s", req.DID, user.DID)
99
+
if user.Handle != req.Handle {
100
+
t.Errorf("Expected handle %s, got %s", req.Handle, user.Handle)
103
+
if user.CreatedAt.IsZero() {
104
+
t.Error("CreatedAt should not be zero")
108
+
// Test 2: Retrieve user by DID
109
+
t.Run("Get User By DID", func(t *testing.T) {
110
+
user, err := userService.GetUserByDID(ctx, "did:plc:test123456")
112
+
t.Fatalf("Failed to get user by DID: %v", err)
115
+
if user.Handle != "alice.test" {
116
+
t.Errorf("Expected handle alice.test, got %s", user.Handle)
120
+
// Test 3: Retrieve user by handle
121
+
t.Run("Get User By Handle", func(t *testing.T) {
122
+
user, err := userService.GetUserByHandle(ctx, "alice.test")
124
+
t.Fatalf("Failed to get user by handle: %v", err)
127
+
if user.DID != "did:plc:test123456" {
128
+
t.Errorf("Expected DID did:plc:test123456, got %s", user.DID)
132
+
// Test 4: Resolve handle to DID
133
+
t.Run("Resolve Handle to DID", func(t *testing.T) {
134
+
did, err := userService.ResolveHandleToDID(ctx, "alice.test")
136
+
t.Fatalf("Failed to resolve handle: %v", err)
139
+
if did != "did:plc:test123456" {
140
+
t.Errorf("Expected DID did:plc:test123456, got %s", did)
145
+
func TestGetProfileEndpoint(t *testing.T) {
77
-
// Wire up dependencies according to architecture
149
+
// Wire up dependencies
userRepo := postgres.NewUserRepository(db)
79
-
userService := users.NewUserService(userRepo)
151
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
153
+
// Create test user directly in service
154
+
ctx := context.Background()
155
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
156
+
DID: "did:plc:endpoint123",
157
+
Handle: "bob.test",
160
+
t.Fatalf("Failed to create test user: %v", err)
163
+
// Set up HTTP router
82
-
r.Mount("/api/users", routes.UserRoutes(userService))
165
+
r.Mount("/xrpc/social.coves.actor", routes.UserRoutes(userService))
167
+
// Test 1: Get profile by DID
168
+
t.Run("Get Profile By DID", func(t *testing.T) {
169
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile?actor=did:plc:endpoint123", nil)
170
+
w := httptest.NewRecorder()
171
+
r.ServeHTTP(w, req)
173
+
if w.Code != http.StatusOK {
174
+
t.Errorf("Expected status %d, got %d. Response: %s", http.StatusOK, w.Code, w.Body.String())
178
+
var response map[string]interface{}
179
+
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
180
+
t.Fatalf("Failed to decode response: %v", err)
183
+
if response["did"] != "did:plc:endpoint123" {
184
+
t.Errorf("Expected DID did:plc:endpoint123, got %v", response["did"])
188
+
// Test 2: Get profile by handle
189
+
t.Run("Get Profile By Handle", func(t *testing.T) {
190
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile?actor=bob.test", nil)
191
+
w := httptest.NewRecorder()
192
+
r.ServeHTTP(w, req)
194
+
if w.Code != http.StatusOK {
195
+
t.Errorf("Expected status %d, got %d. Response: %s", http.StatusOK, w.Code, w.Body.String())
199
+
var response map[string]interface{}
200
+
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
201
+
t.Fatalf("Failed to decode response: %v", err)
204
+
profile := response["profile"].(map[string]interface{})
205
+
if profile["handle"] != "bob.test" {
206
+
t.Errorf("Expected handle bob.test, got %v", profile["handle"])
210
+
// Test 3: Missing actor parameter
211
+
t.Run("Missing Actor Parameter", func(t *testing.T) {
212
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile", nil)
213
+
w := httptest.NewRecorder()
214
+
r.ServeHTTP(w, req)
216
+
if w.Code != http.StatusBadRequest {
217
+
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
221
+
// Test 4: User not found
222
+
t.Run("User Not Found", func(t *testing.T) {
223
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile?actor=nonexistent.test", nil)
224
+
w := httptest.NewRecorder()
225
+
r.ServeHTTP(w, req)
227
+
if w.Code != http.StatusNotFound {
228
+
t.Errorf("Expected status %d, got %d", http.StatusNotFound, w.Code)
233
+
// TestDuplicateCreation tests that duplicate DID/handle creation fails properly
234
+
func TestDuplicateCreation(t *testing.T) {
235
+
db := setupTestDB(t)
238
+
userRepo := postgres.NewUserRepository(db)
239
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
240
+
ctx := context.Background()
84
-
user := users.CreateUserRequest{
85
-
Email: "test@example.com",
86
-
Username: "testuser",
242
+
// Create first user
243
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
244
+
DID: "did:plc:duplicate123",
245
+
Handle: "duplicate.test",
248
+
t.Fatalf("Failed to create first user: %v", err)
89
-
body, _ := json.Marshal(user)
90
-
req := httptest.NewRequest("POST", "/api/users", bytes.NewBuffer(body))
91
-
req.Header.Set("Content-Type", "application/json")
251
+
// Test duplicate DID
252
+
t.Run("Duplicate DID", func(t *testing.T) {
253
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
254
+
DID: "did:plc:duplicate123",
255
+
Handle: "different.test",
93
-
w := httptest.NewRecorder()
259
+
t.Error("Expected error for duplicate DID, got nil")
96
-
if w.Code != http.StatusCreated {
97
-
t.Errorf("Expected status %d, got %d. Response: %s", http.StatusCreated, w.Code, w.Body.String())
262
+
if !strings.Contains(err.Error(), "DID already exists") {
263
+
t.Errorf("Expected 'DID already exists' error, got: %v", err)
101
-
var createdUser users.User
102
-
if err := json.NewDecoder(w.Body).Decode(&createdUser); err != nil {
103
-
t.Fatalf("Failed to decode response: %v", err)
267
+
// Test duplicate handle
268
+
t.Run("Duplicate Handle", func(t *testing.T) {
269
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
270
+
DID: "did:plc:different456",
271
+
Handle: "duplicate.test",
275
+
t.Error("Expected error for duplicate handle, got nil")
278
+
if !strings.Contains(err.Error(), "handle already taken") {
279
+
t.Errorf("Expected 'handle already taken' error, got: %v", err)
284
+
// TestHandleValidation tests atProto handle validation rules
285
+
func TestHandleValidation(t *testing.T) {
286
+
db := setupTestDB(t)
106
-
if createdUser.Email != user.Email {
107
-
t.Errorf("Expected email %s, got %s", user.Email, createdUser.Email)
289
+
userRepo := postgres.NewUserRepository(db)
290
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
291
+
ctx := context.Background()
293
+
testCases := []struct {
301
+
name: "Valid handle with hyphen",
302
+
did: "did:plc:valid1",
303
+
handle: "alice-bob.test",
304
+
shouldError: false,
307
+
name: "Valid handle with dots",
308
+
did: "did:plc:valid2",
309
+
handle: "alice.bob.test",
310
+
shouldError: false,
313
+
name: "Invalid: consecutive hyphens",
314
+
did: "did:plc:invalid1",
315
+
handle: "alice--bob.test",
317
+
errorMsg: "consecutive hyphens",
320
+
name: "Invalid: starts with hyphen",
321
+
did: "did:plc:invalid2",
322
+
handle: "-alice.test",
324
+
errorMsg: "invalid handle format",
327
+
name: "Invalid: ends with hyphen",
328
+
did: "did:plc:invalid3",
329
+
handle: "alice-.test",
331
+
errorMsg: "invalid handle format",
334
+
name: "Invalid: special characters",
335
+
did: "did:plc:invalid4",
336
+
handle: "alice!bob.test",
338
+
errorMsg: "invalid handle format",
341
+
name: "Invalid: spaces",
342
+
did: "did:plc:invalid5",
343
+
handle: "alice bob.test",
345
+
errorMsg: "invalid handle format",
348
+
name: "Invalid: too long",
349
+
did: "did:plc:invalid6",
350
+
handle: strings.Repeat("a", 254) + ".test",
352
+
errorMsg: "must be between 1 and 253 characters",
355
+
name: "Invalid: missing DID prefix",
356
+
did: "plc:invalid7",
357
+
handle: "valid.test",
359
+
errorMsg: "must start with 'did:'",
110
-
if createdUser.Username != user.Username {
111
-
t.Errorf("Expected username %s, got %s", user.Username, createdUser.Username)
363
+
for _, tc := range testCases {
364
+
t.Run(tc.name, func(t *testing.T) {
365
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
370
+
if tc.shouldError {
372
+
t.Errorf("Expected error, got nil")
373
+
} else if !strings.Contains(err.Error(), tc.errorMsg) {
374
+
t.Errorf("Expected error containing '%s', got: %v", tc.errorMsg, err)
378
+
t.Errorf("Expected no error, got: %v", err)