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