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.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;

View file

@ -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)));

View file

@ -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 */

View file

@ -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)))

View file

@ -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);

View file

@ -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) {

View file

@ -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
);
}
}
}

View file

@ -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

View file

@ -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) {

View file

@ -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) {

View file

@ -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)