package service import ( "context" "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/user-management-system/internal/domain" ) // Mock implementations type mockStatsUserRepo struct { mock.Mock } func (m *mockStatsUserRepo) List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error) { args := m.Called(ctx, offset, limit) return args.Get(0).([]*domain.User), args.Get(1).(int64), args.Error(2) } func (m *mockStatsUserRepo) ListByStatus(ctx context.Context, status domain.UserStatus, offset, limit int) ([]*domain.User, int64, error) { args := m.Called(ctx, status, offset, limit) return args.Get(0).([]*domain.User), args.Get(1).(int64), args.Error(2) } func (m *mockStatsUserRepo) ListCreatedAfter(ctx context.Context, since time.Time, offset, limit int) ([]*domain.User, int64, error) { args := m.Called(ctx, since, offset, limit) return args.Get(0).([]*domain.User), args.Get(1).(int64), args.Error(2) } type mockStatsLoginLogRepo struct { mock.Mock } func (m *mockStatsLoginLogRepo) CountByResultSince(ctx context.Context, success bool, since time.Time) (int64, error) { args := m.Called(ctx, success, since) return args.Get(0).(int64), args.Error(1) } func setupStatsServiceTest() (*StatsService, *mockStatsUserRepo, *mockStatsLoginLogRepo) { userRepo := &mockStatsUserRepo{} loginLogRepo := &mockStatsLoginLogRepo{} svc := NewStatsService(userRepo, loginLogRepo) return svc, userRepo, loginLogRepo } // ============================================================================= // GetUserStats Tests // ============================================================================= func TestStatsService_GetUserStats_Success(t *testing.T) { svc, userRepo, _ := setupStatsServiceTest() // Setup expectations userRepo.On("List", mock.Anything, 0, 1).Return([]*domain.User{}, int64(100), nil) userRepo.On("ListByStatus", mock.Anything, domain.UserStatusActive, 0, 1).Return([]*domain.User{}, int64(80), nil) userRepo.On("ListByStatus", mock.Anything, domain.UserStatusInactive, 0, 1).Return([]*domain.User{}, int64(10), nil) userRepo.On("ListByStatus", mock.Anything, domain.UserStatusLocked, 0, 1).Return([]*domain.User{}, int64(5), nil) userRepo.On("ListByStatus", mock.Anything, domain.UserStatusDisabled, 0, 1).Return([]*domain.User{}, int64(5), nil) userRepo.On("ListCreatedAfter", mock.Anything, mock.Anything, 0, 0).Return([]*domain.User{}, int64(5), nil).Times(3) stats, err := svc.GetUserStats(context.Background()) assert.NoError(t, err) assert.Equal(t, int64(100), stats.TotalUsers) assert.Equal(t, int64(80), stats.ActiveUsers) assert.Equal(t, int64(10), stats.InactiveUsers) assert.Equal(t, int64(5), stats.LockedUsers) assert.Equal(t, int64(5), stats.DisabledUsers) userRepo.AssertExpectations(t) } func TestStatsService_GetUserStats_ListError(t *testing.T) { svc, userRepo, _ := setupStatsServiceTest() userRepo.On("List", mock.Anything, 0, 1).Return([]*domain.User{}, int64(0), errors.New("db error")) stats, err := svc.GetUserStats(context.Background()) assert.Error(t, err) assert.Nil(t, stats) userRepo.AssertExpectations(t) } // ============================================================================= // GetDashboardStats Tests // ============================================================================= func TestStatsService_GetDashboardStats_Success(t *testing.T) { svc, userRepo, loginLogRepo := setupStatsServiceTest() // User stats expectations userRepo.On("List", mock.Anything, 0, 1).Return([]*domain.User{}, int64(100), nil) userRepo.On("ListByStatus", mock.Anything, mock.Anything, 0, 1).Return([]*domain.User{}, int64(0), nil).Times(4) userRepo.On("ListCreatedAfter", mock.Anything, mock.Anything, 0, 0).Return([]*domain.User{}, int64(0), nil).Times(3) // Login stats expectations loginLogRepo.On("CountByResultSince", mock.Anything, true, mock.Anything).Return(int64(50), nil).Twice() loginLogRepo.On("CountByResultSince", mock.Anything, false, mock.Anything).Return(int64(10), nil).Once() stats, err := svc.GetDashboardStats(context.Background()) assert.NoError(t, err) assert.NotNil(t, stats) assert.Equal(t, int64(100), stats.Users.TotalUsers) assert.Equal(t, int64(50), stats.Logins.LoginsTodaySuccess) assert.Equal(t, int64(10), stats.Logins.LoginsTodayFailed) userRepo.AssertExpectations(t) loginLogRepo.AssertExpectations(t) } func TestStatsService_GetDashboardStats_UserStatsError(t *testing.T) { svc, userRepo, _ := setupStatsServiceTest() userRepo.On("List", mock.Anything, 0, 1).Return([]*domain.User{}, int64(0), errors.New("db error")) stats, err := svc.GetDashboardStats(context.Background()) assert.Error(t, err) assert.Nil(t, stats) userRepo.AssertExpectations(t) } // ============================================================================= // daysAgo Tests // ============================================================================= func TestDaysAgo_Today(t *testing.T) { result := daysAgo(0) now := time.Now() // Should be today at midnight assert.Equal(t, now.Year(), result.Year()) assert.Equal(t, now.Month(), result.Month()) assert.Equal(t, now.Day(), result.Day()) assert.Equal(t, 0, result.Hour()) assert.Equal(t, 0, result.Minute()) assert.Equal(t, 0, result.Second()) } func TestDaysAgo_Yesterday(t *testing.T) { result := daysAgo(1) expected := time.Now().AddDate(0, 0, -1) assert.Equal(t, expected.Year(), result.Year()) assert.Equal(t, expected.Month(), result.Month()) assert.Equal(t, expected.Day(), result.Day()) } func TestDaysAgo_OneWeek(t *testing.T) { result := daysAgo(7) expected := time.Now().AddDate(0, 0, -7) assert.Equal(t, expected.Year(), result.Year()) assert.Equal(t, expected.Month(), result.Month()) assert.Equal(t, expected.Day(), result.Day()) } func TestDaysAgo_OneMonth(t *testing.T) { result := daysAgo(30) expected := time.Now().AddDate(0, 0, -30) assert.Equal(t, expected.Year(), result.Year()) assert.Equal(t, expected.Month(), result.Month()) assert.Equal(t, expected.Day(), result.Day()) }