using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using UniVerse.Application.DTOs.Notifications; using UniVerse.Application.Interfaces; using UniVerse.Domain.Entities; using UniVerse.Domain.Enums; using UniVerse.Domain.Exceptions; using UniVerse.Infrastructure.Data; using UniVerse.Infrastructure.Services; using Xunit; namespace UniVerse.Api.Tests.Auth; public class AuthServiceTests { [Fact] public async Task RefreshTokenAsync_InactiveUser_RevokesTokenAndThrowsForbidden() { await using var db = CreateDbContext(); db.Users.Add(new User { Id = 1, Email = "blocked@test.local", IsActive = false, Roles = [new UserRoleAssignment { UserId = 1, Role = UserRole.Student }] }); db.RefreshTokens.Add(new RefreshToken { Id = 1, UserId = 1, Token = "refresh-token", ExpiresAt = DateTime.UtcNow.AddDays(1), CreatedAt = DateTime.UtcNow }); await db.SaveChangesAsync(); var service = CreateService(db); await Assert.ThrowsAsync(() => service.RefreshTokenAsync("refresh-token")); var token = await db.RefreshTokens.SingleAsync(t => t.Token == "refresh-token"); Assert.NotNull(token.RevokedAt); } [Fact] public async Task GetCurrentUserAsync_InactiveUser_ThrowsForbidden() { await using var db = CreateDbContext(); db.Users.Add(new User { Id = 1, Email = "blocked@test.local", IsActive = false, Roles = [new UserRoleAssignment { UserId = 1, Role = UserRole.Student }] }); await db.SaveChangesAsync(); var service = CreateService(db); await Assert.ThrowsAsync(() => service.GetCurrentUserAsync(1)); } [Fact] public async Task LoginWithMicrosoftAsync_LinksScheduleTeacherBySubId() { await using var db = CreateDbContext(); db.Users.Add(new User { Id = 10, Email = "modeus-person-1@modeus.local", DisplayName = "Иванов Иван Иванович", MicrosoftId = "sso-sub-1", IsActive = true, Roles = [new UserRoleAssignment { UserId = 10, Role = UserRole.Teacher }], TeacherProfile = new TeacherProfile { UserId = 10, ModeusId = "person-1" } }); await db.SaveChangesAsync(); var microsoftAuth = Substitute.For(); microsoftAuth.ExchangeAuthorizationCodeAsync("code", "http://localhost/callback", Arg.Any()) .Returns(new MicrosoftTokenResult(BuildIdToken("sso-sub-1", "teacher@sfedu.ru", "Иванов Иван Иванович"))); var service = CreateService(db, microsoftAuth); var result = await service.LoginWithMicrosoftAsync("code", "http://localhost/callback"); Assert.Equal(10, result.Response.User.Id); Assert.Equal("teacher@sfedu.ru", result.Response.User.Email); Assert.Contains(UserRole.Teacher, result.Response.User.Roles); Assert.Single(await db.Users.ToListAsync()); var user = await db.Users.Include(u => u.TeacherProfile).SingleAsync(); Assert.Equal("sso-sub-1", user.MicrosoftId); Assert.Equal("person-1", user.TeacherProfile?.ModeusId); } private static AppDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase($"AuthServiceTests_{Guid.NewGuid()}") .Options; return new AppDbContext(options); } private static AuthService CreateService(AppDbContext db, IMicrosoftAuthClient? microsoftAuth = null) { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Jwt:Secret"] = "test-secret-test-secret-test-secret-test-secret", ["Jwt:Issuer"] = "UniVerse.Tests", ["Jwt:Audience"] = "UniVerse.Tests", ["Jwt:AccessTokenExpirationMinutes"] = "15", ["Jwt:RefreshTokenExpirationDays"] = "30" }) .Build(); var gamification = Substitute.For(); gamification.CalculateLevelAsync(Arg.Any()).Returns(1); var notifications = Substitute.For(); notifications.SendAsync(Arg.Any(), Arg.Any()) .Returns(Task.CompletedTask); microsoftAuth ??= Substitute.For(); return new AuthService(db, config, microsoftAuth, gamification, notifications, NullLogger.Instance); } private static string BuildIdToken(string sub, string email, string name) { var token = new JwtSecurityToken(claims: [ new Claim(JwtRegisteredClaimNames.Sub, sub), new Claim("preferred_username", email), new Claim("name", name) ]); return new JwtSecurityTokenHandler().WriteToken(token); } }