using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using UniVerse.Application.DTOs.Sync; using UniVerse.Application.Interfaces; using UniVerse.Domain.Enums; using UniVerse.Infrastructure.Data; using UniVerse.Infrastructure.Services; using Xunit; namespace UniVerse.Api.Tests.Sync; public class ScheduleSyncServiceTests { [Fact] public async Task SyncScheduleAsync_UsesRoomWorkingCapacityForLectureSeats() { await using var db = CreateDbContext(); var modeus = Substitute.For(); modeus.SearchEventsAsync(Arg.Any()) .Returns(new ModeusEventsResponse { Embedded = new ModeusEventsEmbedded { Events = [ new ModeusEvent { Id = "event-1", Name = "Open lecture", StartsAt = new DateTime(2026, 5, 13, 9, 0, 0, DateTimeKind.Utc), EndsAt = new DateTime(2026, 5, 13, 10, 30, 0, DateTimeKind.Utc) } ], EventRooms = [ new ModeusEventRoom { Links = new ModeusEventRoomLinks { Event = new ModeusHrefLink("/events/event-1"), Room = new ModeusHrefLink("/rooms/room-1") } } ], Rooms = [ new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: 60, WorkingCapacity: 42) ], EventTeams = [ new ModeusEventTeam("event-1", 15) ] } }); var service = new ScheduleSyncService(db, modeus, NullLogger.Instance); var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null)); var lecture = await db.Lectures.SingleAsync(); Assert.Null(result.Error); Assert.Equal(1, result.Created); Assert.Equal(42, lecture.MaxEnrollments); } [Fact] public async Task SyncScheduleAsync_LoadsRoomCapacityWhenEventRoomHasNoCapacity() { await using var db = CreateDbContext(); var modeus = Substitute.For(); modeus.SearchEventsAsync(Arg.Any()) .Returns(new ModeusEventsResponse { Embedded = new ModeusEventsEmbedded { Events = [ new ModeusEvent { Id = "event-1", Name = "Open lecture", StartsAt = new DateTime(2026, 5, 13, 9, 0, 0, DateTimeKind.Utc), EndsAt = new DateTime(2026, 5, 13, 10, 30, 0, DateTimeKind.Utc) } ], EventRooms = [ new ModeusEventRoom { Links = new ModeusEventRoomLinks { Event = new ModeusHrefLink("/events/event-1"), Room = new ModeusHrefLink("/rooms/room-1") } } ], Rooms = [ new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: null, WorkingCapacity: null) ], EventTeams = [ new ModeusEventTeam("event-1", 15) ] } }); modeus.SearchRoomsAsync() .Returns(new ModeusRoomsResponse { Rooms = [ new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: 60, WorkingCapacity: 48) ] }); var service = new ScheduleSyncService(db, modeus, NullLogger.Instance); var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null)); var lecture = await db.Lectures.SingleAsync(); Assert.Null(result.Error); Assert.Equal(1, result.Created); Assert.Equal(48, lecture.MaxEnrollments); } [Fact] public async Task SyncScheduleAsync_UsesModeusEventAttendeeTeacher() { await using var db = CreateDbContext(); var modeus = new FakeModeusApiClient(BuildEventsResponse()); var service = new ScheduleSyncService(db, modeus, NullLogger.Instance); var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null)); Assert.Null(result.Error); Assert.Equal(1, result.Created); var lecture = await db.Lectures.Include(item => item.Teacher).SingleAsync(); Assert.Equal("Иванов Иван Иванович", lecture.Teacher?.DisplayName); Assert.Equal("modeus-b5a5cad8-60c2-4d94-9972-8a0c2e981440@modeus.local", lecture.Teacher?.Email); var teacherProfile = await db.TeacherProfiles.Include(item => item.User).SingleAsync(); Assert.Equal("b5a5cad8-60c2-4d94-9972-8a0c2e981440", teacherProfile.ModeusId); Assert.Equal(teacherProfile.UserId, lecture.TeacherId); var teacherRole = await db.UserRoles.SingleAsync(); Assert.Equal(lecture.TeacherId, teacherRole.UserId); Assert.Equal(UserRole.Teacher, teacherRole.Role); } private static AppDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase($"ScheduleSyncTests_{Guid.NewGuid()}") .Options; return new AppDbContext(options); } private static ModeusEventsResponse BuildEventsResponse() { const string eventId = "48102128-2224-4cb9-ae8f-a91d0b7c512a"; const string courseId = "73aa6226-adbb-4e15-b264-e16fee19fd73"; const string attendeeId = "a894db4e-833f-4f52-a153-fdd7c7d32ca7"; const string personId = "b5a5cad8-60c2-4d94-9972-8a0c2e981440"; return new ModeusEventsResponse { Embedded = new ModeusEventsEmbedded { Events = [ new ModeusEvent { Id = eventId, Name = "Тема 20. Управление ресурсами проекта. Часть 2.", TypeId = "LAB", StartsAt = new DateTime(2026, 4, 14, 5, 0, 0, DateTimeKind.Utc), EndsAt = new DateTime(2026, 4, 14, 6, 35, 0, DateTimeKind.Utc), Links = new ModeusEventLinks { CourseUnitRealization = new ModeusHrefLink($"/{courseId}") } } ], CourseUnitRealizations = [ new ModeusCourseUnitRealization( courseId, "Управление проектами разработки программного обеспечения", "УПРПО") ], EventTeams = [new ModeusEventTeam(eventId, 25)], EventAttendees = [ new ModeusEventAttendee { Id = attendeeId, RoleId = "TEACH", RoleName = "Преподаватель", Links = new ModeusEventAttendeeLinks { Event = new ModeusHrefLink($"/{eventId}"), Person = new ModeusHrefLink($"/{personId}") } } ], Persons = [ new ModeusPerson( personId, "Иванов", "Иван", "Иванович", "Иванов Иван Иванович") ] } }; } private sealed class FakeModeusApiClient(ModeusEventsResponse events) : IModeusApiClient { public Task SearchEventsAsync(SyncScheduleRequest request) => Task.FromResult(events); public Task SearchRoomsAsync() => Task.FromResult(new ModeusRoomsResponse()); public Task> SearchEmployeeAsync(string fullname) => Task.FromResult(new List()); } }