initial commit
This commit is contained in:
426
internal/application/specialist/service.go
Normal file
426
internal/application/specialist/service.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package specialist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
appProfile "base/internal/application/profile"
|
||||
domainAsset "base/internal/domain/asset"
|
||||
domainProfile "base/internal/domain/profile"
|
||||
"base/internal/dto"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Overview(ctx context.Context, userID uuid.UUID) (*dto.SpecialistOverviewFetchedResponse, error)
|
||||
UpdateHero(ctx context.Context, userID uuid.UUID, req dto.HeroDTO) error
|
||||
UpdateContact(ctx context.Context, userID uuid.UUID, req dto.ContactDTO) error
|
||||
UpdateSkills(ctx context.Context, userID uuid.UUID, req dto.SkillsUpdateRequest) error
|
||||
GetPageSections(ctx context.Context, userID uuid.UUID) (*dto.PageSectionsResponse, error)
|
||||
GetProfile(ctx context.Context, userID uuid.UUID) (*dto.ProfileResponse, error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
logger zerolog.Logger
|
||||
profileRepo domainProfile.Repository
|
||||
assetRepo domainAsset.AssetRepository
|
||||
}
|
||||
|
||||
// Param holds dependencies for the specialist overview service.
|
||||
type Param struct {
|
||||
Logger zerolog.Logger
|
||||
ProfileRepo domainProfile.Repository
|
||||
AssetRepo domainAsset.AssetRepository
|
||||
|
||||
fx.In
|
||||
}
|
||||
|
||||
func New(param Param) Service {
|
||||
return &service{
|
||||
logger: param.Logger,
|
||||
profileRepo: param.ProfileRepo,
|
||||
assetRepo: param.AssetRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Overview(ctx context.Context, userID uuid.UUID) (*dto.SpecialistOverviewFetchedResponse, error) {
|
||||
resp := &struct {
|
||||
profile *domainProfile.Profile
|
||||
assets []*domainAsset.Asset
|
||||
recentlyJoined []*domainProfile.Profile
|
||||
totalProfiles int
|
||||
totalAssets int
|
||||
}{}
|
||||
|
||||
g, gCtx := errgroup.WithContext(ctx)
|
||||
|
||||
// 1. Profile by OwnerID (includes Skills) + Assets by ProfileID
|
||||
g.Go(func() error {
|
||||
profile, err := s.profileRepo.FindByUserID(gCtx, userID)
|
||||
if err != nil {
|
||||
return domainProfile.ErrProfileNotFound
|
||||
}
|
||||
resp.profile = profile
|
||||
|
||||
assets, err := s.assetRepo.FindByProfileID(gCtx, profile.ID)
|
||||
if err != nil {
|
||||
assets = []*domainAsset.Asset{}
|
||||
}
|
||||
resp.assets = assets
|
||||
return nil
|
||||
})
|
||||
|
||||
// 2. Latest 6 profiles + total profiles count (FindAll returns both)
|
||||
g.Go(func() error {
|
||||
profiles, total, err := s.profileRepo.FindAll(gCtx, domainProfile.Filter{
|
||||
Page: 1,
|
||||
PageSize: 6,
|
||||
SortedBy: "created_at",
|
||||
Ascending: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.recentlyJoined = profiles
|
||||
resp.totalProfiles = total
|
||||
return nil
|
||||
})
|
||||
|
||||
// 3. Total assets count
|
||||
g.Go(func() error {
|
||||
count, err := s.assetRepo.Count(gCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.totalAssets = count
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assets := s.toOverviewAssets(resp.assets, userID)
|
||||
flatProfiles := ToFlatProfiles(resp.recentlyJoined)
|
||||
|
||||
profileResp := appProfile.DomainProfileToProfileResponse(resp.profile)
|
||||
var skills []dto.SkillDTO
|
||||
if profileResp != nil {
|
||||
skills = profileResp.Skills
|
||||
}
|
||||
|
||||
tasks := s.computeTasks(resp.profile, resp.assets)
|
||||
completionPercent := s.computeCompletionPercent(tasks)
|
||||
|
||||
return &dto.SpecialistOverviewFetchedResponse{
|
||||
Message: "",
|
||||
Data: dto.SpecialistOverviewFetchedDataDTO{
|
||||
Assets: assets,
|
||||
RecentlyJoined: flatProfiles,
|
||||
Analytics: dto.AnalyticsDTO{TotalAssets: resp.totalAssets, TotalProfiles: resp.totalProfiles},
|
||||
Profile: profileResp,
|
||||
Skills: skills,
|
||||
CompletionPercent: completionPercent,
|
||||
Tasks: tasks,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// computeTasks derives task flags from profile and assets. true = needs action.
|
||||
func (s *service) computeTasks(p *domainProfile.Profile, assets []*domainAsset.Asset) dto.TasksDTO {
|
||||
tasks := dto.TasksDTO{}
|
||||
if p == nil {
|
||||
return tasks
|
||||
}
|
||||
// profile_action: Hero section (firstName, lastName, shortDescription) incomplete
|
||||
tasks.ProfileAction = strings.TrimSpace(p.Hero.FirstName) == "" ||
|
||||
strings.TrimSpace(p.Hero.LastName) == "" ||
|
||||
strings.TrimSpace(p.Hero.ShortDescription) == ""
|
||||
|
||||
// about_action: About section incomplete
|
||||
tasks.AboutAction = strings.TrimSpace(p.About.ProfilePicture) == "" ||
|
||||
strings.TrimSpace(p.About.About) == ""
|
||||
|
||||
// publish_action: not public
|
||||
tasks.PublishAction = p.PageSetting.VisibilityLevel != "public"
|
||||
|
||||
// works_action: no assets
|
||||
tasks.WorksAction = len(assets) == 0
|
||||
|
||||
// skills_action: no skills
|
||||
tasks.SkillsAction = len(p.Skills) == 0
|
||||
|
||||
// social_action: no social links
|
||||
tasks.SocialAction = len(p.Contact.SocialLinks) == 0
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
// computeCompletionPercent: 6 sections, each complete = !action. Percent = (6 - actionsNeeded) / 6 * 100
|
||||
func (s *service) computeCompletionPercent(tasks dto.TasksDTO) int {
|
||||
complete := 0
|
||||
if !tasks.ProfileAction {
|
||||
complete++
|
||||
}
|
||||
if !tasks.AboutAction {
|
||||
complete++
|
||||
}
|
||||
if !tasks.PublishAction {
|
||||
complete++
|
||||
}
|
||||
if !tasks.WorksAction {
|
||||
complete++
|
||||
}
|
||||
if !tasks.SkillsAction {
|
||||
complete++
|
||||
}
|
||||
if !tasks.SocialAction {
|
||||
complete++
|
||||
}
|
||||
if complete == 0 {
|
||||
return 0
|
||||
}
|
||||
return (complete * 100) / 6
|
||||
}
|
||||
|
||||
func (s *service) UpdateHero(ctx context.Context, userID uuid.UUID, req dto.HeroDTO) error {
|
||||
p, err := s.profileRepo.FindByUserID(ctx, userID)
|
||||
if err != nil || p == nil {
|
||||
return domainProfile.ErrProfileNotFound
|
||||
}
|
||||
|
||||
p.Hero.FirstName = req.FirstName
|
||||
p.Hero.LastName = req.LastName
|
||||
p.Hero.Company = req.Company
|
||||
p.Hero.ShortDescription = req.ShortDescription
|
||||
p.Hero.ResumeLink = req.ResumeLink
|
||||
p.Hero.CTAEnabled = req.CTAEnabled
|
||||
p.Hero.Avatar = req.Avatar
|
||||
|
||||
if req.RoleID != nil {
|
||||
if p.Hero.Role == nil {
|
||||
p.Hero.Role = &domainProfile.Role{ID: *req.RoleID, Level: req.RoleLevel}
|
||||
} else {
|
||||
p.Hero.Role.ID = *req.RoleID
|
||||
p.Hero.Role.Level = req.RoleLevel
|
||||
}
|
||||
} else if req.RoleLevel != "" {
|
||||
if p.Hero.Role == nil {
|
||||
p.Hero.Role = &domainProfile.Role{Level: req.RoleLevel}
|
||||
} else {
|
||||
p.Hero.Role.Level = req.RoleLevel
|
||||
}
|
||||
}
|
||||
|
||||
p.UpdatedAt = time.Now()
|
||||
return s.profileRepo.Update(ctx, p)
|
||||
}
|
||||
|
||||
func (s *service) UpdateContact(ctx context.Context, userID uuid.UUID, req dto.ContactDTO) error {
|
||||
p, err := s.profileRepo.FindByUserID(ctx, userID)
|
||||
if err != nil || p == nil {
|
||||
return domainProfile.ErrProfileNotFound
|
||||
}
|
||||
p.Contact.Email = req.Email
|
||||
p.Contact.Phone = req.Phone
|
||||
p.Contact.SocialLinks = make([]domainProfile.SocialLink, len(req.SocialLinks))
|
||||
for i, sl := range req.SocialLinks {
|
||||
p.Contact.SocialLinks[i] = domainProfile.SocialLink{LinkType: sl.LinkType, Link: sl.Link}
|
||||
}
|
||||
p.UpdatedAt = time.Now()
|
||||
return s.profileRepo.Update(ctx, p)
|
||||
}
|
||||
|
||||
func (s *service) UpdateSkills(ctx context.Context, userID uuid.UUID, req dto.SkillsUpdateRequest) error {
|
||||
p, err := s.profileRepo.FindByUserID(ctx, userID)
|
||||
if err != nil || p == nil {
|
||||
return domainProfile.ErrProfileNotFound
|
||||
}
|
||||
p.Skills = make([]domainProfile.Skill, len(req.Skills))
|
||||
for i, s := range req.Skills {
|
||||
p.Skills[i] = domainProfile.Skill{SkillName: s.SkillName, Level: s.Level}
|
||||
}
|
||||
p.UpdatedAt = time.Now()
|
||||
return s.profileRepo.Update(ctx, p)
|
||||
}
|
||||
|
||||
func (s *service) GetPageSections(ctx context.Context, userID uuid.UUID) (*dto.PageSectionsResponse, error) {
|
||||
p, err := s.profileRepo.FindByUserID(ctx, userID)
|
||||
if err != nil || p == nil {
|
||||
return nil, domainProfile.ErrProfileNotFound
|
||||
}
|
||||
resp := appProfile.DomainProfileToProfileResponse(p)
|
||||
if resp == nil {
|
||||
return nil, domainProfile.ErrProfileNotFound
|
||||
}
|
||||
return &dto.PageSectionsResponse{
|
||||
Hero: resp.Hero,
|
||||
Contact: resp.Contact,
|
||||
Skills: resp.Skills,
|
||||
PageSectionOrder: resp.PageSectionOrder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *service) GetProfile(ctx context.Context, userID uuid.UUID) (*dto.ProfileResponse, error) {
|
||||
p, err := s.profileRepo.FindByUserID(ctx, userID)
|
||||
if err != nil || p == nil {
|
||||
return nil, domainProfile.ErrProfileNotFound
|
||||
}
|
||||
return appProfile.DomainProfileToProfileResponse(p), nil
|
||||
}
|
||||
|
||||
func (s *service) toOverviewAssets(assets []*domainAsset.Asset, ownerID uuid.UUID) []dto.OverviewAssetDTO {
|
||||
out := make([]dto.OverviewAssetDTO, len(assets))
|
||||
for i, a := range assets {
|
||||
out[i] = s.toOverviewAsset(a, ownerID)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *service) toOverviewAsset(a *domainAsset.Asset, ownerID uuid.UUID) dto.OverviewAssetDTO {
|
||||
price := 0
|
||||
coverImage := ""
|
||||
for _, art := range a.AssetArtifacts {
|
||||
if strings.Contains(strings.ToLower(art.Type), "image") {
|
||||
coverImage = art.DownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(a.AssetArtifacts) > 0 {
|
||||
price = a.AssetArtifacts[0].Price
|
||||
}
|
||||
|
||||
cat := (*dto.CategoryDTO)(nil)
|
||||
if a.AssetCategory.ID != uuid.Nil {
|
||||
cat = &dto.CategoryDTO{
|
||||
ID: a.AssetCategory.ID,
|
||||
Name: a.AssetCategory.Name,
|
||||
Icon: a.AssetCategory.Icon,
|
||||
Color: a.AssetCategory.Color,
|
||||
CardType: a.AssetCategory.CardType,
|
||||
Featured: a.AssetCategory.Featured,
|
||||
Description: a.AssetCategory.Description,
|
||||
}
|
||||
}
|
||||
|
||||
return dto.OverviewAssetDTO{
|
||||
ID: a.ID.String(),
|
||||
Title: a.Title,
|
||||
Description: a.Description,
|
||||
Content: a.Description,
|
||||
AssetCategoryID: a.AssetCategoryID.String(),
|
||||
AssetCategory: cat,
|
||||
CoverImage: coverImage,
|
||||
Link: a.Link,
|
||||
OwnerID: ownerID.String(),
|
||||
ProfileID: a.ProfileID.String(),
|
||||
Profile: nil,
|
||||
Price: price,
|
||||
Currency: "USD",
|
||||
Status: assetStatusToString(a.Status),
|
||||
Rating: 0,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func assetStatusToString(st domainAsset.Status) string {
|
||||
switch st {
|
||||
case domainAsset.StatusPublished:
|
||||
return "published"
|
||||
case domainAsset.StatusDisabled:
|
||||
return "disabled"
|
||||
case domainAsset.StatusPending:
|
||||
return "pending"
|
||||
case domainAsset.StatusDeleted:
|
||||
return "deleted"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// ToFlatProfiles converts domain profiles to flat DTOs.
|
||||
func ToFlatProfiles(profiles []*domainProfile.Profile) []dto.FlatProfileDTO {
|
||||
out := make([]dto.FlatProfileDTO, len(profiles))
|
||||
for i, p := range profiles {
|
||||
out[i] = ToFlatProfile(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ToFlatProfile converts a single profile to flat DTO.
|
||||
func ToFlatProfile(p *domainProfile.Profile) dto.FlatProfileDTO {
|
||||
roleID := ""
|
||||
roleName := ""
|
||||
if p.Hero.Role != nil {
|
||||
roleID = p.Hero.Role.ID.String()
|
||||
if p.Hero.Role.Title != "" {
|
||||
roleName = p.Hero.Role.Title
|
||||
}
|
||||
}
|
||||
|
||||
achievements := make(map[string]dto.AchievementItemDTO)
|
||||
for _, a := range p.About.Achievements {
|
||||
key := strings.ToLower(strings.ReplaceAll(a.Title, " ", ""))
|
||||
if key == "" {
|
||||
key = a.Title
|
||||
}
|
||||
achievements[key] = dto.AchievementItemDTO{Value: a.Value, Enabled: a.Enabled}
|
||||
}
|
||||
if len(achievements) == 0 {
|
||||
achievements = map[string]dto.AchievementItemDTO{
|
||||
"happyClient": {Value: "", Enabled: true},
|
||||
"yearExperience": {Value: "", Enabled: true},
|
||||
"projectCompeleted": {Value: "", Enabled: true},
|
||||
}
|
||||
}
|
||||
|
||||
var socialLinks []dto.SocialLinkDTO
|
||||
for _, sl := range p.Contact.SocialLinks {
|
||||
socialLinks = append(socialLinks, dto.SocialLinkDTO{LinkType: sl.LinkType, Link: sl.Link})
|
||||
}
|
||||
|
||||
displayName := strings.TrimSpace(p.Hero.FirstName + " " + p.Hero.LastName)
|
||||
if displayName == "" {
|
||||
displayName = p.Handle
|
||||
}
|
||||
|
||||
status := "published"
|
||||
if p.PageSetting.VisibilityLevel != "public" {
|
||||
status = "draft"
|
||||
}
|
||||
|
||||
return dto.FlatProfileDTO{
|
||||
ID: p.ID.String(),
|
||||
ProfileHandle: p.Handle,
|
||||
Status: status,
|
||||
BackgroundImage: "",
|
||||
ProfilePicture: p.About.ProfilePicture,
|
||||
FirstName: p.Hero.FirstName,
|
||||
LastName: p.Hero.LastName,
|
||||
DisplayName: displayName,
|
||||
RoleID: roleID,
|
||||
Role: dto.RoleDTO{ID: roleID, Name: roleName},
|
||||
CurrentCompany: p.Hero.Company,
|
||||
ShortDescription: p.Hero.ShortDescription,
|
||||
CTAEnabled: p.Hero.CTAEnabled,
|
||||
CTAAction: "",
|
||||
ResumeLink: p.Hero.ResumeLink,
|
||||
About: p.About.About,
|
||||
ContactEmail: p.Contact.Email,
|
||||
Achievements: achievements,
|
||||
ContactPhone: p.Contact.Phone,
|
||||
Country: "",
|
||||
CustomRoles: "",
|
||||
RoleLevel: p.Hero.Role.Level,
|
||||
SocialLinks: socialLinks,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
HandleUpdatedAt: time.Time{},
|
||||
}
|
||||
}
|
||||
176
internal/application/specialist/service_test.go
Normal file
176
internal/application/specialist/service_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package specialist_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
appMock "base/internal/application/mock"
|
||||
"base/internal/application/specialist"
|
||||
domainAsset "base/internal/domain/asset"
|
||||
domainProfile "base/internal/domain/profile"
|
||||
)
|
||||
|
||||
func TestSpecialistService_Overview(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
logger := zerolog.Nop()
|
||||
userID := uuid.New()
|
||||
profileID := uuid.New()
|
||||
|
||||
t.Run("success - returns overview with profile, assets, tasks", func(t *testing.T) {
|
||||
assetRepo := new(appMock.MockAssetRepository)
|
||||
profileRepo := new(appMock.MockProfileRepository)
|
||||
|
||||
profile := &domainProfile.Profile{
|
||||
ID: profileID,
|
||||
Handle: "specialist-user",
|
||||
UserID: &userID,
|
||||
Hero: domainProfile.Hero{
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
ShortDescription: "ML Engineer",
|
||||
},
|
||||
About: domainProfile.About{
|
||||
ProfilePicture: "avatar.jpg",
|
||||
About: "About me",
|
||||
},
|
||||
Skills: []domainProfile.Skill{
|
||||
{SkillName: "Go", Level: "expert"},
|
||||
},
|
||||
Contact: domainProfile.Contact{
|
||||
Email: "jane@example.com",
|
||||
SocialLinks: []domainProfile.SocialLink{{LinkType: "github", Link: "https://github.com/jane"}},
|
||||
},
|
||||
PageSetting: domainProfile.PageSetting{VisibilityLevel: "public"},
|
||||
}
|
||||
|
||||
asset := &domainAsset.Asset{
|
||||
ID: uuid.New(),
|
||||
ProfileID: profileID,
|
||||
Status: domainAsset.StatusPublished,
|
||||
AssetCategoryID: uuid.New(),
|
||||
Title: "My Project",
|
||||
Description: "A cool project",
|
||||
AssetArtifacts: []domainAsset.Artifact{{Type: "image", DownloadURL: "cover.png", Price: 0}},
|
||||
}
|
||||
|
||||
otherProfile := &domainProfile.Profile{
|
||||
ID: uuid.New(),
|
||||
Handle: "other-user",
|
||||
Hero: domainProfile.Hero{FirstName: "Other", LastName: "User"},
|
||||
PageSetting: domainProfile.PageSetting{VisibilityLevel: "public"},
|
||||
}
|
||||
|
||||
profileRepo.On("FindByUserID", ctx, userID).Return(profile, nil)
|
||||
assetRepo.On("FindByProfileID", ctx, profileID).Return([]*domainAsset.Asset{asset}, nil)
|
||||
profileRepo.On("FindAll", ctx, mock.MatchedBy(func(arg interface{}) bool {
|
||||
f, ok := arg.(domainProfile.Filter)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return f.Page == 1 && f.PageSize == 6 && f.SortedBy == "created_at" && !f.Ascending
|
||||
})).Return([]*domainProfile.Profile{otherProfile}, 26, nil)
|
||||
assetRepo.On("Count", ctx).Return(42, nil)
|
||||
|
||||
svc := specialist.New(specialist.Param{
|
||||
Logger: logger,
|
||||
ProfileRepo: profileRepo,
|
||||
AssetRepo: assetRepo,
|
||||
})
|
||||
|
||||
resp, err := svc.Overview(ctx, userID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
assert.Equal(t, "", resp.Message)
|
||||
assert.Len(t, resp.Data.Assets, 1)
|
||||
assert.Equal(t, "My Project", resp.Data.Assets[0].Title)
|
||||
|
||||
assert.Len(t, resp.Data.RecentlyJoined, 1)
|
||||
assert.Equal(t, "other-user", resp.Data.RecentlyJoined[0].ProfileHandle)
|
||||
|
||||
assert.Equal(t, 42, resp.Data.Analytics.TotalAssets)
|
||||
assert.Equal(t, 26, resp.Data.Analytics.TotalProfiles)
|
||||
|
||||
require.NotNil(t, resp.Data.Profile)
|
||||
assert.Equal(t, "specialist-user", resp.Data.Profile.Handle)
|
||||
assert.Len(t, resp.Data.Skills, 1)
|
||||
assert.Equal(t, "Go", resp.Data.Skills[0].SkillName)
|
||||
|
||||
// All sections complete -> 100% or high completion
|
||||
assert.False(t, resp.Data.Tasks.ProfileAction)
|
||||
assert.False(t, resp.Data.Tasks.AboutAction)
|
||||
assert.False(t, resp.Data.Tasks.PublishAction)
|
||||
assert.False(t, resp.Data.Tasks.WorksAction)
|
||||
assert.False(t, resp.Data.Tasks.SkillsAction)
|
||||
assert.False(t, resp.Data.Tasks.SocialAction)
|
||||
assert.Equal(t, 100, resp.Data.CompletionPercent)
|
||||
|
||||
assetRepo.AssertExpectations(t)
|
||||
profileRepo.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("profile not found returns ErrProfileNotFound", func(t *testing.T) {
|
||||
assetRepo := new(appMock.MockAssetRepository)
|
||||
profileRepo := new(appMock.MockProfileRepository)
|
||||
|
||||
profileRepo.On("FindByUserID", ctx, userID).Return(nil, domainProfile.ErrProfileNotFound)
|
||||
|
||||
svc := specialist.New(specialist.Param{
|
||||
Logger: logger,
|
||||
ProfileRepo: profileRepo,
|
||||
AssetRepo: assetRepo,
|
||||
})
|
||||
|
||||
resp, err := svc.Overview(ctx, userID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, domainProfile.ErrProfileNotFound))
|
||||
assert.Nil(t, resp)
|
||||
|
||||
profileRepo.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("incomplete profile computes tasks and completion percent", func(t *testing.T) {
|
||||
assetRepo := new(appMock.MockAssetRepository)
|
||||
profileRepo := new(appMock.MockProfileRepository)
|
||||
|
||||
profile := &domainProfile.Profile{
|
||||
ID: profileID,
|
||||
Handle: "incomplete",
|
||||
UserID: &userID,
|
||||
Hero: domainProfile.Hero{FirstName: "A"}, // missing LastName, ShortDescription
|
||||
About: domainProfile.About{}, // missing picture, about
|
||||
Skills: []domainProfile.Skill{},
|
||||
PageSetting: domainProfile.PageSetting{VisibilityLevel: "private"},
|
||||
}
|
||||
|
||||
profileRepo.On("FindByUserID", ctx, userID).Return(profile, nil)
|
||||
assetRepo.On("FindByProfileID", ctx, profileID).Return([]*domainAsset.Asset{}, nil)
|
||||
profileRepo.On("FindAll", ctx, mock.Anything).Return([]*domainProfile.Profile{}, 0, nil)
|
||||
assetRepo.On("Count", ctx).Return(0, nil)
|
||||
|
||||
svc := specialist.New(specialist.Param{
|
||||
Logger: logger,
|
||||
ProfileRepo: profileRepo,
|
||||
AssetRepo: assetRepo,
|
||||
})
|
||||
|
||||
resp, err := svc.Overview(ctx, userID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
|
||||
assert.True(t, resp.Data.Tasks.ProfileAction)
|
||||
assert.True(t, resp.Data.Tasks.AboutAction)
|
||||
assert.True(t, resp.Data.Tasks.PublishAction)
|
||||
assert.True(t, resp.Data.Tasks.WorksAction)
|
||||
assert.True(t, resp.Data.Tasks.SkillsAction)
|
||||
assert.True(t, resp.Data.Tasks.SocialAction)
|
||||
assert.Equal(t, 0, resp.Data.CompletionPercent)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user