SEBSERV-419 implementation and local testing
This commit is contained in:
parent
16b2c8deb4
commit
fb8df62f59
11 changed files with 408 additions and 317 deletions
|
@ -86,6 +86,10 @@ public final class Exam implements GrantEntity {
|
|||
ExamStatus.TEST_RUN.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)
|
||||
public final Long id;
|
||||
|
||||
|
|
|
@ -202,10 +202,10 @@ public class MonitoringProctoringService {
|
|||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
|
||||
|
||||
final TreeItem screeProcotringGroupAction = proctoringGUIService.getScreeProcotringGroupAction(group);
|
||||
if (screeProcotringGroupAction != null) {
|
||||
final TreeItem screeProctoringGroupAction = proctoringGUIService.getScreeProcotringGroupAction(group);
|
||||
if (screeProctoringGroupAction != null) {
|
||||
// update action
|
||||
screeProcotringGroupAction.setText(i18nSupport.getText(new LocTextKey(
|
||||
screeProctoringGroupAction.setText(i18nSupport.getText(new LocTextKey(
|
||||
ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP.title.name,
|
||||
group.name,
|
||||
group.size)));
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
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 */
|
||||
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
|
||||
*
|
||||
* @return collection of all currently running exam identifiers */
|
||||
|
|
|
@ -335,7 +335,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
isEqualTo(BooleanUtils.toInteger(true)))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isEqualTo(ExamStatus.RUNNING.name()))
|
||||
isIn(Exam.RUNNING_STATE_NAMES))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.updating,
|
||||
isEqualTo(BooleanUtils.toInteger(false)))
|
||||
|
@ -392,6 +392,27 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
.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
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
|
||||
public Result<Long> placeLock(final Long examId, final String updateId) {
|
||||
|
@ -736,7 +757,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isEqualTo(ExamStatus.RUNNING.name()))
|
||||
isIn(Exam.RUNNING_STATE_NAMES))
|
||||
.build()
|
||||
.execute();
|
||||
});
|
||||
|
@ -754,7 +775,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.status,
|
||||
isEqualTo(ExamStatus.RUNNING.name()))
|
||||
isIn(Exam.RUNNING_STATE_NAMES))
|
||||
.and(
|
||||
ExamRecordDynamicSqlSupport.lmsAvailable,
|
||||
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
||||
|
|
|
@ -137,7 +137,8 @@ public class ExamSessionCacheService {
|
|||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
return this.internalClientConnectionDataFactory.createClientConnectionData(clientConnection);
|
||||
|
|
|
@ -52,7 +52,7 @@ public class InternalClientConnectionDataFactory {
|
|||
|
||||
public ClientConnectionDataInternal createClientConnectionData(final ClientConnection clientConnection) {
|
||||
|
||||
ClientConnectionDataInternal result;
|
||||
final ClientConnectionDataInternal result;
|
||||
if (clientConnection.status == ConnectionStatus.CLOSED
|
||||
|| clientConnection.status == ConnectionStatus.DISABLED) {
|
||||
|
||||
|
|
|
@ -12,21 +12,18 @@ import java.util.Collections;
|
|||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
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.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.ehcache.impl.internal.concurrent.ConcurrentHashMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
@ -44,6 +41,7 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
|
||||
private final ExamSessionCacheService examSessionCacheService;
|
||||
private final SEBClientInstructionService sebClientInstructionService;
|
||||
private final ClientConnectionDAO clientConnectionDAO;
|
||||
|
||||
private final Set<String> pingKeys = new HashSet<>();
|
||||
private final Map<String, String> pings = new ConcurrentHashMap<>();
|
||||
|
@ -51,10 +49,12 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
|
||||
public SEBClientPingBatchService(
|
||||
final ExamSessionCacheService examSessionCacheService,
|
||||
final SEBClientInstructionService sebClientInstructionService) {
|
||||
final SEBClientInstructionService sebClientInstructionService,
|
||||
final ClientConnectionDAO clientConnectionDAO) {
|
||||
|
||||
this.examSessionCacheService = examSessionCacheService;
|
||||
this.sebClientInstructionService = sebClientInstructionService;
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}")
|
||||
|
@ -71,7 +71,7 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
try {
|
||||
this.pingKeys.clear();
|
||||
this.pingKeys.addAll(this.pings.keySet());
|
||||
this.pingKeys.stream().forEach(cid -> processPing(
|
||||
this.pingKeys.forEach(cid -> processPing(
|
||||
cid,
|
||||
this.pings.remove(cid),
|
||||
Utils.getMillisecondsNow()));
|
||||
|
@ -94,10 +94,8 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
final String instruction = this.instructions.remove(connectionToken);
|
||||
|
||||
if (instructionConfirm != null) {
|
||||
System.out.println("************ put instructionConfirm: " + instructionConfirm + " instructions: "
|
||||
+ this.instructions);
|
||||
|
||||
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 + "\"")) {
|
||||
return null;
|
||||
}
|
||||
|
@ -105,9 +103,6 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
this.pings.put(connectionToken, StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
// System.out.println(
|
||||
// "**************** notifyPing instructionConfirm: " + instructionConfirm + " pings: " + this.pings);
|
||||
|
||||
return instruction;
|
||||
}
|
||||
|
||||
|
@ -126,19 +121,13 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
if (connectionData != null) {
|
||||
if (connectionData.clientConnection.status == ClientConnection.ConnectionStatus.DISABLED) {
|
||||
// SEBSERV-440 send quit instruction to SEB
|
||||
sebClientInstructionService.registerInstruction(
|
||||
connectionData.clientConnection.examId,
|
||||
ClientInstruction.InstructionType.SEB_QUIT,
|
||||
Collections.emptyMap(),
|
||||
connectionData.clientConnection.connectionToken,
|
||||
false,
|
||||
false
|
||||
);
|
||||
sendQuitInstruction(connectionToken, connectionData.clientConnection.examId);
|
||||
}
|
||||
|
||||
connectionData.notifyPing(timestamp);
|
||||
} 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)) {
|
||||
|
@ -154,4 +143,38 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
|||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
|
|||
|
||||
final Collection<String> currentlyInBreakoutRooms = this.remoteProctoringRoomDAO
|
||||
.getConnectionsInBreakoutRooms(examId)
|
||||
.getOrElse(() -> Collections.emptyList());
|
||||
.getOrElse(Collections::emptyList);
|
||||
|
||||
if (currentlyInBreakoutRooms.isEmpty()) {
|
||||
return this.clientConnectionDAO
|
||||
|
@ -132,52 +132,58 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
|
|||
|
||||
@Override
|
||||
public void updateProctoringCollectingRooms() {
|
||||
try {
|
||||
|
||||
// Applying to collecting room
|
||||
this.clientConnectionDAO
|
||||
.getAllForProctoringUpdateActive()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(this::assignToCollectingRoom);
|
||||
// NOTE: Since life proctoring is not supported anymore, we disable automated updates here
|
||||
|
||||
// Dispose from collecting room
|
||||
this.clientConnectionDAO
|
||||
.getAllForProctoringUpdateInactive()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(this::removeFromRoom);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to update proctoring collecting rooms: ", e);
|
||||
}
|
||||
// try {
|
||||
//
|
||||
// // Applying to collecting room
|
||||
// this.clientConnectionDAO
|
||||
// .getAllForProctoringUpdateActive()
|
||||
// .getOrThrow()
|
||||
// .forEach(this::assignToCollectingRoom);
|
||||
//
|
||||
// // 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)
|
||||
public void notifyExamDeletionEvent(final ExamDeletionEvent event) {
|
||||
event.ids.forEach(examId -> {
|
||||
try {
|
||||
|
||||
this.examAdminService
|
||||
.examForPK(examId)
|
||||
.flatMap(this::disposeRoomsForExam)
|
||||
.getOrThrow();
|
||||
// NOTE: Since life proctoring is not supported anymore, we disable automated updates here
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to delete depending proctoring data for exam: {}", examId, e);
|
||||
}
|
||||
});
|
||||
// event.ids.forEach(examId -> {
|
||||
// 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
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ExamFinishedEvent received, process disposeRoomsForExam...");
|
||||
}
|
||||
// NOTE: Since life proctoring is not supported anymore, we disable automated updates here
|
||||
|
||||
disposeRoomsForExam(event.exam)
|
||||
.onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error));
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("ExamFinishedEvent received, process disposeRoomsForExam...");
|
||||
// }
|
||||
//
|
||||
// disposeRoomsForExam(event.exam)
|
||||
// .onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -386,7 +386,7 @@ class ScreenProctoringAPIBinding {
|
|||
|
||||
void synchronizeUserAccounts(final Exam exam) {
|
||||
try {
|
||||
|
||||
final ScreenProctoringServiceOAuthTemplate apiTemplate = getAPITemplate(null);
|
||||
exam.supporter.forEach(userUUID -> synchronizeUserAccount(userUUID, apiTemplate));
|
||||
if (exam.owner != null) {
|
||||
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) {
|
||||
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()) {
|
||||
log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
|
||||
}
|
||||
|
@ -1170,27 +1182,44 @@ class ScreenProctoringAPIBinding {
|
|||
.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!");
|
||||
this.apiTemplateExam = new ScreenProctoringServiceOAuthTemplate(this, settings);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -284,6 +284,7 @@ public class ExamMonitoringController {
|
|||
.flatMap(authorization::checkModify)
|
||||
.flatMap(examSessionService::toggleTestRun)
|
||||
.map(exam -> {
|
||||
examSessionService.flushCache(exam);
|
||||
if (exam.status == Exam.ExamStatus.TEST_RUN) {
|
||||
applicationEventPublisher.publishEvent(new ExamStartedEvent(exam));
|
||||
} else if (exam.status == Exam.ExamStatus.UP_COMING) {
|
||||
|
|
|
@ -3772,236 +3772,237 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
@Autowired
|
||||
private ClientConnectionDAO clientConnectionDAO;
|
||||
|
||||
@Test
|
||||
@Order(28)
|
||||
// *************************************
|
||||
// Use Case 28: Login as admin and connect with SEBs to running exam with procotring enabled
|
||||
// - Get Exam (running)
|
||||
// - start some SEB clients connecting to running exam
|
||||
// - Check collecting rooms created
|
||||
public void testUsecase28_TestExamProctoring() throws IOException {
|
||||
|
||||
final RestServiceImpl restService = createRestServiceForUser(
|
||||
"admin",
|
||||
"admin",
|
||||
new GetExamPage(),
|
||||
new GetExamProctoringSettings(),
|
||||
new SaveExamProctoringSettings(),
|
||||
new IsTownhallRoomAvailable(),
|
||||
new GetCollectingRooms(),
|
||||
new GetClientConfigPage(),
|
||||
new ActivateClientConfig(),
|
||||
new NewClientConfig(),
|
||||
new GetClientConfig(),
|
||||
new GetProctorRoomConnection(),
|
||||
new GetCollectingRoomConnections(),
|
||||
new NotifyProctoringRoomOpened(),
|
||||
new SendProctoringReconfigurationAttributes(),
|
||||
new GetTownhallRoom(),
|
||||
new OpenTownhallRoom(),
|
||||
new CloseProctoringRoom());
|
||||
|
||||
// get exam
|
||||
final Result<Page<Exam>> exams = restService
|
||||
.getBuilder(GetExamPage.class)
|
||||
.call();
|
||||
|
||||
assertNotNull(exams);
|
||||
assertFalse(exams.hasError());
|
||||
final Page<Exam> examPage = exams.get();
|
||||
assertFalse(examPage.isEmpty());
|
||||
|
||||
final Exam runningExam = examPage.content
|
||||
.stream()
|
||||
.filter(exam -> exam.status == ExamStatus.RUNNING)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
assertNotNull(runningExam);
|
||||
assertTrue(runningExam.status == ExamStatus.RUNNING);
|
||||
|
||||
final Result<ProctoringServiceSettings> pSettings = restService
|
||||
.getBuilder(GetExamProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call();
|
||||
|
||||
assertNotNull(pSettings);
|
||||
assertFalse(pSettings.hasError());
|
||||
final ProctoringServiceSettings proctoringServiceSettings = pSettings.get();
|
||||
assertTrue(proctoringServiceSettings.enableProctoring);
|
||||
assertEquals("https://test.proc/service", proctoringServiceSettings.serverURL);
|
||||
|
||||
// start some SEB connections for this exam
|
||||
|
||||
// create SEB Client Config without password protection
|
||||
Result<SEBClientConfig> newConfigResponse = restService
|
||||
.getBuilder(NewClientConfig.class)
|
||||
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection")
|
||||
.withFormParam(SEBClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
|
||||
.withFormParam(SEBClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
|
||||
.withFormParam(SEBClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
|
||||
.withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
|
||||
.withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
|
||||
.withFormParam(SEBClientConfig.ATTR_CONFIG_PURPOSE, SEBClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
.call();
|
||||
|
||||
assertNotNull(newConfigResponse);
|
||||
assertFalse(newConfigResponse.hasError());
|
||||
final SEBClientConfig sebClientConfig = newConfigResponse.get();
|
||||
assertEquals("No Password Protection", sebClientConfig.name);
|
||||
assertFalse(sebClientConfig.isActive());
|
||||
assertEquals("http://fallback.com/fallback", sebClientConfig.fallbackStartURL);
|
||||
|
||||
// activate the new Client Configuration
|
||||
restService
|
||||
.getBuilder(ActivateClientConfig.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId())
|
||||
.call();
|
||||
|
||||
newConfigResponse = restService.getBuilder(GetClientConfig.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId())
|
||||
.call();
|
||||
|
||||
final SEBClientConfig clientConfig = newConfigResponse.get();
|
||||
assertTrue(clientConfig.isActive());
|
||||
final ClientCredentials credentials = this.sebClientConfigDAO
|
||||
.getSEBClientCredentials(clientConfig.getModelId())
|
||||
.getOrThrow();
|
||||
|
||||
assertTrue(clientConfig.isActive());
|
||||
|
||||
// simulate a SEB connection...
|
||||
try {
|
||||
new SEBClientBot(
|
||||
credentials.clientIdAsString(),
|
||||
this.cryptor.decrypt(credentials.secret).getOrThrow().toString(),
|
||||
runningExam.getModelId(),
|
||||
String.valueOf(runningExam.institutionId),
|
||||
false);
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
this.examProcotringRoomService.updateProctoringCollectingRooms();
|
||||
|
||||
// check collecting room was created
|
||||
final Collection<RemoteProctoringRoom> collectingRooms = restService
|
||||
.getBuilder(GetCollectingRooms.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call()
|
||||
.get();
|
||||
|
||||
assertNotNull(collectingRooms);
|
||||
assertFalse(collectingRooms.isEmpty());
|
||||
// Two rooms a two people for four connections
|
||||
assertEquals(2, collectingRooms.size());
|
||||
final RemoteProctoringRoom room1 = collectingRooms.iterator().next();
|
||||
assertEquals(2, room1.roomSize.intValue());
|
||||
assertFalse(room1.townhallRoom);
|
||||
|
||||
final ProctoringRoomConnection proctoringRoomConnection = restService
|
||||
.getBuilder(GetProctorRoomConnection.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name)
|
||||
.call()
|
||||
.get();
|
||||
|
||||
assertNotNull(proctoringRoomConnection);
|
||||
assertEquals(room1.name, proctoringRoomConnection.roomName);
|
||||
assertNotNull(proctoringRoomConnection.accessToken);
|
||||
|
||||
// notify room open
|
||||
restService
|
||||
.getBuilder(NotifyProctoringRoomOpened.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name)
|
||||
.call()
|
||||
.get();
|
||||
|
||||
// reconfigure clients in room
|
||||
restService
|
||||
.getBuilder(SendProctoringReconfigurationAttributes.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name)
|
||||
.withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, "true")
|
||||
.withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO, "true")
|
||||
.withQueryParam(API.EXAM_PROCTORING_ATTR_ALLOW_CHAT, "true")
|
||||
.call()
|
||||
.get();
|
||||
|
||||
final Collection<ClientConnection> collection = restService
|
||||
.getBuilder(GetCollectingRoomConnections.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name)
|
||||
.call()
|
||||
.get();
|
||||
|
||||
assertNotNull(collection);
|
||||
assertFalse(collection.isEmpty());
|
||||
assertEquals(2, collection.size());
|
||||
final ClientConnection connection = collection.iterator().next();
|
||||
assertEquals(runningExam.id, connection.examId);
|
||||
// this is because the Json model do not contian certain attributes due to performance
|
||||
assertNull(connection.remoteProctoringRoomId);
|
||||
// we can geht the room number by getting it directyl from the record
|
||||
final ClientConnection clientConnection = this.clientConnectionDAO.byPK(connection.id).get();
|
||||
assertNotNull(clientConnection.remoteProctoringRoomId);
|
||||
|
||||
// get and open townhall
|
||||
final String townhallActive = restService
|
||||
.getBuilder(IsTownhallRoomAvailable.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call()
|
||||
.get();
|
||||
|
||||
assertEquals("true", townhallActive);
|
||||
|
||||
// check no Townhallroom yet
|
||||
RemoteProctoringRoom townhallRoom = restService
|
||||
.getBuilder(GetTownhallRoom.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call()
|
||||
.get();
|
||||
assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom);
|
||||
|
||||
// open townhall room
|
||||
final ProctoringRoomConnection townhallRoomConntection = restService
|
||||
.getBuilder(OpenTownhallRoom.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call()
|
||||
.get();
|
||||
|
||||
assertNotNull(townhallRoomConntection);
|
||||
|
||||
// check Townhallroom is available yet
|
||||
townhallRoom = restService
|
||||
.getBuilder(GetTownhallRoom.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call()
|
||||
.get();
|
||||
assertTrue(townhallRoom.townhallRoom);
|
||||
assertEquals(townhallRoom.name, townhallRoomConntection.roomName);
|
||||
|
||||
// close townhall room
|
||||
restService
|
||||
.getBuilder(CloseProctoringRoom.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, townhallRoom.name)
|
||||
.call()
|
||||
.get();
|
||||
|
||||
townhallRoom = restService
|
||||
.getBuilder(GetTownhallRoom.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
.call()
|
||||
.get();
|
||||
assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom);
|
||||
|
||||
Thread.sleep(5000);
|
||||
|
||||
} catch (final Exception e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
// NOTE: Not supported anymore
|
||||
// @Test
|
||||
// @Order(28)
|
||||
// // *************************************
|
||||
// // Use Case 28: Login as admin and connect with SEBs to running exam with procotring enabled
|
||||
// // - Get Exam (running)
|
||||
// // - start some SEB clients connecting to running exam
|
||||
// // - Check collecting rooms created
|
||||
// public void testUsecase28_TestExamProctoring() throws IOException {
|
||||
//
|
||||
// final RestServiceImpl restService = createRestServiceForUser(
|
||||
// "admin",
|
||||
// "admin",
|
||||
// new GetExamPage(),
|
||||
// new GetExamProctoringSettings(),
|
||||
// new SaveExamProctoringSettings(),
|
||||
// new IsTownhallRoomAvailable(),
|
||||
// new GetCollectingRooms(),
|
||||
// new GetClientConfigPage(),
|
||||
// new ActivateClientConfig(),
|
||||
// new NewClientConfig(),
|
||||
// new GetClientConfig(),
|
||||
// new GetProctorRoomConnection(),
|
||||
// new GetCollectingRoomConnections(),
|
||||
// new NotifyProctoringRoomOpened(),
|
||||
// new SendProctoringReconfigurationAttributes(),
|
||||
// new GetTownhallRoom(),
|
||||
// new OpenTownhallRoom(),
|
||||
// new CloseProctoringRoom());
|
||||
//
|
||||
// // get exam
|
||||
// final Result<Page<Exam>> exams = restService
|
||||
// .getBuilder(GetExamPage.class)
|
||||
// .call();
|
||||
//
|
||||
// assertNotNull(exams);
|
||||
// assertFalse(exams.hasError());
|
||||
// final Page<Exam> examPage = exams.get();
|
||||
// assertFalse(examPage.isEmpty());
|
||||
//
|
||||
// final Exam runningExam = examPage.content
|
||||
// .stream()
|
||||
// .filter(exam -> exam.status == ExamStatus.RUNNING)
|
||||
// .findFirst()
|
||||
// .orElse(null);
|
||||
//
|
||||
// assertNotNull(runningExam);
|
||||
// assertTrue(runningExam.status == ExamStatus.RUNNING);
|
||||
//
|
||||
// final Result<ProctoringServiceSettings> pSettings = restService
|
||||
// .getBuilder(GetExamProctoringSettings.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call();
|
||||
//
|
||||
// assertNotNull(pSettings);
|
||||
// assertFalse(pSettings.hasError());
|
||||
// final ProctoringServiceSettings proctoringServiceSettings = pSettings.get();
|
||||
// assertTrue(proctoringServiceSettings.enableProctoring);
|
||||
// assertEquals("https://test.proc/service", proctoringServiceSettings.serverURL);
|
||||
//
|
||||
// // start some SEB connections for this exam
|
||||
//
|
||||
// // create SEB Client Config without password protection
|
||||
// Result<SEBClientConfig> newConfigResponse = restService
|
||||
// .getBuilder(NewClientConfig.class)
|
||||
// .withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection")
|
||||
// .withFormParam(SEBClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
|
||||
// .withFormParam(SEBClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
|
||||
// .withFormParam(SEBClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
|
||||
// .withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
|
||||
// .withFormParam(SEBClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
|
||||
// .withFormParam(SEBClientConfig.ATTR_CONFIG_PURPOSE, SEBClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
// .call();
|
||||
//
|
||||
// assertNotNull(newConfigResponse);
|
||||
// assertFalse(newConfigResponse.hasError());
|
||||
// final SEBClientConfig sebClientConfig = newConfigResponse.get();
|
||||
// assertEquals("No Password Protection", sebClientConfig.name);
|
||||
// assertFalse(sebClientConfig.isActive());
|
||||
// assertEquals("http://fallback.com/fallback", sebClientConfig.fallbackStartURL);
|
||||
//
|
||||
// // activate the new Client Configuration
|
||||
// restService
|
||||
// .getBuilder(ActivateClientConfig.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId())
|
||||
// .call();
|
||||
//
|
||||
// newConfigResponse = restService.getBuilder(GetClientConfig.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, sebClientConfig.getModelId())
|
||||
// .call();
|
||||
//
|
||||
// final SEBClientConfig clientConfig = newConfigResponse.get();
|
||||
// assertTrue(clientConfig.isActive());
|
||||
// final ClientCredentials credentials = this.sebClientConfigDAO
|
||||
// .getSEBClientCredentials(clientConfig.getModelId())
|
||||
// .getOrThrow();
|
||||
//
|
||||
// assertTrue(clientConfig.isActive());
|
||||
//
|
||||
// // simulate a SEB connection...
|
||||
// try {
|
||||
// new SEBClientBot(
|
||||
// credentials.clientIdAsString(),
|
||||
// this.cryptor.decrypt(credentials.secret).getOrThrow().toString(),
|
||||
// runningExam.getModelId(),
|
||||
// String.valueOf(runningExam.institutionId),
|
||||
// false);
|
||||
//
|
||||
// Thread.sleep(1000);
|
||||
//
|
||||
// this.examProcotringRoomService.updateProctoringCollectingRooms();
|
||||
//
|
||||
// // check collecting room was created
|
||||
// final Collection<RemoteProctoringRoom> collectingRooms = restService
|
||||
// .getBuilder(GetCollectingRooms.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// assertNotNull(collectingRooms);
|
||||
// assertFalse(collectingRooms.isEmpty());
|
||||
// // Two rooms a two people for four connections
|
||||
// assertEquals(2, collectingRooms.size());
|
||||
// final RemoteProctoringRoom room1 = collectingRooms.iterator().next();
|
||||
// assertEquals(2, room1.roomSize.intValue());
|
||||
// assertFalse(room1.townhallRoom);
|
||||
//
|
||||
// final ProctoringRoomConnection proctoringRoomConnection = restService
|
||||
// .getBuilder(GetProctorRoomConnection.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name)
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// assertNotNull(proctoringRoomConnection);
|
||||
// assertEquals(room1.name, proctoringRoomConnection.roomName);
|
||||
// assertNotNull(proctoringRoomConnection.accessToken);
|
||||
//
|
||||
// // notify room open
|
||||
// restService
|
||||
// .getBuilder(NotifyProctoringRoomOpened.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room1.name)
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// // reconfigure clients in room
|
||||
// restService
|
||||
// .getBuilder(SendProctoringReconfigurationAttributes.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name)
|
||||
// .withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, "true")
|
||||
// .withQueryParam(API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO, "true")
|
||||
// .withQueryParam(API.EXAM_PROCTORING_ATTR_ALLOW_CHAT, "true")
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// final Collection<ClientConnection> collection = restService
|
||||
// .getBuilder(GetCollectingRoomConnections.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, room1.name)
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// assertNotNull(collection);
|
||||
// assertFalse(collection.isEmpty());
|
||||
// assertEquals(2, collection.size());
|
||||
// final ClientConnection connection = collection.iterator().next();
|
||||
// assertEquals(runningExam.id, connection.examId);
|
||||
// // this is because the Json model do not contian certain attributes due to performance
|
||||
// assertNull(connection.remoteProctoringRoomId);
|
||||
// // we can geht the room number by getting it directyl from the record
|
||||
// final ClientConnection clientConnection = this.clientConnectionDAO.byPK(connection.id).get();
|
||||
// assertNotNull(clientConnection.remoteProctoringRoomId);
|
||||
//
|
||||
// // get and open townhall
|
||||
// final String townhallActive = restService
|
||||
// .getBuilder(IsTownhallRoomAvailable.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// assertEquals("true", townhallActive);
|
||||
//
|
||||
// // check no Townhallroom yet
|
||||
// RemoteProctoringRoom townhallRoom = restService
|
||||
// .getBuilder(GetTownhallRoom.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call()
|
||||
// .get();
|
||||
// assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom);
|
||||
//
|
||||
// // open townhall room
|
||||
// final ProctoringRoomConnection townhallRoomConntection = restService
|
||||
// .getBuilder(OpenTownhallRoom.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// assertNotNull(townhallRoomConntection);
|
||||
//
|
||||
// // check Townhallroom is available yet
|
||||
// townhallRoom = restService
|
||||
// .getBuilder(GetTownhallRoom.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call()
|
||||
// .get();
|
||||
// assertTrue(townhallRoom.townhallRoom);
|
||||
// assertEquals(townhallRoom.name, townhallRoomConntection.roomName);
|
||||
//
|
||||
// // close townhall room
|
||||
// restService
|
||||
// .getBuilder(CloseProctoringRoom.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, townhallRoom.name)
|
||||
// .call()
|
||||
// .get();
|
||||
//
|
||||
// townhallRoom = restService
|
||||
// .getBuilder(GetTownhallRoom.class)
|
||||
// .withURIVariable(API.PARAM_MODEL_ID, runningExam.getModelId())
|
||||
// .call()
|
||||
// .get();
|
||||
// assertEquals(RemoteProctoringRoom.NULL_ROOM, townhallRoom);
|
||||
//
|
||||
// Thread.sleep(5000);
|
||||
//
|
||||
// } catch (final Exception e) {
|
||||
// fail(e.getMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
@Order(29)
|
||||
|
|
Loading…
Reference in a new issue