Skip to content

Commit

Permalink
Fix repository lock for individual exam extension
Browse files Browse the repository at this point in the history
  • Loading branch information
meltemarsl committed Nov 20, 2024
1 parent e3ed347 commit 289b371
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation;
import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.cit.aet.artemis.programming.repository.SubmissionPolicyRepository;
import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseParticipationService;
import de.tum.cit.aet.artemis.quiz.repository.SubmittedAnswerRepository;

/**
Expand Down Expand Up @@ -126,11 +128,15 @@ public class StudentExamResource {

private static final boolean IS_TEST_RUN = false;

private final ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository;

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

private static final String ENTITY_NAME = "studentExam";

private final ProgrammingExerciseParticipationService programmingExerciseParticipationService;

@Value("${jhipster.clientApp.name}")
private String applicationName;

Expand All @@ -140,7 +146,8 @@ public StudentExamResource(ExamAccessService examAccessService, ExamDeletionServ
StudentParticipationRepository studentParticipationRepository, ExamRepository examRepository, SubmittedAnswerRepository submittedAnswerRepository,
AuthorizationCheckService authorizationCheckService, ExamService examService, InstanceMessageSendService instanceMessageSendService,
WebsocketMessagingService websocketMessagingService, SubmissionPolicyRepository submissionPolicyRepository, ExamLiveEventsService examLiveEventsService,
ExamLiveEventRepository examLiveEventRepository) {
ExamLiveEventRepository examLiveEventRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository,
ProgrammingExerciseParticipationService programmingExerciseParticipationService) {
this.examAccessService = examAccessService;
this.examDeletionService = examDeletionService;
this.studentExamService = studentExamService;
Expand All @@ -160,6 +167,8 @@ public StudentExamResource(ExamAccessService examAccessService, ExamDeletionServ
this.submissionPolicyRepository = submissionPolicyRepository;
this.examLiveEventsService = examLiveEventsService;
this.examLiveEventRepository = examLiveEventRepository;
this.programmingExerciseStudentParticipationRepository = programmingExerciseStudentParticipationRepository;
this.programmingExerciseParticipationService = programmingExerciseParticipationService;
}

/**
Expand Down Expand Up @@ -257,6 +266,19 @@ public ResponseEntity<StudentExam> updateWorkingTime(@PathVariable Long courseId
// potentially re-schedule clustering of modeling submissions (in case Compass is active)
examService.scheduleModelingExercises(exam);
}
boolean wasEndedOriginally = now.isAfter(exam.getEndDate());
if (!studentExam.isEnded() && wasEndedOriginally) {
studentExam.getExercises().stream().filter(ProgrammingExercise.class::isInstance).forEach(exercise -> {
var programmingExerciseStudentParticipation = programmingExerciseStudentParticipationRepository.findByExerciseIdAndStudentLogin(exercise.getId(),
studentExam.getUser().getLogin());
var programmingExerciseSubmissionPolicy = ((ProgrammingExercise) exercise).getSubmissionPolicy();
// Unlock if there is no submission policy
// or there is a submission policy, but its limit was not reached yet
if (programmingExerciseSubmissionPolicy == null || exercise.getNumberOfSubmissions().inTime() < programmingExerciseSubmissionPolicy.getSubmissionLimit()) {
programmingExerciseStudentParticipation.ifPresent(programmingExerciseParticipationService::unlockStudentRepositoryAndParticipation);
}
});
}
}

return ResponseEntity.ok(savedStudentExam);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import static org.assertj.core.api.Assertions.within;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.time.Duration;
Expand Down Expand Up @@ -71,7 +73,9 @@
import de.tum.cit.aet.artemis.core.domain.Language;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.security.SecurityUtils;
import de.tum.cit.aet.artemis.core.user.util.UserUtilService;
import de.tum.cit.aet.artemis.core.util.RoundingUtil;
import de.tum.cit.aet.artemis.exam.domain.Exam;
import de.tum.cit.aet.artemis.exam.domain.ExamUser;
Expand Down Expand Up @@ -114,6 +118,8 @@
import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission;
import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.LockRepositoryPolicy;
import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPolicy;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.cit.aet.artemis.programming.test_repository.ProgrammingSubmissionTestRepository;
import de.tum.cit.aet.artemis.programming.util.LocalRepository;
import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseTestService;
Expand Down Expand Up @@ -151,6 +157,12 @@ class StudentExamIntegrationTest extends AbstractSpringIntegrationJenkinsGitlabT
@Autowired
private ExamUserRepository examUserRepository;

@Autowired
private ProgrammingExerciseStudentParticipationRepository participationRepository;

@Autowired
private ProgrammingExerciseRepository programmingExerciseRepository;

@Autowired
private SubmissionTestRepository submissionRepository;

Expand Down Expand Up @@ -205,6 +217,12 @@ class StudentExamIntegrationTest extends AbstractSpringIntegrationJenkinsGitlabT
@Autowired
private GradingScaleUtilService gradingScaleUtilService;

@Autowired
private CourseRepository courseRepository;

@Autowired
private UserUtilService userUtilService;

private User student1;

private Course course1;
Expand All @@ -231,6 +249,8 @@ class StudentExamIntegrationTest extends AbstractSpringIntegrationJenkinsGitlabT

private static final int NUMBER_OF_STUDENTS = 2;

private static final int NUMBER_OF_INSTRUCTORS = 1;

private static final boolean IS_TEST_RUN = false;

@BeforeEach
Expand Down Expand Up @@ -841,6 +861,48 @@ void testUpdateWorkingTimeLate() throws Exception {
assertThat(capturedEvent.oldWorkingTime()).isEqualTo(oldWorkingTime);
}

@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void testUpdateWorkingTime_ShouldTriggerUnlock() throws Exception {
ProgrammingExercise programmingExercise = programmingExerciseUtilService.addCourseExamExerciseGroupWithOneProgrammingExercise();
programmingExerciseRepository.save(programmingExercise);

Course course = programmingExercise.getCourseViaExerciseGroupOrCourseMember();
courseRepository.save(course);

userUtilService.addUsers(TEST_PREFIX, NUMBER_OF_STUDENTS, 0, 0, NUMBER_OF_INSTRUCTORS);
User student = userUtilService.getUserByLogin(TEST_PREFIX + "student1");

ProgrammingExerciseStudentParticipation participation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, student.getLogin());
participationRepository.save(participation);

Exam exam = programmingExercise.getExam();
exam.setStartDate(ZonedDateTime.now().minusHours(2));
exam.setEndDate(ZonedDateTime.now().minusHours(1));
examRepository.save(exam);

StudentExam studentExam = new StudentExam();
studentExam.setUser(student);
studentExam.setExercises(List.of(programmingExercise));
studentExam.setExam(exam);
studentExam.setTestRun(false);
studentExam.setWorkingTime(1);
studentExamRepository.save(studentExam);

doNothing().when(programmingExerciseParticipationService).unlockStudentRepositoryAndParticipation(any());

int newWorkingTime = 180 * 60;

StudentExam updatedExam = request.patchWithResponseBody(
"/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/student-exams/" + studentExam.getId() + "/working-time", newWorkingTime, StudentExam.class,
HttpStatus.OK);

assertThat(updatedExam).isNotNull();
assertThat(updatedExam.getWorkingTime()).isEqualTo(newWorkingTime);

verify(programmingExerciseParticipationService, times(1)).unlockStudentRepositoryAndParticipation(participation);
}

private ExamLiveEventBaseDTO captureExamLiveEventForId(Long studentExamOrExamId, boolean examWide) {
// Create an ArgumentCaptor for the WebSocket message
ArgumentCaptor<ExamLiveEventBaseDTO> websocketEventCaptor = ArgumentCaptor.forClass(ExamLiveEventBaseDTO.class);
Expand Down

0 comments on commit 289b371

Please sign in to comment.