Files
base/internal/repository/postgres/profile/profile_test.go
2026-04-10 18:25:21 +03:30

871 lines
22 KiB
Go

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)
})
}