SEBSERV-419 implementation and local testing

This commit is contained in:
anhefti 2024-07-04 15:29:43 +02:00
parent 16b2c8deb4
commit fb8df62f59
11 changed files with 408 additions and 317 deletions

View file

@ -86,6 +86,10 @@ public final class Exam implements GrantEntity {
ExamStatus.TEST_RUN.name(), ExamStatus.TEST_RUN.name(),
ExamStatus.RUNNING.name()); ExamStatus.RUNNING.name());
public static final List<String> RUNNING_STATE_NAMES = Arrays.asList(
ExamStatus.TEST_RUN.name(),
ExamStatus.RUNNING.name());
@JsonProperty(EXAM.ATTR_ID) @JsonProperty(EXAM.ATTR_ID)
public final Long id; public final Long id;

View file

@ -202,10 +202,10 @@ public class MonitoringProctoringService {
final EntityKey entityKey = pageContext.getEntityKey(); final EntityKey entityKey = pageContext.getEntityKey();
final I18nSupport i18nSupport = this.pageService.getI18nSupport(); final I18nSupport i18nSupport = this.pageService.getI18nSupport();
final TreeItem screeProcotringGroupAction = proctoringGUIService.getScreeProcotringGroupAction(group); final TreeItem screeProctoringGroupAction = proctoringGUIService.getScreeProcotringGroupAction(group);
if (screeProcotringGroupAction != null) { if (screeProctoringGroupAction != null) {
// update action // update action
screeProcotringGroupAction.setText(i18nSupport.getText(new LocTextKey( screeProctoringGroupAction.setText(i18nSupport.getText(new LocTextKey(
ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP.title.name, ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP.title.name,
group.name, group.name,
group.size))); group.size)));

View file

@ -9,7 +9,6 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao; package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
@ -124,6 +123,12 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
* @return Result refer to a collection of exams or to an error if happened */ * @return Result refer to a collection of exams or to an error if happened */
Result<Collection<Exam>> allThatNeedsStatusUpdate(long leadTime, long followupTime); Result<Collection<Exam>> allThatNeedsStatusUpdate(long leadTime, long followupTime);
/** Quickly checks if an Exam is running or not (Sate RUNNING or TEST_RUN)
*
* @param examId The identifier of the exam to check
* @return true if the exam is in a running state */
boolean isRunning(Long examId);
/** Get a collection of all currently running exam identifiers /** Get a collection of all currently running exam identifiers
* *
* @return collection of all currently running exam identifiers */ * @return collection of all currently running exam identifiers */

View file

@ -335,7 +335,7 @@ public class ExamDAOImpl implements ExamDAO {
isEqualTo(BooleanUtils.toInteger(true))) isEqualTo(BooleanUtils.toInteger(true)))
.and( .and(
ExamRecordDynamicSqlSupport.status, ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.RUNNING.name())) isIn(Exam.RUNNING_STATE_NAMES))
.and( .and(
ExamRecordDynamicSqlSupport.updating, ExamRecordDynamicSqlSupport.updating,
isEqualTo(BooleanUtils.toInteger(false))) isEqualTo(BooleanUtils.toInteger(false)))
@ -392,6 +392,27 @@ public class ExamDAOImpl implements ExamDAO {
.flatMap(this::toDomainModel); .flatMap(this::toDomainModel);
} }
@Override
@Transactional(readOnly = true)
public boolean isRunning(final Long examId) {
try {
final Long exists = this.examRecordMapper.countByExample()
.where(
id,
isEqualTo(examId))
.and(
status,
isIn(Exam.RUNNING_STATE_NAMES))
.build()
.execute();
return exists >= 1;
} catch (final Exception e) {
log.error("Failed to check if exam is running: {} error: {}", examId, e.getMessage());
return false;
}
}
@Override @Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE) @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public Result<Long> placeLock(final Long examId, final String updateId) { public Result<Long> placeLock(final Long examId, final String updateId) {
@ -736,7 +757,7 @@ public class ExamDAOImpl implements ExamDAO {
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true))) isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
.and( .and(
ExamRecordDynamicSqlSupport.status, ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.RUNNING.name())) isIn(Exam.RUNNING_STATE_NAMES))
.build() .build()
.execute(); .execute();
}); });
@ -754,7 +775,7 @@ public class ExamDAOImpl implements ExamDAO {
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true))) isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
.and( .and(
ExamRecordDynamicSqlSupport.status, ExamRecordDynamicSqlSupport.status,
isEqualTo(ExamStatus.RUNNING.name())) isIn(Exam.RUNNING_STATE_NAMES))
.and( .and(
ExamRecordDynamicSqlSupport.lmsAvailable, ExamRecordDynamicSqlSupport.lmsAvailable,
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true))) isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))

View file

@ -137,7 +137,8 @@ public class ExamSessionCacheService {
} }
final ClientConnection clientConnection = getClientConnectionByToken(connectionToken); final ClientConnection clientConnection = getClientConnectionByToken(connectionToken);
if (clientConnection == null) { // TODO check running exam within cache instead of DB call
if (clientConnection == null || (clientConnection.examId != null && !examDAO.isRunning(clientConnection.examId))) {
return null; return null;
} else { } else {
return this.internalClientConnectionDataFactory.createClientConnectionData(clientConnection); return this.internalClientConnectionDataFactory.createClientConnectionData(clientConnection);

View file

@ -52,7 +52,7 @@ public class InternalClientConnectionDataFactory {
public ClientConnectionDataInternal createClientConnectionData(final ClientConnection clientConnection) { public ClientConnectionDataInternal createClientConnectionData(final ClientConnection clientConnection) {
ClientConnectionDataInternal result; final ClientConnectionDataInternal result;
if (clientConnection.status == ConnectionStatus.CLOSED if (clientConnection.status == ConnectionStatus.CLOSED
|| clientConnection.status == ConnectionStatus.DISABLED) { || clientConnection.status == ConnectionStatus.DISABLED) {

View file

@ -12,21 +12,18 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import javax.annotation.PreDestroy;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@ -44,6 +41,7 @@ public class SEBClientPingBatchService implements SEBClientPingService {
private final ExamSessionCacheService examSessionCacheService; private final ExamSessionCacheService examSessionCacheService;
private final SEBClientInstructionService sebClientInstructionService; private final SEBClientInstructionService sebClientInstructionService;
private final ClientConnectionDAO clientConnectionDAO;
private final Set<String> pingKeys = new HashSet<>(); private final Set<String> pingKeys = new HashSet<>();
private final Map<String, String> pings = new ConcurrentHashMap<>(); private final Map<String, String> pings = new ConcurrentHashMap<>();
@ -51,10 +49,12 @@ public class SEBClientPingBatchService implements SEBClientPingService {
public SEBClientPingBatchService( public SEBClientPingBatchService(
final ExamSessionCacheService examSessionCacheService, final ExamSessionCacheService examSessionCacheService,
final SEBClientInstructionService sebClientInstructionService) { final SEBClientInstructionService sebClientInstructionService,
final ClientConnectionDAO clientConnectionDAO) {
this.examSessionCacheService = examSessionCacheService; this.examSessionCacheService = examSessionCacheService;
this.sebClientInstructionService = sebClientInstructionService; this.sebClientInstructionService = sebClientInstructionService;
this.clientConnectionDAO = clientConnectionDAO;
} }
@Scheduled(fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}") @Scheduled(fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}")
@ -71,7 +71,7 @@ public class SEBClientPingBatchService implements SEBClientPingService {
try { try {
this.pingKeys.clear(); this.pingKeys.clear();
this.pingKeys.addAll(this.pings.keySet()); this.pingKeys.addAll(this.pings.keySet());
this.pingKeys.stream().forEach(cid -> processPing( this.pingKeys.forEach(cid -> processPing(
cid, cid,
this.pings.remove(cid), this.pings.remove(cid),
Utils.getMillisecondsNow())); Utils.getMillisecondsNow()));
@ -94,10 +94,8 @@ public class SEBClientPingBatchService implements SEBClientPingService {
final String instruction = this.instructions.remove(connectionToken); final String instruction = this.instructions.remove(connectionToken);
if (instructionConfirm != null) { if (instructionConfirm != null) {
System.out.println("************ put instructionConfirm: " + instructionConfirm + " instructions: "
+ this.instructions);
this.pings.put(connectionToken, instructionConfirm); this.pings.put(connectionToken, instructionConfirm);
// // TODO is this a good idea or is there another better way to deal with instruction confirm synchronization?
if (instruction != null && instruction.contains("\"instruction-confirm\":\"" + instructionConfirm + "\"")) { if (instruction != null && instruction.contains("\"instruction-confirm\":\"" + instructionConfirm + "\"")) {
return null; return null;
} }
@ -105,9 +103,6 @@ public class SEBClientPingBatchService implements SEBClientPingService {
this.pings.put(connectionToken, StringUtils.EMPTY); this.pings.put(connectionToken, StringUtils.EMPTY);
} }
// System.out.println(
// "**************** notifyPing instructionConfirm: " + instructionConfirm + " pings: " + this.pings);
return instruction; return instruction;
} }
@ -126,19 +121,13 @@ public class SEBClientPingBatchService implements SEBClientPingService {
if (connectionData != null) { if (connectionData != null) {
if (connectionData.clientConnection.status == ClientConnection.ConnectionStatus.DISABLED) { if (connectionData.clientConnection.status == ClientConnection.ConnectionStatus.DISABLED) {
// SEBSERV-440 send quit instruction to SEB // SEBSERV-440 send quit instruction to SEB
sebClientInstructionService.registerInstruction( sendQuitInstruction(connectionToken, connectionData.clientConnection.examId);
connectionData.clientConnection.examId,
ClientInstruction.InstructionType.SEB_QUIT,
Collections.emptyMap(),
connectionData.clientConnection.connectionToken,
false,
false
);
} }
connectionData.notifyPing(timestamp); connectionData.notifyPing(timestamp);
} else { } else {
log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken); log.warn("Failed to get ClientConnectionDataInternal probably due to finished Exam for: {}.", connectionToken);
sendQuitInstruction(connectionToken,null);
} }
if (StringUtils.isNotBlank(instructionConfirm)) { if (StringUtils.isNotBlank(instructionConfirm)) {
@ -154,4 +143,38 @@ public class SEBClientPingBatchService implements SEBClientPingService {
this.instructions.put(connectionToken, instructionJSON); this.instructions.put(connectionToken, instructionJSON);
} }
} }
private void sendQuitInstruction(final String connectionToken, final Long examId) {
Long _examId = examId;
if (examId == null) {
final Result<ClientConnection> clientConnectionResult = clientConnectionDAO
.byConnectionToken(connectionToken);
if (clientConnectionResult.hasError()) {
log.error(
"Failed to get examId for client connection token: {} error: {}",
connectionToken,
clientConnectionResult.getError().getMessage());
}
_examId = clientConnectionResult.get().examId;
}
if (_examId != null) {
log.info("Send automated quit instruction to SEB for connection token: {}", connectionToken);
// TODO add SEB event log that SEB Server has automatically send quit instruction to SEB
sebClientInstructionService.registerInstruction(
_examId,
ClientInstruction.InstructionType.SEB_QUIT,
Collections.emptyMap(),
connectionToken,
false,
false
);
}
}
} }

View file

@ -115,7 +115,7 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
final Collection<String> currentlyInBreakoutRooms = this.remoteProctoringRoomDAO final Collection<String> currentlyInBreakoutRooms = this.remoteProctoringRoomDAO
.getConnectionsInBreakoutRooms(examId) .getConnectionsInBreakoutRooms(examId)
.getOrElse(() -> Collections.emptyList()); .getOrElse(Collections::emptyList);
if (currentlyInBreakoutRooms.isEmpty()) { if (currentlyInBreakoutRooms.isEmpty()) {
return this.clientConnectionDAO return this.clientConnectionDAO
@ -132,52 +132,58 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
@Override @Override
public void updateProctoringCollectingRooms() { public void updateProctoringCollectingRooms() {
try {
// Applying to collecting room // NOTE: Since life proctoring is not supported anymore, we disable automated updates here
this.clientConnectionDAO
.getAllForProctoringUpdateActive()
.getOrThrow()
.stream()
.forEach(this::assignToCollectingRoom);
// Dispose from collecting room // try {
this.clientConnectionDAO //
.getAllForProctoringUpdateInactive() // // Applying to collecting room
.getOrThrow() // this.clientConnectionDAO
.stream() // .getAllForProctoringUpdateActive()
.forEach(this::removeFromRoom); // .getOrThrow()
// .forEach(this::assignToCollectingRoom);
} catch (final Exception e) { //
log.error("Unexpected error while trying to update proctoring collecting rooms: ", e); // // Dispose from collecting room
} // this.clientConnectionDAO
// .getAllForProctoringUpdateInactive()
// .getOrThrow()
// .forEach(this::removeFromRoom);
//
// } catch (final Exception e) {
// log.error("Unexpected error while trying to update proctoring collecting rooms: ", e);
// }
} }
@EventListener(ExamDeletionEvent.class) @EventListener(ExamDeletionEvent.class)
public void notifyExamDeletionEvent(final ExamDeletionEvent event) { public void notifyExamDeletionEvent(final ExamDeletionEvent event) {
event.ids.forEach(examId -> {
try {
this.examAdminService // NOTE: Since life proctoring is not supported anymore, we disable automated updates here
.examForPK(examId)
.flatMap(this::disposeRoomsForExam)
.getOrThrow();
} catch (final Exception e) { // event.ids.forEach(examId -> {
log.error("Failed to delete depending proctoring data for exam: {}", examId, e); // try {
} //
}); // this.examAdminService
// .examForPK(examId)
// .flatMap(this::disposeRoomsForExam)
// .getOrThrow();
//
// } catch (final Exception e) {
// log.error("Failed to delete depending proctoring data for exam: {}", examId, e);
// }
// });
} }
@EventListener @EventListener
public void notifyExamFinished(final ExamFinishedEvent event) { public void notifyExamFinished(final ExamFinishedEvent event) {
if (log.isDebugEnabled()) { // NOTE: Since life proctoring is not supported anymore, we disable automated updates here
log.debug("ExamFinishedEvent received, process disposeRoomsForExam...");
}
disposeRoomsForExam(event.exam) // if (log.isDebugEnabled()) {
.onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error)); // log.debug("ExamFinishedEvent received, process disposeRoomsForExam...");
// }
//
// disposeRoomsForExam(event.exam)
// .onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error));
} }
@Override @Override

View file

@ -386,7 +386,7 @@ class ScreenProctoringAPIBinding {
void synchronizeUserAccounts(final Exam exam) { void synchronizeUserAccounts(final Exam exam) {
try { try {
final ScreenProctoringServiceOAuthTemplate apiTemplate = getAPITemplate(null);
exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate)); exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate));
if (exam.owner != null) { if (exam.owner != null) {
synchronizeUserAccount(exam.owner, apiTemplate); synchronizeUserAccount(exam.owner, apiTemplate);
@ -1156,12 +1156,24 @@ class ScreenProctoringAPIBinding {
} }
} }
private ScreenProctoringServiceOAuthTemplate apiTemplate = null; private ScreenProctoringServiceOAuthTemplate apiTemplateExam = null;
private ScreenProctoringServiceOAuthTemplate apiTemplateBundle = null;
private ScreenProctoringServiceOAuthTemplate getAPITemplate(final Long examId) { private ScreenProctoringServiceOAuthTemplate getAPITemplate(final Long examId) {
if (this.apiTemplate == null || !this.apiTemplate.isValid(examId)) { if (examId == null) {
if (examId != null) { if (apiTemplateBundle == null) {
if (log.isDebugEnabled()) {
log.debug("Create new ScreenProctoringServiceOAuthTemplate for bundle");
}
final WebserviceInfo.ScreenProctoringServiceBundle bundle = this.webserviceInfo
.getScreenProctoringServiceBundle();
this.testConnection(bundle).getOrThrow();
this.apiTemplateBundle = new ScreenProctoringServiceOAuthTemplate(this, bundle);
}
return apiTemplateBundle;
} else {
if (this.apiTemplateExam == null || !this.apiTemplateExam.isValid(examId)) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId); log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
} }
@ -1170,27 +1182,44 @@ class ScreenProctoringAPIBinding {
.getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM)) .getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM))
.getOrThrow(); .getOrThrow();
this.testConnection(settings).getOrThrow(); this.testConnection(settings).getOrThrow();
this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, settings); this.apiTemplateExam = new ScreenProctoringServiceOAuthTemplate(this, settings);
} else if (this.webserviceInfo.getScreenProctoringServiceBundle().bundled) {
if (log.isDebugEnabled()) {
log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
}
final WebserviceInfo.ScreenProctoringServiceBundle bundle = this.webserviceInfo
.getScreenProctoringServiceBundle();
this.testConnection(bundle).getOrThrow();
this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, bundle);
} else {
throw new IllegalStateException("No SPS API access information found!");
} }
return apiTemplateExam;
} }
return this.apiTemplate; // if (this.apiTemplate == null || !this.apiTemplate.isValid(examId)) {
// if (examId != null) {
//
// if (log.isDebugEnabled()) {
// log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
// }
//
// final ScreenProctoringSettings settings = this.proctoringSettingsDAO
// .getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM))
// .getOrThrow();
// this.testConnection(settings).getOrThrow();
// this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, settings);
//
// } else if (this.webserviceInfo.getScreenProctoringServiceBundle().bundled) {
//
// if (log.isDebugEnabled()) {
// log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
// }
//
// final WebserviceInfo.ScreenProctoringServiceBundle bundle = this.webserviceInfo
// .getScreenProctoringServiceBundle();
//
// this.testConnection(bundle).getOrThrow();
// this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, bundle);
//
//
// } else {
// throw new IllegalStateException("No SPS API access information found!");
// }
// }
//
// return this.apiTemplate;
} }
private static List<String> getSupporterIds(final Exam exam) { private static List<String> getSupporterIds(final Exam exam) {

View file

@ -284,6 +284,7 @@ public class ExamMonitoringController {
.flatMap(authorization::checkModify) .flatMap(authorization::checkModify)
.flatMap(examSessionService::toggleTestRun) .flatMap(examSessionService::toggleTestRun)
.map(exam -> { .map(exam -> {
examSessionService.flushCache(exam);
if (exam.status == Exam.ExamStatus.TEST_RUN) { if (exam.status == Exam.ExamStatus.TEST_RUN) {
applicationEventPublisher.publishEvent(new ExamStartedEvent(exam)); applicationEventPublisher.publishEvent(new ExamStartedEvent(exam));
} else if (exam.status == Exam.ExamStatus.UP_COMING) { } else if (exam.status == Exam.ExamStatus.UP_COMING) {

View file

@ -3772,236 +3772,237 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
@Autowired @Autowired
private ClientConnectionDAO clientConnectionDAO; private ClientConnectionDAO clientConnectionDAO;
@Test // NOTE: Not supported anymore
@Order(28) // @Test
// ************************************* // @Order(28)
// Use Case 28: Login as admin and connect with SEBs to running exam with procotring enabled // // *************************************
// - Get Exam (running) // // Use Case 28: Login as admin and connect with SEBs to running exam with procotring enabled
// - start some SEB clients connecting to running exam // // - Get Exam (running)
// - Check collecting rooms created // // - start some SEB clients connecting to running exam
public void testUsecase28_TestExamProctoring() throws IOException { // // - Check collecting rooms created
// public void testUsecase28_TestExamProctoring() throws IOException {
final RestServiceImpl restService = createRestServiceForUser( //
"admin", // final RestServiceImpl restService = createRestServiceForUser(
"admin", // "admin",
new GetExamPage(), // "admin",
new GetExamProctoringSettings(), // new GetExamPage(),
new SaveExamProctoringSettings(), // new GetExamProctoringSettings(),
new IsTownhallRoomAvailable(), // new SaveExamProctoringSettings(),
new GetCollectingRooms(), // new IsTownhallRoomAvailable(),
new GetClientConfigPage(), // new GetCollectingRooms(),
new ActivateClientConfig(), // new GetClientConfigPage(),
new NewClientConfig(), // new ActivateClientConfig(),
new GetClientConfig(), // new NewClientConfig(),
new GetProctorRoomConnection(), // new GetClientConfig(),
new GetCollectingRoomConnections(), // new GetProctorRoomConnection(),
new NotifyProctoringRoomOpened(), // new GetCollectingRoomConnections(),
new SendProctoringReconfigurationAttributes(), // new NotifyProctoringRoomOpened(),
new GetTownhallRoom(), // new SendProctoringReconfigurationAttributes(),
new OpenTownhallRoom(), // new GetTownhallRoom(),
new CloseProctoringRoom()); // new OpenTownhallRoom(),
// new CloseProctoringRoom());
// get exam //
final Result<Page<Exam>> exams = restService // // get exam
.getBuilder(GetExamPage.class) // final Result<Page<Exam>> exams = restService
.call(); // .getBuilder(GetExamPage.class)
// .call();
assertNotNull(exams); //
assertFalse(exams.hasError()); // assertNotNull(exams);
final Page<Exam> examPage = exams.get(); // assertFalse(exams.hasError());
assertFalse(examPage.isEmpty()); // final Page<Exam> examPage = exams.get();
// assertFalse(examPage.isEmpty());
final Exam runningExam = examPage.content //
.stream() // final Exam runningExam = examPage.content
.filter(exam -> exam.status == ExamStatus.RUNNING) // .stream()
.findFirst() // .filter(exam -> exam.status == ExamStatus.RUNNING)
.orElse(null); // .findFirst()
// .orElse(null);
assertNotNull(runningExam); //
assertTrue(runningExam.status == ExamStatus.RUNNING); // assertNotNull(runningExam);
// assertTrue(runningExam.status == ExamStatus.RUNNING);
final Result<ProctoringServiceSettings> pSettings = restService //
.getBuilder(GetExamProctoringSettings.class) // final Result<ProctoringServiceSettings> pSettings = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetExamProctoringSettings.class)
.call(); // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
// .call();
assertNotNull(pSettings); //
assertFalse(pSettings.hasError()); // assertNotNull(pSettings);
final ProctoringServiceSettings proctoringServiceSettings = pSettings.get(); // assertFalse(pSettings.hasError());
assertTrue(proctoringServiceSettings.enableProctoring); // final ProctoringServiceSettings proctoringServiceSettings = pSettings.get();
assertEquals("https://test.proc/service", proctoringServiceSettings.serverURL); // assertTrue(proctoringServiceSettings.enableProctoring);
// assertEquals("https://test.proc/service", proctoringServiceSettings.serverURL);
// start some SEB connections for this exam //
// // start some SEB connections for this exam
// create SEB Client Config without password protection //
Result<SEBClientConfig> newConfigResponse = restService // // create SEB Client Config without password protection
.getBuilder(NewClientConfig.class) // Result<SEBClientConfig> newConfigResponse = restService
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection") // .getBuilder(NewClientConfig.class)
.withFormParam(SEBClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING) // .withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection")
.withFormParam(SEBClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback") // .withFormParam(SEBClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
.withFormParam(SEBClientConfig.ATTR_FALLBACK_TIMEOUT, "100") // .withFormParam(SEBClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
.withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPTS, "5") // .withFormParam(SEBClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
.withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5") // .withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
.withFormParam(SEBClientConfig.ATTR_CONFIG_PURPOSE, SEBClientConfig.ConfigPurpose.START_EXAM.name()) // .withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
.call(); // .withFormParam(SEBClientConfig.ATTR_CONFIG_PURPOSE, SEBClientConfig.ConfigPurpose.START_EXAM.name())
// .call();
assertNotNull(newConfigResponse); //
assertFalse(newConfigResponse.hasError()); // assertNotNull(newConfigResponse);
final SEBClientConfig sebClientConfig = newConfigResponse.get(); // assertFalse(newConfigResponse.hasError());
assertEquals("No Password Protection", sebClientConfig.name); // final SEBClientConfig sebClientConfig = newConfigResponse.get();
assertFalse(sebClientConfig.isActive()); // assertEquals("No Password Protection", sebClientConfig.name);
assertEquals("http://fallback.com/fallback", sebClientConfig.fallbackStartURL); // assertFalse(sebClientConfig.isActive());
// assertEquals("http://fallback.com/fallback", sebClientConfig.fallbackStartURL);
// activate the new Client Configuration //
restService // // activate the new Client Configuration
.getBuilder(ActivateClientConfig.class) // restService
.withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId()) // .getBuilder(ActivateClientConfig.class)
.call(); // .withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId())
// .call();
newConfigResponse = restService.getBuilder(GetClientConfig.class) //
.withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId()) // newConfigResponse = restService.getBuilder(GetClientConfig.class)
.call(); // .withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId())
// .call();
final SEBClientConfig clientConfig = newConfigResponse.get(); //
assertTrue(clientConfig.isActive()); // final SEBClientConfig clientConfig = newConfigResponse.get();
final ClientCredentials credentials = this.sebClientConfigDAO // assertTrue(clientConfig.isActive());
.getSEBClientCredentials(clientConfig.getModelId()) // final ClientCredentials credentials = this.sebClientConfigDAO
.getOrThrow(); // .getSEBClientCredentials(clientConfig.getModelId())
// .getOrThrow();
assertTrue(clientConfig.isActive()); //
// assertTrue(clientConfig.isActive());
// simulate a SEB connection... //
try { // // simulate a SEB connection...
new SEBClientBot( // try {
credentials.clientIdAsString(), // new SEBClientBot(
this.cryptor.decrypt(credentials.secret).getOrThrow().toString(), // credentials.clientIdAsString(),
runningExam.getModelId(), // this.cryptor.decrypt(credentials.secret).getOrThrow().toString(),
String.valueOf(runningExam.institutionId), // runningExam.getModelId(),
false); // String.valueOf(runningExam.institutionId),
// false);
Thread.sleep(1000); //
// Thread.sleep(1000);
this.examProcotringRoomService.updateProctoringCollectingRooms(); //
// this.examProcotringRoomService.updateProctoringCollectingRooms();
// check collecting room was created //
final Collection<RemoteProctoringRoom> collectingRooms = restService // // check collecting room was created
.getBuilder(GetCollectingRooms.class) // final Collection<RemoteProctoringRoom> collectingRooms = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetCollectingRooms.class)
.call() // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.get(); // .call()
// .get();
assertNotNull(collectingRooms); //
assertFalse(collectingRooms.isEmpty()); // assertNotNull(collectingRooms);
// Two rooms a two people for four connections // assertFalse(collectingRooms.isEmpty());
assertEquals(2, collectingRooms.size()); // // Two rooms a two people for four connections
final RemoteProctoringRoom room1 = collectingRooms.iterator().next(); // assertEquals(2, collectingRooms.size());
assertEquals(2, room1.roomSize.intValue()); // final RemoteProctoringRoom room1 = collectingRooms.iterator().next();
assertFalse(room1.townhallRoom); // assertEquals(2, room1.roomSize.intValue());
// assertFalse(room1.townhallRoom);
final ProctoringRoomConnection proctoringRoomConnection = restService //
.getBuilder(GetProctorRoomConnection.class) // final ProctoringRoomConnection proctoringRoomConnection = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetProctorRoomConnection.class)
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name) // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.call() // .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name)
.get(); // .call()
// .get();
assertNotNull(proctoringRoomConnection); //
assertEquals(room1.name, proctoringRoomConnection.roomName); // assertNotNull(proctoringRoomConnection);
assertNotNull(proctoringRoomConnection.accessToken); // assertEquals(room1.name, proctoringRoomConnection.roomName);
// assertNotNull(proctoringRoomConnection.accessToken);
// notify room open //
restService // // notify room open
.getBuilder(NotifyProctoringRoomOpened.class) // restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(NotifyProctoringRoomOpened.class)
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name) // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.call() // .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name)
.get(); // .call()
// .get();
// reconfigure clients in room //
restService // // reconfigure clients in room
.getBuilder(SendProctoringReconfigurationAttributes.class) // restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(SendProctoringReconfigurationAttributes.class)
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name) // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, "true") // .withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name)
.withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO, "true") // .withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, "true")
.withQueryParam(API.EXAM_PROCTORING_ATTR_ALLOW_CHAT, "true") // .withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO, "true")
.call() // .withQueryParam(API.EXAM_PROCTORING_ATTR_ALLOW_CHAT, "true")
.get(); // .call()
// .get();
final Collection<ClientConnection> collection = restService //
.getBuilder(GetCollectingRoomConnections.class) // final Collection<ClientConnection> collection = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetCollectingRoomConnections.class)
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name) // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.call() // .withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name)
.get(); // .call()
// .get();
assertNotNull(collection); //
assertFalse(collection.isEmpty()); // assertNotNull(collection);
assertEquals(2, collection.size()); // assertFalse(collection.isEmpty());
final ClientConnection connection = collection.iterator().next(); // assertEquals(2, collection.size());
assertEquals(runningExam.id, connection.examId); // final ClientConnection connection = collection.iterator().next();
// this is because the Json model do not contian certain attributes due to performance // assertEquals(runningExam.id, connection.examId);
assertNull(connection.remoteProctoringRoomId); // // this is because the Json model do not contian certain attributes due to performance
// we can geht the room number by getting it directyl from the record // assertNull(connection.remoteProctoringRoomId);
final ClientConnection clientConnection = this.clientConnectionDAO.byPK(connection.id).get(); // // we can geht the room number by getting it directyl from the record
assertNotNull(clientConnection.remoteProctoringRoomId); // final ClientConnection clientConnection = this.clientConnectionDAO.byPK(connection.id).get();
// assertNotNull(clientConnection.remoteProctoringRoomId);
// get and open townhall //
final String townhallActive = restService // // get and open townhall
.getBuilder(IsTownhallRoomAvailable.class) // final String townhallActive = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(IsTownhallRoomAvailable.class)
.call() // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.get(); // .call()
// .get();
assertEquals("true", townhallActive); //
// assertEquals("true", townhallActive);
// check no Townhallroom yet //
RemoteProctoringRoom townhallRoom = restService // // check no Townhallroom yet
.getBuilder(GetTownhallRoom.class) // RemoteProctoringRoom townhallRoom = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetTownhallRoom.class)
.call() // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.get(); // .call()
assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom); // .get();
// assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom);
// open townhall room //
final ProctoringRoomConnection townhallRoomConntection = restService // // open townhall room
.getBuilder(OpenTownhallRoom.class) // final ProctoringRoomConnection townhallRoomConntection = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(OpenTownhallRoom.class)
.call() // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.get(); // .call()
// .get();
assertNotNull(townhallRoomConntection); //
// assertNotNull(townhallRoomConntection);
// check Townhallroom is available yet //
townhallRoom = restService // // check Townhallroom is available yet
.getBuilder(GetTownhallRoom.class) // townhallRoom = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetTownhallRoom.class)
.call() // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.get(); // .call()
assertTrue(townhallRoom.townhallRoom); // .get();
assertEquals(townhallRoom.name, townhallRoomConntection.roomName); // assertTrue(townhallRoom.townhallRoom);
// assertEquals(townhallRoom.name, townhallRoomConntection.roomName);
// close townhall room //
restService // // close townhall room
.getBuilder(CloseProctoringRoom.class) // restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(CloseProctoringRoom.class)
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, townhallRoom.name) // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.call() // .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, townhallRoom.name)
.get(); // .call()
// .get();
townhallRoom = restService //
.getBuilder(GetTownhallRoom.class) // townhallRoom = restService
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId()) // .getBuilder(GetTownhallRoom.class)
.call() // .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
.get(); // .call()
assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom); // .get();
// assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom);
Thread.sleep(5000); //
// Thread.sleep(5000);
} catch (final Exception e) { //
fail(e.getMessage()); // } catch (final Exception e) {
} // fail(e.getMessage());
} // }
// }
@Test @Test
@Order(29) @Order(29)