feat: перелопатил синхронизацию преподавателей
Backend CI / build-and-test (push) Failing after 13m11s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 10m12s
Frontend CI / build-and-check (push) Failing after 16m9s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m6s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m58s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 14m58s
Backend CI / build-and-test (push) Failing after 13m11s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 10m12s
Frontend CI / build-and-check (push) Failing after 16m9s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m6s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m58s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 14m58s
This commit is contained in:
@@ -12,6 +12,11 @@ namespace UniVerse.Api.Tests.Sync;
|
||||
|
||||
public class ScheduleSyncServiceTests
|
||||
{
|
||||
private const string EventId = "48102128-2224-4cb9-ae8f-a91d0b7c512a";
|
||||
private const string CourseId = "73aa6226-adbb-4e15-b264-e16fee19fd73";
|
||||
private const string PersonId = "b5a5cad8-60c2-4d94-9972-8a0c2e981440";
|
||||
private const string FullName = "Иванов Иван Иванович";
|
||||
|
||||
[Fact]
|
||||
public async Task SyncScheduleAsync_UsesRoomWorkingCapacityForLectureSeats()
|
||||
{
|
||||
@@ -149,6 +154,138 @@ public class ScheduleSyncServiceTests
|
||||
Assert.Equal(UserRole.Teacher, teacherRole.Role);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncScheduleAsync_SavesResolvedTeacherSubId()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
var modeus = new FakeModeusApiClient(BuildEventsResponse(), subId: "sso-sub-1");
|
||||
var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);
|
||||
|
||||
var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));
|
||||
|
||||
Assert.Null(result.Error);
|
||||
var teacher = await db.Users.Include(user => user.TeacherProfile).SingleAsync();
|
||||
Assert.Equal("sso-sub-1", teacher.MicrosoftId);
|
||||
Assert.Equal($"modeus-{PersonId}@modeus.local", teacher.Email);
|
||||
Assert.Equal(PersonId, teacher.TeacherProfile?.ModeusId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncScheduleAsync_UsesPlaceholderWhenSubLookupFails()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
var modeus = new FakeModeusApiClient(BuildEventsResponse(), throwOnSubLookup: true);
|
||||
var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);
|
||||
|
||||
var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));
|
||||
|
||||
Assert.Null(result.Error);
|
||||
var teacher = await db.Users.Include(user => user.TeacherProfile).SingleAsync();
|
||||
Assert.Null(teacher.MicrosoftId);
|
||||
Assert.Equal($"modeus-{PersonId}@modeus.local", teacher.Email);
|
||||
Assert.Equal(PersonId, teacher.TeacherProfile?.ModeusId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncScheduleAsync_AttachesTeacherProfileToExistingSsoUser()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
db.Users.Add(new UniVerse.Domain.Entities.User
|
||||
{
|
||||
Id = 77,
|
||||
Email = "teacher@sfedu.ru",
|
||||
DisplayName = "Old Name",
|
||||
MicrosoftId = "sso-sub-1",
|
||||
Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 77, Role = UserRole.Student }]
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
var modeus = new FakeModeusApiClient(BuildEventsResponse(), subId: "sso-sub-1");
|
||||
var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);
|
||||
|
||||
var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));
|
||||
|
||||
Assert.Null(result.Error);
|
||||
Assert.Single(await db.Users.ToListAsync());
|
||||
var teacher = await db.Users.Include(user => user.Roles).Include(user => user.TeacherProfile).SingleAsync();
|
||||
Assert.Equal(77, teacher.Id);
|
||||
Assert.Equal("teacher@sfedu.ru", teacher.Email);
|
||||
Assert.Contains(teacher.Roles, role => role.Role == UserRole.Student);
|
||||
Assert.Contains(teacher.Roles, role => role.Role == UserRole.Teacher);
|
||||
Assert.Equal(PersonId, teacher.TeacherProfile?.ModeusId);
|
||||
Assert.True(await db.Lectures.AnyAsync(lecture => lecture.TeacherId == 77));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncScheduleAsync_MergesPlaceholderIntoExistingSsoUserOnRetry()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
var placeholder = new UniVerse.Domain.Entities.User
|
||||
{
|
||||
Id = 10,
|
||||
Email = $"modeus-{PersonId}@modeus.local",
|
||||
DisplayName = FullName,
|
||||
Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 10, Role = UserRole.Teacher }],
|
||||
TeacherProfile = new UniVerse.Domain.Entities.TeacherProfile { UserId = 10, ModeusId = PersonId }
|
||||
};
|
||||
db.Users.Add(placeholder);
|
||||
db.Users.Add(new UniVerse.Domain.Entities.User
|
||||
{
|
||||
Id = 20,
|
||||
Email = "teacher@sfedu.ru",
|
||||
DisplayName = FullName,
|
||||
MicrosoftId = "sso-sub-1",
|
||||
Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 20, Role = UserRole.Student }]
|
||||
});
|
||||
db.Courses.Add(new UniVerse.Domain.Entities.Course { Id = 1, Name = "Course", ExternalId = CourseId, IsSynced = true });
|
||||
db.Lectures.Add(new UniVerse.Domain.Entities.Lecture
|
||||
{
|
||||
Id = 1,
|
||||
CourseId = 1,
|
||||
TeacherId = 10,
|
||||
ExternalId = EventId,
|
||||
Title = "Old",
|
||||
StartsAt = DateTime.UtcNow,
|
||||
EndsAt = DateTime.UtcNow.AddHours(1)
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
var modeus = new FakeModeusApiClient(BuildEventsResponse(), subId: "sso-sub-1");
|
||||
var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);
|
||||
|
||||
var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));
|
||||
|
||||
Assert.Null(result.Error);
|
||||
Assert.Single(await db.Users.ToListAsync());
|
||||
var realUser = await db.Users.Include(user => user.Roles).Include(user => user.TeacherProfile).SingleAsync();
|
||||
Assert.Equal(20, realUser.Id);
|
||||
Assert.Equal(PersonId, realUser.TeacherProfile?.ModeusId);
|
||||
Assert.Contains(realUser.Roles, role => role.Role == UserRole.Teacher);
|
||||
Assert.True(await db.Lectures.AllAsync(lecture => lecture.TeacherId == 20));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SyncScheduleAsync_DoesNotLookupSubWhenTeacherAlreadyHasMicrosoftId()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
db.Users.Add(new UniVerse.Domain.Entities.User
|
||||
{
|
||||
Id = 10,
|
||||
Email = "teacher@sfedu.ru",
|
||||
DisplayName = FullName,
|
||||
MicrosoftId = "sso-sub-1",
|
||||
Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 10, Role = UserRole.Teacher }],
|
||||
TeacherProfile = new UniVerse.Domain.Entities.TeacherProfile { UserId = 10, ModeusId = PersonId }
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
var modeus = Substitute.For<IModeusApiClient>();
|
||||
modeus.SearchEventsAsync(Arg.Any<SyncScheduleRequest>()).Returns(BuildEventsResponse());
|
||||
var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);
|
||||
|
||||
var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));
|
||||
|
||||
Assert.Null(result.Error);
|
||||
await modeus.DidNotReceive().GetSubIdByFullNameAsync(Arg.Any<string>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private static AppDbContext CreateDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
@@ -160,10 +297,7 @@ public class ScheduleSyncServiceTests
|
||||
|
||||
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
|
||||
{
|
||||
@@ -173,25 +307,25 @@ public class ScheduleSyncServiceTests
|
||||
[
|
||||
new ModeusEvent
|
||||
{
|
||||
Id = eventId,
|
||||
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}")
|
||||
CourseUnitRealization = new ModeusHrefLink($"/{CourseId}")
|
||||
}
|
||||
}
|
||||
],
|
||||
CourseUnitRealizations =
|
||||
[
|
||||
new ModeusCourseUnitRealization(
|
||||
courseId,
|
||||
CourseId,
|
||||
"Управление проектами разработки программного обеспечения",
|
||||
"УПРПО")
|
||||
],
|
||||
EventTeams = [new ModeusEventTeam(eventId, 25)],
|
||||
EventTeams = [new ModeusEventTeam(EventId, 25)],
|
||||
EventAttendees =
|
||||
[
|
||||
new ModeusEventAttendee
|
||||
@@ -201,30 +335,41 @@ public class ScheduleSyncServiceTests
|
||||
RoleName = "Преподаватель",
|
||||
Links = new ModeusEventAttendeeLinks
|
||||
{
|
||||
Event = new ModeusHrefLink($"/{eventId}"),
|
||||
Person = new ModeusHrefLink($"/{personId}")
|
||||
Event = new ModeusHrefLink($"/{EventId}"),
|
||||
Person = new ModeusHrefLink($"/{PersonId}")
|
||||
}
|
||||
}
|
||||
],
|
||||
Persons =
|
||||
[
|
||||
new ModeusPerson(
|
||||
personId,
|
||||
PersonId,
|
||||
"Иванов",
|
||||
"Иван",
|
||||
"Иванович",
|
||||
"Иванов Иван Иванович")
|
||||
FullName)
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class FakeModeusApiClient(ModeusEventsResponse events) : IModeusApiClient
|
||||
private sealed class FakeModeusApiClient(
|
||||
ModeusEventsResponse events,
|
||||
string? subId = null,
|
||||
bool throwOnSubLookup = false) : IModeusApiClient
|
||||
{
|
||||
public Task<ModeusEventsResponse> SearchEventsAsync(SyncScheduleRequest request) => Task.FromResult(events);
|
||||
|
||||
public Task<ModeusRoomsResponse> SearchRoomsAsync() => Task.FromResult(new ModeusRoomsResponse());
|
||||
|
||||
public Task<List<ModeusEmployee>> SearchEmployeeAsync(string fullname) => Task.FromResult(new List<ModeusEmployee>());
|
||||
|
||||
public Task<string?> GetSubIdByFullNameAsync(string fullname, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (throwOnSubLookup)
|
||||
throw new HttpRequestException("lookup failed");
|
||||
|
||||
return Task.FromResult(subId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user