Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exam mode: Allow instructors to extend exam time #7119

Merged
merged 48 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c6f8c66
add a new REST call to enable time extension of the whole exam
Aug 30, 2023
e309d48
use correct request test method
Aug 30, 2023
3edca95
Merge branch 'develop' into feature/exam-mode/time-extension
Aug 30, 2023
ca8a348
Merge branch 'develop' into feature/exam-mode/time-extension
Aug 31, 2023
bb0aea1
Client implementation
aplr Sep 8, 2023
4d413b3
Update exam state, switch to relative value input, fix editor warning…
aplr Sep 9, 2023
ace83f6
Disable changing working time 5 minutes before exam end
aplr Sep 9, 2023
9652eb7
Add rescheduling of repository lock operations
aplr Sep 9, 2023
e41e989
Update src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java
aplr Sep 9, 2023
934ebc0
Merge commit 'f6e9a37cfc01ebccb4f439ba2cd90e77ddbedd84' into feature/…
aplr Sep 9, 2023
9871da5
Fix warning
aplr Sep 9, 2023
16648df
Self-review changes
aplr Sep 9, 2023
52f078e
Send working time extension instance notification once
aplr Sep 9, 2023
4504450
Teamscale fixes
aplr Sep 9, 2023
f7d357b
Rescheduling for whole exam, grouped by participation due date
aplr Sep 9, 2023
885c6b7
Merge commit '12057ed62683db3897691d395f5f93baf583a05a' into feature/…
aplr Sep 10, 2023
cc6df6b
Revert "Fix warning"
aplr Sep 10, 2023
b566e62
Add note to shadowed methods
aplr Sep 10, 2023
ed6a721
Update src/main/webapp/app/exam/shared/working-time-update/working-ti…
aplr Sep 10, 2023
09bda7a
Update debug log
aplr Sep 10, 2023
e2adaa8
Fix test declaration import
aplr Sep 10, 2023
56d42fd
Fix client tests
aplr Sep 12, 2023
f298f2d
PR feedback
aplr Sep 13, 2023
0c8469d
PR feedback
aplr Sep 13, 2023
6393263
Fix button naming
aplr Sep 13, 2023
f1437e5
PR feedback
aplr Sep 13, 2023
442352b
PR feedback
aplr Sep 13, 2023
c92220a
Remove comment
aplr Sep 13, 2023
9b8b92e
Use event emitter instead of callback
aplr Sep 13, 2023
c0fa9c6
Fix client tests
aplr Sep 13, 2023
4ae05d6
Unsubscribe on destroy
aplr Sep 13, 2023
438cd9a
Merge commit '4332d9e11655ed417350356ef0a3acf2c509df4a' into feature/…
aplr Sep 13, 2023
6b15787
Fix prettier
aplr Sep 14, 2023
26bed1f
improve logic for scheduling modeling exercises when updating (indivi…
Sep 14, 2023
3afaca9
Merge branch 'develop' into feature/exam-mode/time-extension
Strohgelaender Sep 14, 2023
2655403
Fix PR feedback
aplr Sep 14, 2023
5728c11
Fix PR feedback
aplr Sep 14, 2023
44e2402
Fix PR feedback
aplr Sep 14, 2023
67bbdab
Fetch exercise groups
aplr Sep 14, 2023
8f47b7e
Next checkout timeout
aplr Sep 14, 2023
9459fe1
Merge branch 'develop' of https://github.com/ls1intum/Artemis into fe…
Strohgelaender Sep 15, 2023
c3a4f28
fix an issue with individual end dates
Strohgelaender Sep 15, 2023
bb9f3a0
fix an issue with time extensions
Strohgelaender Sep 15, 2023
b4d537c
Code cleanup
aplr Sep 16, 2023
73adf8e
Update note
aplr Sep 16, 2023
df245cf
update note
Strohgelaender Sep 16, 2023
46f142c
Merge branch 'develop' into feature/exam-mode/time-extension
Strohgelaender Sep 16, 2023
a69f318
Display absolute working time
aplr Sep 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ public final class Constants {
*/
public static final int GROUP_CONVERSATION_HUMAN_READABLE_NAME_LIMIT = 100;

public static final String WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC = "/topic/studentExams/%s/working-time-change-during-conduction";

private Constants() {
}
}
9 changes: 9 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/domain/exam/Exam.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.in.www1.artemis.domain.exam;

import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
Expand Down Expand Up @@ -192,6 +193,14 @@ public void setEndDate(@NotNull ZonedDateTime endDate) {
this.endDate = endDate;
}

/**
* @return the duration of the exam in seconds
*/
@JsonIgnore
public int getDuration() {
return Math.toIntExact(Duration.between(getStartDate(), getEndDate()).toSeconds());
}

public ZonedDateTime getPublishResultsDate() {
return publishResultsDate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ public class StudentExamService {

private static final String EXAM_EXERCISE_START_STATUS_TOPIC = "/topic/exams/%s/exercise-start-status";

private static final String WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC = "/topic/studentExams/%s/working-time-change-during-conduction";

private final Logger log = LoggerFactory.getLogger(StudentExamService.class);

private final ParticipationService participationService;
Expand Down Expand Up @@ -805,8 +803,4 @@ private StudentExam generateIndividualStudentExam(Exam exam, User student) {
userHashSet.add(student);
return studentExamRepository.createRandomStudentExams(exam, userHashSet).get(0);
}

public void notifyStudentAboutWorkingTimeChangeDuringConduction(StudentExam studentExam) {
websocketMessagingService.sendMessage(WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC.formatted(studentExam.getId()), studentExam.getWorkingTime());
}
}
75 changes: 65 additions & 10 deletions src/main/java/de/tum/in/www1/artemis/web/rest/ExamResource.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.in.www1.artemis.web.rest;

import static de.tum.in.www1.artemis.config.Constants.WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC;
import static de.tum.in.www1.artemis.service.util.TimeLogUtil.formatDurationFrom;
import static java.time.ZonedDateTime.now;

Expand All @@ -9,12 +10,12 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

import javax.validation.constraints.NotNull;
import javax.ws.rs.BadRequestException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -42,10 +43,7 @@
import de.tum.in.www1.artemis.repository.metis.conversation.ChannelRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.*;
import de.tum.in.www1.artemis.service.AssessmentDashboardService;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ProfileService;
import de.tum.in.www1.artemis.service.SubmissionService;
import de.tum.in.www1.artemis.service.*;
Strohgelaender marked this conversation as resolved.
Show resolved Hide resolved
import de.tum.in.www1.artemis.service.dto.StudentDTO;
import de.tum.in.www1.artemis.service.exam.*;
import de.tum.in.www1.artemis.service.feature.Feature;
Expand Down Expand Up @@ -113,12 +111,14 @@ public class ExamResource {

private final ChannelService channelService;

private final WebsocketMessagingService websocketMessagingService;

public ExamResource(ProfileService profileService, UserRepository userRepository, CourseRepository courseRepository, ExamService examService,
ExamDeletionService examDeletionService, ExamAccessService examAccessService, InstanceMessageSendService instanceMessageSendService, ExamRepository examRepository,
SubmissionService submissionService, AuthorizationCheckService authCheckService, ExamDateService examDateService,
TutorParticipationRepository tutorParticipationRepository, AssessmentDashboardService assessmentDashboardService, ExamRegistrationService examRegistrationService,
StudentExamRepository studentExamRepository, ExamImportService examImportService, CustomAuditEventRepository auditEventRepository, ChannelService channelService,
ChannelRepository channelRepository) {
ChannelRepository channelRepository, WebsocketMessagingService websocketMessagingService) {
this.profileService = profileService;
this.userRepository = userRepository;
this.courseRepository = courseRepository;
Expand All @@ -138,6 +138,7 @@ public ExamResource(ProfileService profileService, UserRepository userRepository
this.auditEventRepository = auditEventRepository;
this.channelService = channelService;
this.channelRepository = channelRepository;
this.websocketMessagingService = websocketMessagingService;
}

/**
Expand Down Expand Up @@ -235,6 +236,60 @@ public ResponseEntity<Exam> updateExam(@PathVariable Long courseId, @RequestBody
return ResponseEntity.ok(savedExam);
}

/**
* PATCH /courses/{courseId}/exams/{examId}/student-exams/{studentExamId}/working-time : Update the working time of the student exam
*
* @param courseId the course to which the student exams belong to
* @param examId the exam to which the student exams belong to
* @param workingTimeChange the working time change in seconds (can be positive or negative, but must not be 0)
* @return the ResponseEntity with status 200 (OK) and with the updated student exam as body
*/
aplr marked this conversation as resolved.
Show resolved Hide resolved
@PatchMapping("/courses/{courseId}/exams/{examId}/student-exams/working-time")
@EnforceAtLeastInstructor
public ResponseEntity<Exam> updateExamWorkingTime(@PathVariable Long courseId, @PathVariable Long examId, @RequestBody Integer workingTimeChange) {
log.debug("REST request to update the working time of exam : {}", examId);
aplr marked this conversation as resolved.
Show resolved Hide resolved

examAccessService.checkCourseAndExamAccessForInstructorElseThrow(courseId, examId);

if (workingTimeChange == 0) {
throw new BadRequestException();
}
Exam exam = examService.findByIdWithExerciseGroupsAndExercisesElseThrow(examId);
aplr marked this conversation as resolved.
Show resolved Hide resolved
var originalExamDuration = exam.getDuration();
// update the end date of the exam
exam.setEndDate(exam.getEndDate().plusSeconds(workingTimeChange));
examRepository.save(exam);

// re-calculate the working times of all student exams
var studentExams = studentExamRepository.findByExamId(examId);
for (var studentExam : studentExams) {
Integer originalStudentWorkingTime = studentExam.getWorkingTime();
int originalTimeExtension = originalStudentWorkingTime - originalExamDuration;
// NOTE: take the original working time extensions into account
if (originalTimeExtension == 0) {
studentExam.setWorkingTime(originalStudentWorkingTime + workingTimeChange);
}
else {
double relativeTimeExtension = (double) originalTimeExtension / (double) originalExamDuration;
int adjustedTimeExtension = Math.toIntExact(Math.round(relativeTimeExtension * workingTimeChange));
studentExam.setWorkingTime(originalStudentWorkingTime + adjustedTimeExtension);
}
var savedStudentExam = studentExamRepository.save(studentExam);
if (ZonedDateTime.now().isAfter(exam.getVisibleDate())) {
// TODO: this is probably not very efficient, instead we should re-calculate once for all student exams
instanceMessageSendService.sendExamWorkingTimeChangeDuringConduction(studentExam.getId());
websocketMessagingService.sendMessage(WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC.formatted(savedStudentExam.getId()), savedStudentExam.getWorkingTime());
}
}

if (ZonedDateTime.now().isBefore(examDateService.getLatestIndividualExamEndDate(exam)) && exam.getStartDate() != null
krusche marked this conversation as resolved.
Show resolved Hide resolved
&& ZonedDateTime.now().isBefore(exam.getStartDate().plusSeconds(workingTimeChange))) {
examService.scheduleModelingExercises(exam);
krusche marked this conversation as resolved.
Show resolved Hide resolved
}

return ResponseEntity.ok(exam);
}

/**
* POST /courses/{courseId}/exam-import : Imports a new exam with exercises.
*
Expand Down Expand Up @@ -333,19 +388,19 @@ else if (!exam.getVisibleDate().isBefore(exam.getStartDate()) || !exam.getStartD
* @param exam the exam to be checked
*/
private void checkExamForWorkingTimeConflictsElseThrow(Exam exam) {
int differenceStartEndDate = Math.toIntExact(Duration.between(exam.getStartDate(), exam.getEndDate()).toSeconds());
var examDuration = exam.getDuration();

if (exam.isTestExam()) {
if (exam.getWorkingTime() > differenceStartEndDate || exam.getWorkingTime() < 1) {
if (exam.getWorkingTime() > examDuration || exam.getWorkingTime() < 1) {
throw new BadRequestAlertException("For TestExams, the working time must be at least 1 and at most the duration of the working window.", ENTITY_NAME, "examTimes");
}
}
else if (exam.getWorkingTime() != differenceStartEndDate) {
else if (exam.getWorkingTime() != examDuration) {
/*
* Set the working time to the time difference for real exams, if not done by the client. This can be an issue if the working time calculation in the client is not
* performed (e.g. for Cypress-2E2-Tests). However, since the working time currently depends on the start- and end-date, we can do a server-side assignment
*/
exam.setWorkingTime(differenceStartEndDate);
exam.setWorkingTime(examDuration);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.tum.in.www1.artemis.web.rest;

import static de.tum.in.www1.artemis.config.Constants.EXAM_START_WAIT_TIME_MINUTES;
import static de.tum.in.www1.artemis.config.Constants.WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC;
import static de.tum.in.www1.artemis.service.util.TimeLogUtil.formatDurationFrom;

import java.time.ZonedDateTime;
Expand Down Expand Up @@ -85,6 +86,8 @@ public class StudentExamResource {

private final WebsocketMessagingService messagingService;

private final WebsocketMessagingService websocketMessagingService;

@Value("${info.student-exam-store-session-data:#{true}}")
private boolean storeSessionDataInStudentExamSession;

Expand All @@ -98,7 +101,7 @@ public StudentExamResource(ExamAccessService examAccessService, ExamDeletionServ
StudentExamRepository studentExamRepository, ExamDateService examDateService, ExamSessionService examSessionService,
StudentParticipationRepository studentParticipationRepository, ExamRepository examRepository, SubmittedAnswerRepository submittedAnswerRepository,
AuthorizationCheckService authorizationCheckService, ExamService examService, InstanceMessageSendService instanceMessageSendService,
WebsocketMessagingService messagingService) {
WebsocketMessagingService messagingService, WebsocketMessagingService websocketMessagingService) {
this.examAccessService = examAccessService;
this.examDeletionService = examDeletionService;
this.studentExamService = studentExamService;
Expand All @@ -115,6 +118,7 @@ public StudentExamResource(ExamAccessService examAccessService, ExamDeletionServ
this.examService = examService;
this.instanceMessageSendService = instanceMessageSendService;
this.messagingService = messagingService;
this.websocketMessagingService = websocketMessagingService;
}

/**
Expand Down Expand Up @@ -204,7 +208,7 @@ public ResponseEntity<StudentExam> updateWorkingTime(@PathVariable Long courseId
Exam exam = examService.findByIdWithExerciseGroupsAndExercisesElseThrow(examId);
if (ZonedDateTime.now().isAfter(exam.getVisibleDate())) {
instanceMessageSendService.sendExamWorkingTimeChangeDuringConduction(studentExamId);
studentExamService.notifyStudentAboutWorkingTimeChangeDuringConduction(savedStudentExam);
websocketMessagingService.sendMessage(WORKING_TIME_CHANGE_DURING_CONDUCTION_TOPIC.formatted(savedStudentExam.getId()), savedStudentExam.getWorkingTime());
}
if (ZonedDateTime.now().isBefore(examDateService.getLatestIndividualExamEndDate(exam)) && exam.getStartDate() != null
&& ZonedDateTime.now().isBefore(exam.getStartDate().plusSeconds(workingTime))) {
Expand Down
15 changes: 7 additions & 8 deletions src/test/java/de/tum/in/www1/artemis/exam/ExamUtilService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static org.assertj.core.api.Assertions.assertThat;

import java.net.URI;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.*;

Expand Down Expand Up @@ -300,7 +299,7 @@ public Exam addExamWithUser(Course course, User user, boolean exerciseGroup, Zon
exam.setVisibleDate(visibleDate);
exam.setStartDate(startDate);
exam.setEndDate(endDate);
exam.setWorkingTime((int) Duration.between(exam.getStartDate(), exam.getEndDate()).toSeconds());
exam.setWorkingTime(exam.getDuration());
exam.setNumberOfCorrectionRoundsInExam(1);
examRepository.save(exam);
return exam;
Expand Down Expand Up @@ -331,7 +330,7 @@ public Exam addExam(Course course, ZonedDateTime visibleDate, ZonedDateTime star
exam.setVisibleDate(visibleDate);
exam.setStartDate(startDate);
exam.setEndDate(endDate);
exam.setWorkingTime((int) Duration.between(startDate, endDate).toSeconds());
exam.setWorkingTime(exam.getDuration());
exam.setGracePeriod(180);
exam = examRepository.save(exam);
return exam;
Expand All @@ -343,7 +342,7 @@ public Exam addExam(Course course, ZonedDateTime visibleDate, ZonedDateTime star
exam.setStartDate(startDate);
exam.setEndDate(endDate);
exam.setPublishResultsDate(publishResultDate);
exam.setWorkingTime((int) Duration.between(startDate, endDate).toSeconds());
exam.setWorkingTime(exam.getDuration());
exam.setGracePeriod(180);
exam = examRepository.save(exam);
return exam;
Expand Down Expand Up @@ -372,7 +371,7 @@ public Exam addActiveExamWithRegisteredUser(Course course, User user) {
studentExam.setExam(exam);
studentExam.setTestRun(false);
studentExam.setUser(user);
studentExam.setWorkingTime((int) Duration.between(exam.getStartDate(), exam.getEndDate()).toSeconds());
studentExam.setWorkingTime(exam.getDuration());
studentExamRepository.save(studentExam);
return exam;
}
Expand Down Expand Up @@ -437,7 +436,7 @@ public StudentExam addStudentExamWithUser(Exam exam, String user) {
public StudentExam addStudentExamWithUser(Exam exam, User user) {
StudentExam studentExam = ExamFactory.generateStudentExam(exam);
studentExam.setUser(user);
studentExam.setWorkingTime((int) Duration.between(exam.getStartDate(), exam.getEndDate()).toSeconds());
studentExam.setWorkingTime(exam.getDuration());
studentExam = studentExamRepository.save(studentExam);
return studentExam;
}
Expand All @@ -463,7 +462,7 @@ public StudentExam addStudentExamForTestExam(Exam exam, User user) {
public StudentExam addStudentExamWithUser(Exam exam, User user, int additionalWorkingTime) {
StudentExam studentExam = ExamFactory.generateStudentExam(exam);
studentExam.setUser(user);
studentExam.setWorkingTime((int) Duration.between(exam.getStartDate(), exam.getEndDate()).toSeconds() + additionalWorkingTime);
studentExam.setWorkingTime(exam.getDuration() + additionalWorkingTime);
studentExam = studentExamRepository.save(studentExam);
return studentExam;
}
Expand Down Expand Up @@ -674,7 +673,7 @@ public void setVisibleStartAndEndDateOfExam(Exam exam, ZonedDateTime visibleDate
exam.setVisibleDate(visibleDate);
exam.setStartDate(startDate);
exam.setEndDate(endDate);
exam.setWorkingTime((int) Duration.between(startDate, endDate).toSeconds());
exam.setWorkingTime(exam.getDuration());
}

public StudentExam addExercisesWithParticipationsAndSubmissionsToStudentExam(Exam exam, StudentExam studentExam, String validModel, URI localRepoPath) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.tum.in.www1.artemis.exercise.programmingexercise;

import static de.tum.in.www1.artemis.config.Constants.*;
import static de.tum.in.www1.artemis.config.Constants.ASSIGNMENT_REPO_NAME;
import static de.tum.in.www1.artemis.config.Constants.NEW_RESULT_TOPIC;
import static de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage.C;
import static de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage.JAVA;
import static de.tum.in.www1.artemis.exercise.programmingexercise.ProgrammingSubmissionConstants.*;
Expand All @@ -9,7 +10,6 @@
import static org.mockito.AdditionalAnswers.answer;
import static org.mockito.Mockito.*;

import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
Expand Down Expand Up @@ -53,7 +53,6 @@
import de.tum.in.www1.artemis.exercise.ExerciseUtilService;
import de.tum.in.www1.artemis.participation.ParticipationUtilService;
import de.tum.in.www1.artemis.repository.*;
import de.tum.in.www1.artemis.service.connectors.bamboo.dto.BambooBuildLogDTO;
import de.tum.in.www1.artemis.service.connectors.bamboo.dto.BambooBuildResultNotificationDTO;
import de.tum.in.www1.artemis.service.connectors.bamboo.dto.BambooBuildResultNotificationDTO.BambooTestJobDTO;
import de.tum.in.www1.artemis.service.exam.ExamDateService;
Expand Down Expand Up @@ -683,8 +682,6 @@ void shouldSaveBuildLogsOnStudentParticipationWithoutResult(ProgrammingLanguage
var submission = programmingExerciseUtilService.createProgrammingSubmission(participation, false);

// Call programming-exercises/new-result which includes build log entries
final var buildLog = new BambooBuildLogDTO(ZonedDateTime.now().minusMinutes(1), "[ERROR] COMPILATION ERROR missing something",
"[ERROR] COMPILATION ERROR missing something");
postResultWithBuildLogs(participation.getBuildPlanId(), HttpStatus.OK, false, false);

var result = assertBuildError(participation.getId(), userLogin, programmingLanguage);
Expand Down Expand Up @@ -809,7 +806,7 @@ private StudentExam createEndedStudentExamWithGracePeriod(User user, Integer gra
exam = examRepository.save(exam);

var studentExam = studentExamRepository.findWithExercisesByUserIdAndExamId(user.getId(), exam.getId()).orElseThrow();
studentExam.setWorkingTime((int) Duration.between(exam.getStartDate(), exam.getEndDate()).getSeconds());
studentExam.setWorkingTime(exam.getDuration());
studentExam.setExercises(new ArrayList<>(exam.getExerciseGroups().get(6).getExercises()));
studentExam.setUser(user);
studentExam = studentExamRepository.save(studentExam);
Expand Down Expand Up @@ -967,7 +964,7 @@ private Result assertBuildError(Long participationId, String userLogin, Programm

// Assert that the build logs can be retrieved from the REST API
userUtilService.changeUser(userLogin);
var receivedLogs = request.get("/api/repository/" + participationId + "/buildlogs", HttpStatus.OK, List.class);
var receivedLogs = request.getList("/api/repository/" + participationId + "/buildlogs", HttpStatus.OK, BuildLogEntry.class);
assertThat(receivedLogs).isNotNull();
assertThat(receivedLogs).hasSameSizeAs(submissionWithLogs.getBuildLogEntries());

Expand Down