package profile import ( "context" "encoding/json" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" domainProfile "base/internal/domain/profile" ) func TestProfileRepository_Create(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("create profile successfully", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "test-handle", PageSectionOrder: map[string]int{ "hero": 1, "about": 2, "skills": 3, }, Hero: domainProfile.Hero{ FirstName: "John", LastName: "Doe", Company: "Test Company", ShortDescription: "Test description", CTAEnabled: true, }, About: domainProfile.About{ ProfilePicture: "https://example.com/pic.jpg", About: "About me", }, Contact: domainProfile.Contact{ Email: "john.doe@example.com", Phone: "1234567890", }, PageSetting: domainProfile.PageSetting{ VisibilityLevel: "public", }, } err := repo.Create(ctx, profile) assert.NoError(t, err) assert.NotEqual(t, uuid.Nil, profile.ID) // Verify profile was created found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Equal(t, profile.Handle, found.Handle) assert.Equal(t, profile.Hero.FirstName, found.Hero.FirstName) assert.Equal(t, profile.Hero.LastName, found.Hero.LastName) assert.Equal(t, profile.Contact.Email, found.Contact.Email) }) t.Run("create profile with skills", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "test-handle-with-skills", Hero: domainProfile.Hero{ FirstName: "Jane", LastName: "Smith", }, Skills: []domainProfile.Skill{ {SkillName: "Go", Level: "expert"}, {SkillName: "Python", Level: "intermediate"}, }, } err := repo.Create(ctx, profile) assert.NoError(t, err) // Verify profile with skills found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.Skills, 2) assert.Equal(t, "Go", found.Skills[0].SkillName) assert.Equal(t, "expert", found.Skills[0].Level) }) t.Run("create profile with social links", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "test-handle-with-links", Hero: domainProfile.Hero{ FirstName: "Bob", LastName: "Johnson", }, Contact: domainProfile.Contact{ SocialLinks: []domainProfile.SocialLink{ {LinkType: "linkedin", Link: "https://linkedin.com/in/bob"}, {LinkType: "github", Link: "https://github.com/bob"}, }, }, } err := repo.Create(ctx, profile) assert.NoError(t, err) // Verify profile with social links found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.Contact.SocialLinks, 2) assert.Equal(t, "linkedin", found.Contact.SocialLinks[0].LinkType) }) t.Run("create profile with achievements", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "test-handle-with-achievements", Hero: domainProfile.Hero{ FirstName: "Alice", LastName: "Williams", }, About: domainProfile.About{ Achievements: []domainProfile.Achievement{ {Title: "Projects", Value: "50", Enabled: true}, {Title: "Clients", Value: "100", Enabled: true}, }, }, } err := repo.Create(ctx, profile) assert.NoError(t, err) // Verify profile with achievements found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.About.Achievements, 2) assert.Equal(t, "Projects", found.About.Achievements[0].Title) assert.Equal(t, "50", found.About.Achievements[0].Value) }) t.Run("create profile with duplicate handle fails", func(t *testing.T) { handle := "duplicate-handle" profile1 := &domainProfile.Profile{ ID: uuid.New(), Handle: handle, Hero: domainProfile.Hero{ FirstName: "First", LastName: "User", }, } err := repo.Create(ctx, profile1) assert.NoError(t, err) profile2 := &domainProfile.Profile{ ID: uuid.New(), Handle: handle, Hero: domainProfile.Hero{ FirstName: "Second", LastName: "User", }, } err = repo.Create(ctx, profile2) assert.Error(t, err) }) t.Run("create profile with role", func(t *testing.T) { roleID := uuid.New() roleName := "Software Engineer" profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "test-handle-with-role", Hero: domainProfile.Hero{ Role: &domainProfile.Role{ ID: roleID, Title: roleName, }, FirstName: "Role", LastName: "User", }, } err := repo.Create(ctx, profile) assert.NoError(t, err) // Verify profile with role found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.NotNil(t, found.Hero.Role) assert.Equal(t, roleID, found.Hero.Role.ID) assert.Equal(t, roleName, found.Hero.Role.Title) }) } func TestProfileRepository_FindByHandle(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("find profile by handle successfully", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "find-by-handle", Hero: domainProfile.Hero{ FirstName: "Find", LastName: "Handle", }, Contact: domainProfile.Contact{ Email: "find@example.com", }, } err := repo.Create(ctx, profile) require.NoError(t, err) found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.NotNil(t, found) assert.Equal(t, profile.Handle, found.Handle) assert.Equal(t, profile.Hero.FirstName, found.Hero.FirstName) assert.Equal(t, profile.Contact.Email, found.Contact.Email) }) t.Run("find non-existent profile returns error", func(t *testing.T) { found, err := repo.FindByHandle(ctx, "non-existent-handle") assert.Error(t, err) assert.Nil(t, found) assert.Contains(t, err.Error(), "profile not found") }) t.Run("find profile with all related data", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "find-with-all-data", Hero: domainProfile.Hero{ FirstName: "All", LastName: "Data", }, Skills: []domainProfile.Skill{ {SkillName: "JavaScript", Level: "advanced"}, }, Contact: domainProfile.Contact{ SocialLinks: []domainProfile.SocialLink{ {LinkType: "twitter", Link: "https://twitter.com/user"}, }, }, About: domainProfile.About{ Achievements: []domainProfile.Achievement{ {Title: "Years", Value: "10", Enabled: true}, }, }, } err := repo.Create(ctx, profile) require.NoError(t, err) found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.Skills, 1) assert.Len(t, found.Contact.SocialLinks, 1) assert.Len(t, found.About.Achievements, 1) }) } func TestProfileRepository_FindByUserID(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("find profile by user ID successfully", func(t *testing.T) { userID := uuid.New() profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "find-by-user-id", Hero: domainProfile.Hero{ FirstName: "User", LastName: "ID", }, } // Create profile with user_id manually since it's not in the domain model model, err := toProfileModel(profile) require.NoError(t, err) model.UserID = &userID err = db.WithContext(ctx).Create(model).Error require.NoError(t, err) found, err := repo.FindByUserID(ctx, userID) assert.NoError(t, err) assert.NotNil(t, found) assert.Equal(t, profile.Handle, found.Handle) }) t.Run("find non-existent user ID returns error", func(t *testing.T) { nonExistentUserID := uuid.New() found, err := repo.FindByUserID(ctx, nonExistentUserID) assert.Error(t, err) assert.Nil(t, found) assert.Contains(t, err.Error(), "profile not found") }) } func TestProfileRepository_Update(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("update profile successfully", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "update-profile", Hero: domainProfile.Hero{ FirstName: "Original", LastName: "Name", Company: "Old Company", }, Contact: domainProfile.Contact{ Email: "original@example.com", }, } err := repo.Create(ctx, profile) require.NoError(t, err) // Update profile profile.Hero.FirstName = "Updated" profile.Hero.Company = "New Company" profile.Contact.Email = "updated@example.com" err = repo.Update(ctx, profile) assert.NoError(t, err) // Verify update found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Equal(t, "Updated", found.Hero.FirstName) assert.Equal(t, "New Company", found.Hero.Company) assert.Equal(t, "updated@example.com", found.Contact.Email) }) t.Run("update profile with new skills", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "update-skills", Hero: domainProfile.Hero{ FirstName: "Skills", LastName: "User", }, Skills: []domainProfile.Skill{ {SkillName: "Go", Level: "beginner"}, }, } err := repo.Create(ctx, profile) require.NoError(t, err) // Update with new skills profile.Skills = []domainProfile.Skill{ {SkillName: "Go", Level: "expert"}, {SkillName: "Rust", Level: "intermediate"}, } err = repo.Update(ctx, profile) assert.NoError(t, err) // Verify skills were updated found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.Skills, 2) // Check that old skill is gone and new ones exist skillMap := make(map[string]string) for _, skill := range found.Skills { skillMap[skill.SkillName] = skill.Level } assert.Equal(t, "expert", skillMap["Go"]) assert.Equal(t, "intermediate", skillMap["Rust"]) }) t.Run("update profile with new social links", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "update-links", Hero: domainProfile.Hero{ FirstName: "Links", LastName: "User", }, Contact: domainProfile.Contact{ SocialLinks: []domainProfile.SocialLink{ {LinkType: "linkedin", Link: "https://linkedin.com/old"}, }, }, } err := repo.Create(ctx, profile) require.NoError(t, err) // Update with new social links profile.Contact.SocialLinks = []domainProfile.SocialLink{ {LinkType: "github", Link: "https://github.com/new"}, {LinkType: "twitter", Link: "https://twitter.com/new"}, } err = repo.Update(ctx, profile) assert.NoError(t, err) // Verify social links were updated found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.Contact.SocialLinks, 2) linkTypes := make(map[string]bool) for _, link := range found.Contact.SocialLinks { linkTypes[link.LinkType] = true } assert.True(t, linkTypes["github"]) assert.True(t, linkTypes["twitter"]) assert.False(t, linkTypes["linkedin"]) }) t.Run("update profile with new achievements", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "update-achievements", Hero: domainProfile.Hero{ FirstName: "Achievements", LastName: "User", }, About: domainProfile.About{ Achievements: []domainProfile.Achievement{ {Title: "Old", Value: "1", Enabled: true}, }, }, } err := repo.Create(ctx, profile) require.NoError(t, err) // Update with new achievements profile.About.Achievements = []domainProfile.Achievement{ {Title: "New1", Value: "10", Enabled: true}, {Title: "New2", Value: "20", Enabled: false}, } err = repo.Update(ctx, profile) assert.NoError(t, err) // Verify achievements were updated found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Len(t, found.About.Achievements, 2) achievementMap := make(map[string]string) for _, achievement := range found.About.Achievements { achievementMap[achievement.Title] = achievement.Value } assert.Equal(t, "10", achievementMap["New1"]) assert.Equal(t, "20", achievementMap["New2"]) }) t.Run("update profile page section order", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "update-page-order", Hero: domainProfile.Hero{ FirstName: "Page", LastName: "Order", }, PageSectionOrder: map[string]int{ "hero": 1, }, } err := repo.Create(ctx, profile) require.NoError(t, err) // Update page section order profile.PageSectionOrder = map[string]int{ "about": 1, "hero": 2, "skills": 3, } err = repo.Update(ctx, profile) assert.NoError(t, err) // Verify page section order was updated found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.Equal(t, 1, found.PageSectionOrder["about"]) assert.Equal(t, 2, found.PageSectionOrder["hero"]) assert.Equal(t, 3, found.PageSectionOrder["skills"]) }) } func TestProfileRepository_Delete(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("delete profile successfully", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "delete-profile", Hero: domainProfile.Hero{ FirstName: "Delete", LastName: "User", }, Skills: []domainProfile.Skill{ {SkillName: "Go", Level: "expert"}, }, Contact: domainProfile.Contact{ SocialLinks: []domainProfile.SocialLink{ {LinkType: "github", Link: "https://github.com/user"}, }, }, About: domainProfile.About{ Achievements: []domainProfile.Achievement{ {Title: "Projects", Value: "10", Enabled: true}, }, }, } err := repo.Create(ctx, profile) require.NoError(t, err) err = repo.Delete(ctx, profile) assert.NoError(t, err) // Verify deletion found, err := repo.FindByHandle(ctx, profile.Handle) assert.Error(t, err) assert.Nil(t, found) assert.Contains(t, err.Error(), "profile not found") }) } func TestProfileRepository_FindAll(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() // Create test profiles roleID1 := uuid.New() roleID2 := uuid.New() profiles := []*domainProfile.Profile{ { ID: uuid.New(), Handle: "findall-1", Hero: domainProfile.Hero{ Role: &domainProfile.Role{ ID: roleID1, Title: "Engineer", }, FirstName: "Alice", LastName: "Anderson", Company: "Tech Corp", }, Skills: []domainProfile.Skill{ {SkillName: "Go", Level: "expert"}, {SkillName: "Python", Level: "intermediate"}, }, }, { ID: uuid.New(), Handle: "findall-2", Hero: domainProfile.Hero{ Role: &domainProfile.Role{ ID: roleID1, Title: "Engineer", }, FirstName: "Bob", LastName: "Brown", Company: "Tech Corp", }, Skills: []domainProfile.Skill{ {SkillName: "JavaScript", Level: "expert"}, }, }, { ID: uuid.New(), Handle: "findall-3", Hero: domainProfile.Hero{ Role: &domainProfile.Role{ ID: roleID2, Title: "Designer", }, FirstName: "Charlie", LastName: "Clark", Company: "Design Inc", }, Skills: []domainProfile.Skill{ {SkillName: "Figma", Level: "expert"}, }, }, } for _, profile := range profiles { err := repo.Create(ctx, profile) require.NoError(t, err) // Add small delay to ensure different timestamps time.Sleep(10 * time.Millisecond) } t.Run("find all profiles without filters", func(t *testing.T) { filter := domainProfile.Filter{ Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.GreaterOrEqual(t, total, 3) assert.GreaterOrEqual(t, len(results), 3) }) t.Run("find profiles by role ID", func(t *testing.T) { filter := domainProfile.Filter{ RoleID: roleID1, Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 2, total) assert.Len(t, results, 2) for _, result := range results { assert.NotNil(t, result.Hero.Role) assert.Equal(t, roleID1, result.Hero.Role.ID) } }) t.Run("find profiles by first name", func(t *testing.T) { filter := domainProfile.Filter{ FirstName: "alice", Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 1, total) assert.Len(t, results, 1) assert.Equal(t, "Alice", results[0].Hero.FirstName) }) t.Run("find profiles by last name", func(t *testing.T) { filter := domainProfile.Filter{ LastName: "brown", Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 1, total) assert.Len(t, results, 1) assert.Equal(t, "Brown", results[0].Hero.LastName) }) t.Run("find profiles by company", func(t *testing.T) { filter := domainProfile.Filter{ Company: "tech", Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 2, total) assert.Len(t, results, 2) for _, result := range results { assert.Contains(t, result.Hero.Company, "Tech") } }) t.Run("find profiles by skill name", func(t *testing.T) { filter := domainProfile.Filter{ SkillName: "go", Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 1, total) assert.Len(t, results, 1) assert.Equal(t, "findall-1", results[0].Handle) // Verify the profile has the skill hasGoSkill := false for _, skill := range results[0].Skills { if skill.SkillName == "Go" { hasGoSkill = true break } } assert.True(t, hasGoSkill) }) t.Run("find profiles with pagination", func(t *testing.T) { filter := domainProfile.Filter{ Page: 1, PageSize: 2, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.GreaterOrEqual(t, total, 3) assert.Len(t, results, 2) // Second page filter.Page = 2 results2, total2, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, total, total2) assert.GreaterOrEqual(t, len(results2), 1) }) t.Run("find profiles with sorting", func(t *testing.T) { filter := domainProfile.Filter{ Page: 1, PageSize: 10, SortedBy: "first_name", Ascending: true, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.GreaterOrEqual(t, total, 3) assert.GreaterOrEqual(t, len(results), 3) // Verify sorting (first result should be Alice) assert.Equal(t, "Alice", results[0].Hero.FirstName) // Test descending order filter.Ascending = false results2, _, err := repo.FindAll(ctx, filter) assert.NoError(t, err) // Last result should be Alice (or one of the first names alphabetically) assert.NotEqual(t, "Alice", results2[0].Hero.FirstName) }) t.Run("find profiles with combined filters", func(t *testing.T) { filter := domainProfile.Filter{ RoleID: roleID1, Company: "tech", Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 2, total) assert.Len(t, results, 2) for _, result := range results { assert.NotNil(t, result.Hero.Role) assert.Equal(t, roleID1, result.Hero.Role.ID) assert.Contains(t, result.Hero.Company, "Tech") } }) t.Run("find profiles with empty result", func(t *testing.T) { filter := domainProfile.Filter{ FirstName: "nonexistent", Page: 1, PageSize: 10, } results, total, err := repo.FindAll(ctx, filter) assert.NoError(t, err) assert.Equal(t, 0, total) assert.Len(t, results, 0) }) } func TestProfileRepository_PageSectionOrder(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("create and retrieve profile with page section order", func(t *testing.T) { pageSectionOrder := map[string]int{ "hero": 1, "about": 2, "skills": 3, "contact": 4, } profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "page-order-test", PageSectionOrder: pageSectionOrder, Hero: domainProfile.Hero{ FirstName: "Page", LastName: "Order", }, } err := repo.Create(ctx, profile) assert.NoError(t, err) found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) assert.NotNil(t, found.PageSectionOrder) assert.Equal(t, 1, found.PageSectionOrder["hero"]) assert.Equal(t, 2, found.PageSectionOrder["about"]) assert.Equal(t, 3, found.PageSectionOrder["skills"]) assert.Equal(t, 4, found.PageSectionOrder["contact"]) }) t.Run("create profile with empty page section order", func(t *testing.T) { profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "empty-page-order", Hero: domainProfile.Hero{ FirstName: "Empty", LastName: "Order", }, } err := repo.Create(ctx, profile) assert.NoError(t, err) found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) // Empty map should be returned as empty or nil assert.NotNil(t, found) }) } // Helper function to verify JSON marshaling/unmarshaling works correctly func TestProfileRepository_JSONSerialization(t *testing.T) { db := setupTestDB(t) repo := createTestProfileRepository(db) ctx := context.Background() t.Run("verify page section order JSON serialization", func(t *testing.T) { complexOrder := map[string]int{ "section1": 10, "section2": 20, "section3": 30, } profile := &domainProfile.Profile{ ID: uuid.New(), Handle: "json-test", PageSectionOrder: complexOrder, Hero: domainProfile.Hero{ FirstName: "JSON", LastName: "Test", }, } err := repo.Create(ctx, profile) assert.NoError(t, err) // Verify the data can be serialized/deserialized correctly found, err := repo.FindByHandle(ctx, profile.Handle) assert.NoError(t, err) // Re-serialize to verify round-trip jsonData, err := json.Marshal(found.PageSectionOrder) assert.NoError(t, err) var unmarshaled map[string]int err = json.Unmarshal(jsonData, &unmarshaled) assert.NoError(t, err) assert.Equal(t, complexOrder, unmarshaled) }) }