···
"github.com/go-chi/chi/v5"
···
"github.com/pressly/goose/v3"
"Coves/internal/api/routes"
+
"Coves/internal/core/users"
+
"Coves/internal/db/postgres"
func setupTestDB(t *testing.T) *sql.DB {
// Build connection string from environment variables (set by .env.dev)
testUser := os.Getenv("POSTGRES_TEST_USER")
testPassword := os.Getenv("POSTGRES_TEST_PASSWORD")
testPort := os.Getenv("POSTGRES_TEST_PORT")
···
// Clean up any existing test data
+
_, err = db.Exec("DELETE FROM users WHERE handle LIKE '%.test'")
t.Logf("Warning: Failed to clean up test data: %v", err)
···
+
func TestUserCreationAndRetrieval(t *testing.T) {
+
// Wire up dependencies
+
userRepo := postgres.NewUserRepository(db)
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
+
ctx := context.Background()
+
// Test 1: Create a user
+
t.Run("Create User", func(t *testing.T) {
+
req := users.CreateUserRequest{
+
DID: "did:plc:test123456",
+
user, err := userService.CreateUser(ctx, req)
+
t.Fatalf("Failed to create user: %v", err)
+
if user.DID != req.DID {
+
t.Errorf("Expected DID %s, got %s", req.DID, user.DID)
+
if user.Handle != req.Handle {
+
t.Errorf("Expected handle %s, got %s", req.Handle, user.Handle)
+
if user.CreatedAt.IsZero() {
+
t.Error("CreatedAt should not be zero")
+
// Test 2: Retrieve user by DID
+
t.Run("Get User By DID", func(t *testing.T) {
+
user, err := userService.GetUserByDID(ctx, "did:plc:test123456")
+
t.Fatalf("Failed to get user by DID: %v", err)
+
if user.Handle != "alice.test" {
+
t.Errorf("Expected handle alice.test, got %s", user.Handle)
+
// Test 3: Retrieve user by handle
+
t.Run("Get User By Handle", func(t *testing.T) {
+
user, err := userService.GetUserByHandle(ctx, "alice.test")
+
t.Fatalf("Failed to get user by handle: %v", err)
+
if user.DID != "did:plc:test123456" {
+
t.Errorf("Expected DID did:plc:test123456, got %s", user.DID)
+
// Test 4: Resolve handle to DID
+
t.Run("Resolve Handle to DID", func(t *testing.T) {
+
did, err := userService.ResolveHandleToDID(ctx, "alice.test")
+
t.Fatalf("Failed to resolve handle: %v", err)
+
if did != "did:plc:test123456" {
+
t.Errorf("Expected DID did:plc:test123456, got %s", did)
+
func TestGetProfileEndpoint(t *testing.T) {
+
// Wire up dependencies
userRepo := postgres.NewUserRepository(db)
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
+
// Create test user directly in service
+
ctx := context.Background()
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
+
DID: "did:plc:endpoint123",
+
t.Fatalf("Failed to create test user: %v", err)
+
r.Mount("/xrpc/social.coves.actor", routes.UserRoutes(userService))
+
// Test 1: Get profile by DID
+
t.Run("Get Profile By DID", func(t *testing.T) {
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile?actor=did:plc:endpoint123", nil)
+
w := httptest.NewRecorder()
+
if w.Code != http.StatusOK {
+
t.Errorf("Expected status %d, got %d. Response: %s", http.StatusOK, w.Code, w.Body.String())
+
var response map[string]interface{}
+
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
+
t.Fatalf("Failed to decode response: %v", err)
+
if response["did"] != "did:plc:endpoint123" {
+
t.Errorf("Expected DID did:plc:endpoint123, got %v", response["did"])
+
// Test 2: Get profile by handle
+
t.Run("Get Profile By Handle", func(t *testing.T) {
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile?actor=bob.test", nil)
+
w := httptest.NewRecorder()
+
if w.Code != http.StatusOK {
+
t.Errorf("Expected status %d, got %d. Response: %s", http.StatusOK, w.Code, w.Body.String())
+
var response map[string]interface{}
+
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
+
t.Fatalf("Failed to decode response: %v", err)
+
profile := response["profile"].(map[string]interface{})
+
if profile["handle"] != "bob.test" {
+
t.Errorf("Expected handle bob.test, got %v", profile["handle"])
+
// Test 3: Missing actor parameter
+
t.Run("Missing Actor Parameter", func(t *testing.T) {
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile", nil)
+
w := httptest.NewRecorder()
+
if w.Code != http.StatusBadRequest {
+
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
+
// Test 4: User not found
+
t.Run("User Not Found", func(t *testing.T) {
+
req := httptest.NewRequest("GET", "/xrpc/social.coves.actor/profile?actor=nonexistent.test", nil)
+
w := httptest.NewRecorder()
+
if w.Code != http.StatusNotFound {
+
t.Errorf("Expected status %d, got %d", http.StatusNotFound, w.Code)
+
// TestDuplicateCreation tests that duplicate DID/handle creation fails properly
+
func TestDuplicateCreation(t *testing.T) {
+
userRepo := postgres.NewUserRepository(db)
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
+
ctx := context.Background()
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
+
DID: "did:plc:duplicate123",
+
Handle: "duplicate.test",
+
t.Fatalf("Failed to create first user: %v", err)
+
t.Run("Duplicate DID", func(t *testing.T) {
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
+
DID: "did:plc:duplicate123",
+
Handle: "different.test",
+
t.Error("Expected error for duplicate DID, got nil")
+
if !strings.Contains(err.Error(), "DID already exists") {
+
t.Errorf("Expected 'DID already exists' error, got: %v", err)
+
// Test duplicate handle
+
t.Run("Duplicate Handle", func(t *testing.T) {
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
+
DID: "did:plc:different456",
+
Handle: "duplicate.test",
+
t.Error("Expected error for duplicate handle, got nil")
+
if !strings.Contains(err.Error(), "handle already taken") {
+
t.Errorf("Expected 'handle already taken' error, got: %v", err)
+
// TestHandleValidation tests atProto handle validation rules
+
func TestHandleValidation(t *testing.T) {
+
userRepo := postgres.NewUserRepository(db)
+
userService := users.NewUserService(userRepo, "http://localhost:3001")
+
ctx := context.Background()
+
testCases := []struct {
+
name: "Valid handle with hyphen",
+
handle: "alice-bob.test",
+
name: "Valid handle with dots",
+
handle: "alice.bob.test",
+
name: "Invalid: consecutive hyphens",
+
did: "did:plc:invalid1",
+
handle: "alice--bob.test",
+
errorMsg: "consecutive hyphens",
+
name: "Invalid: starts with hyphen",
+
did: "did:plc:invalid2",
+
errorMsg: "invalid handle format",
+
name: "Invalid: ends with hyphen",
+
did: "did:plc:invalid3",
+
errorMsg: "invalid handle format",
+
name: "Invalid: special characters",
+
did: "did:plc:invalid4",
+
handle: "alice!bob.test",
+
errorMsg: "invalid handle format",
+
name: "Invalid: spaces",
+
did: "did:plc:invalid5",
+
handle: "alice bob.test",
+
errorMsg: "invalid handle format",
+
name: "Invalid: too long",
+
did: "did:plc:invalid6",
+
handle: strings.Repeat("a", 254) + ".test",
+
errorMsg: "must be between 1 and 253 characters",
+
name: "Invalid: missing DID prefix",
+
errorMsg: "must start with 'did:'",
+
for _, tc := range testCases {
+
t.Run(tc.name, func(t *testing.T) {
+
_, err := userService.CreateUser(ctx, users.CreateUserRequest{
+
t.Errorf("Expected error, got nil")
+
} else if !strings.Contains(err.Error(), tc.errorMsg) {
+
t.Errorf("Expected error containing '%s', got: %v", tc.errorMsg, err)
+
t.Errorf("Expected no error, got: %v", err)