b0a4a6d259
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 26s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 19s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 8s
Реализовал хранение, получение и отметку прочитанными пользовательских уведомлений. Обновил фронтенд для отображения и управления уведомлениями в профиле студента.
151 lines
5.9 KiB
C#
151 lines
5.9 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using UniVerse.Application.DTOs.Notifications;
|
|
using UniVerse.Application.Interfaces;
|
|
using UniVerse.Domain.Entities;
|
|
using UniVerse.Domain.Enums;
|
|
using UniVerse.Infrastructure.Data;
|
|
using UniVerse.Infrastructure.Services;
|
|
using Xunit;
|
|
|
|
namespace UniVerse.Api.Tests.Gamification;
|
|
|
|
public class GamificationServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task CheckAndAwardAchievementsAsync_AwardsModernConditionsOnce()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var service = CreateService(db);
|
|
|
|
db.Users.Add(new User
|
|
{
|
|
Id = 1,
|
|
Email = "student@test.local",
|
|
DisplayName = "Student",
|
|
AvatarUrl = "avatar.png",
|
|
Xp = 100,
|
|
Coins = 510
|
|
});
|
|
db.Courses.Add(new Course { Id = 1, Name = "Course" });
|
|
db.Lectures.Add(new Lecture
|
|
{
|
|
Id = 1,
|
|
CourseId = 1,
|
|
Title = "Future lecture",
|
|
StartsAt = DateTime.UtcNow.AddDays(1),
|
|
EndsAt = DateTime.UtcNow.AddDays(1).AddHours(2),
|
|
IsOpen = true
|
|
});
|
|
db.LectureEnrollments.Add(new LectureEnrollment { UserId = 1, LectureId = 1 });
|
|
db.Reviews.AddRange(
|
|
new Review { Id = 1, UserId = 1, LectureId = 1, Rating = ReviewRating.Like },
|
|
new Review { Id = 2, UserId = 1, LectureId = 1, Rating = ReviewRating.Neutral },
|
|
new Review { Id = 3, UserId = 1, LectureId = 1, Rating = ReviewRating.Dislike });
|
|
db.CoinTransactions.Add(new CoinTransaction
|
|
{
|
|
UserId = 1,
|
|
Amount = 510,
|
|
Type = CoinTransactionType.AdminAdjustment,
|
|
Description = "Initial coins"
|
|
});
|
|
db.Achievements.AddRange(
|
|
Achievement(1001, "First activity", "first_activity:1", 10),
|
|
Achievement(1002, "Reviews", "reviews_written:3", 20),
|
|
Achievement(1003, "Active registrations", "active_registrations:1", 30),
|
|
Achievement(1004, "Coins earned", "coins_earned:500", 40),
|
|
Achievement(1005, "Level reached", "level_reached:2", 50),
|
|
Achievement(1006, "Profile completed", "profile_completed:1", 60),
|
|
Achievement(1007, "Old condition", "reviews_1", 100));
|
|
await db.SaveChangesAsync();
|
|
|
|
await service.CheckAndAwardAchievementsAsync(1);
|
|
await service.CheckAndAwardAchievementsAsync(1);
|
|
|
|
var user = await db.Users.FindAsync(1);
|
|
Assert.NotNull(user);
|
|
Assert.Equal(720, user!.Coins);
|
|
Assert.Equal(310, user.Xp);
|
|
Assert.Equal(6, await db.UserAchievements.CountAsync(ua => ua.UserId == 1));
|
|
Assert.False(await db.UserAchievements.AnyAsync(ua => ua.AchievementId == 1007));
|
|
Assert.Equal(6, await db.CoinTransactions.CountAsync(ct =>
|
|
ct.UserId == 1 && ct.Type == CoinTransactionType.AchievementReward));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CheckAndAwardAchievementsAsync_CountsConsecutiveIsoWeeksAcrossYears()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var service = CreateService(db);
|
|
|
|
db.Users.Add(new User { Id = 1, Email = "student@test.local" });
|
|
db.Courses.Add(new Course { Id = 1, Name = "Course" });
|
|
db.Lectures.AddRange(
|
|
Lecture(1, new DateTime(2025, 12, 29, 10, 0, 0, DateTimeKind.Utc)),
|
|
Lecture(2, new DateTime(2026, 1, 5, 10, 0, 0, DateTimeKind.Utc)),
|
|
Lecture(3, new DateTime(2026, 1, 12, 10, 0, 0, DateTimeKind.Utc)));
|
|
db.LectureEnrollments.AddRange(
|
|
new LectureEnrollment { UserId = 1, LectureId = 1, Attended = true },
|
|
new LectureEnrollment { UserId = 1, LectureId = 2, Attended = true },
|
|
new LectureEnrollment { UserId = 1, LectureId = 3, Attended = true });
|
|
db.Achievements.Add(Achievement(1001, "Streak", "attendance_streak_weeks:3", 10));
|
|
await db.SaveChangesAsync();
|
|
|
|
await service.CheckAndAwardAchievementsAsync(1);
|
|
|
|
Assert.True(await db.UserAchievements.AnyAsync(ua => ua.UserId == 1 && ua.AchievementId == 1001));
|
|
}
|
|
|
|
private static AppDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
|
.UseInMemoryDatabase($"GamificationTests_{Guid.NewGuid()}")
|
|
.Options;
|
|
return new AppDbContext(options);
|
|
}
|
|
|
|
private static GamificationService CreateService(AppDbContext db)
|
|
{
|
|
var notifications = Substitute.For<INotificationService>();
|
|
notifications.CreateUserNotificationAsync(
|
|
Arg.Any<int>(),
|
|
Arg.Any<string>(),
|
|
Arg.Any<string>(),
|
|
Arg.Any<string>(),
|
|
Arg.Any<CancellationToken>())
|
|
.Returns(callInfo => new UserNotificationDto(1, callInfo.ArgAt<string>(1), callInfo.ArgAt<string>(2), callInfo.ArgAt<string>(3), false, DateTime.UtcNow));
|
|
notifications.SendAsync(Arg.Any<NotificationMessage>(), Arg.Any<CancellationToken>())
|
|
.Returns(Task.CompletedTask);
|
|
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Gamification:XpThresholds:0"] = "0",
|
|
["Gamification:XpThresholds:1"] = "100",
|
|
["Gamification:XpThresholds:2"] = "300"
|
|
})
|
|
.Build();
|
|
|
|
return new GamificationService(db, configuration, notifications, NullLogger<GamificationService>.Instance);
|
|
}
|
|
|
|
private static Achievement Achievement(int id, string name, string condition, int coinReward) => new()
|
|
{
|
|
Id = id,
|
|
Name = name,
|
|
Condition = condition,
|
|
CoinReward = coinReward
|
|
};
|
|
|
|
private static Lecture Lecture(int id, DateTime startsAt) => new()
|
|
{
|
|
Id = id,
|
|
CourseId = 1,
|
|
Title = $"Lecture {id}",
|
|
StartsAt = startsAt,
|
|
EndsAt = startsAt.AddHours(2)
|
|
};
|
|
}
|