using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using UniVerse.Application.DTOs.Notifications; using UniVerse.Application.Interfaces; using UniVerse.Domain.Entities; using UniVerse.Infrastructure.Data; using UniVerse.Infrastructure.Services; using Xunit; namespace UniVerse.Api.Tests.Users; public class UserServiceTests { [Fact] public async Task GetStatsAsync_ReturnsLevelProgressThresholds() { await using var db = CreateDbContext(); SeedLevelThresholds(db); db.Users.Add(new User { Id = 1, Email = "student@test.local", Xp = 120 }); await db.SaveChangesAsync(); var service = CreateService(db); var stats = await service.GetStatsAsync(1); Assert.Equal(2, stats.Level); Assert.Equal(100, stats.CurrentLevelXp); Assert.Equal(300, stats.NextLevelXp); } [Fact] public async Task GetStatsAsync_ReturnsNullNextLevelAtMaxConfiguredLevel() { await using var db = CreateDbContext(); SeedLevelThresholds(db); db.Users.Add(new User { Id = 1, Email = "student@test.local", Xp = 350 }); await db.SaveChangesAsync(); var service = CreateService(db); var stats = await service.GetStatsAsync(1); Assert.Equal(3, stats.Level); Assert.Equal(300, stats.CurrentLevelXp); Assert.Null(stats.NextLevelXp); } [Fact] public async Task GetStatsAsync_ReturnsEnrollmentSlotStateAndRules() { await using var db = CreateDbContext(); SeedLevelThresholds(db); var now = DateTime.UtcNow; db.Users.Add(new User { Id = 1, Email = "student@test.local", Xp = 350 }); db.Courses.Add(new Course { Id = 1, Name = "Course" }); db.Lectures.AddRange( Lecture(1, now.AddDays(1)), Lecture(2, now.AddDays(2)), Lecture(3, now.AddDays(-1))); db.LectureEnrollments.AddRange( new LectureEnrollment { LectureId = 1, UserId = 1 }, new LectureEnrollment { LectureId = 2, UserId = 1 }, new LectureEnrollment { LectureId = 3, UserId = 1 }); await db.SaveChangesAsync(); var service = CreateService(db); var stats = await service.GetStatsAsync(1); Assert.Equal(3, stats.ActiveEnrollments); Assert.Equal(5, stats.EnrollmentSlotLimit); Assert.Equal(new[] { 1, 3, 4 }, stats.EnrollmentSlotRules.Select(rule => rule.Level)); Assert.Equal(new[] { 3, 5, 7 }, stats.EnrollmentSlotRules.Select(rule => rule.Slots)); } private static AppDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase($"UserServiceTests_{Guid.NewGuid()}") .Options; return new AppDbContext(options); } private static UserService CreateService(AppDbContext db) { var notifications = Substitute.For(); notifications.CreateUserNotificationAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(callInfo => new UserNotificationDto(1, callInfo.ArgAt(1), callInfo.ArgAt(2), callInfo.ArgAt(3), false, DateTime.UtcNow)); notifications.SendAsync(Arg.Any(), Arg.Any()) .Returns(Task.CompletedTask); var gamification = new GamificationService(db, notifications, NullLogger.Instance); return new UserService(db, gamification); } private static void SeedLevelThresholds(AppDbContext db) { db.LevelThresholds.AddRange( new LevelThreshold { Level = 1, RequiredXp = 0 }, new LevelThreshold { Level = 2, RequiredXp = 100 }, new LevelThreshold { Level = 3, RequiredXp = 300 }); db.SaveChanges(); } private static Lecture Lecture(int id, DateTime startsAt) => new() { Id = id, CourseId = 1, Title = $"Lecture {id}", StartsAt = startsAt, EndsAt = startsAt.AddHours(2), IsOpen = true, MaxEnrollments = 30 }; }