Merge remote-tracking branch 'origin/dev-1.3' into development

Conflicts:
	src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InternalClientConnectionDataFactory.java
	src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
	src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java
This commit is contained in:
anhefti 2022-03-30 15:20:16 +02:00
commit 16582d0ce8
11 changed files with 202 additions and 152 deletions

View file

@ -25,9 +25,12 @@ import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
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.IndicatorValue;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ClientIndicator;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedIndicatorValueService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.PingIntervalClientIndicator; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.PingIntervalClientIndicator;
@Lazy @Lazy
@ -39,27 +42,103 @@ public class ClientIndicatorFactory {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final IndicatorDAO indicatorDAO; private final IndicatorDAO indicatorDAO;
private final DistributedIndicatorValueService distributedPingCache;
private final boolean distributedSetup;
private final boolean enableCaching; private final boolean enableCaching;
@Autowired @Autowired
public ClientIndicatorFactory( public ClientIndicatorFactory(
final ApplicationContext applicationContext, final ApplicationContext applicationContext,
final IndicatorDAO indicatorDAO, final IndicatorDAO indicatorDAO,
final DistributedIndicatorValueService distributedPingCache,
@Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup, @Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup,
@Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) { @Value("${sebserver.webservice.api.exam.enable-indicator-cache:true}") final boolean enableCaching) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.indicatorDAO = indicatorDAO; this.indicatorDAO = indicatorDAO;
this.distributedPingCache = distributedPingCache;
this.distributedSetup = distributedSetup;
this.enableCaching = distributedSetup ? false : enableCaching; this.enableCaching = distributedSetup ? false : enableCaching;
} }
public List<ClientIndicator> createFor(final ClientConnection clientConnection) { public void initializeDistributedCaches(final ClientConnection clientConnection) {
return createFor(clientConnection, false); try {
if (!this.distributedSetup || clientConnection.examId == null) {
return;
} }
public List<ClientIndicator> createFor( final Collection<Indicator> examIndicators = this.indicatorDAO
final ClientConnection clientConnection, .allForExam(clientConnection.examId)
final boolean enableCachingOverride) { .getOrThrow();
boolean pingIndicatorAvailable = false;
for (final Indicator indicatorDef : examIndicators) {
this.distributedPingCache.createIndicatorForConnection(
clientConnection.id,
indicatorDef.type,
indicatorDef.type == IndicatorType.LAST_PING ? Utils.getMillisecondsNow() : 0L);
if (!pingIndicatorAvailable) {
pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING;
}
}
// If there is no ping interval indicator set from the exam, we add a hidden one
// to at least create missing ping events and track missing state
if (!pingIndicatorAvailable) {
this.distributedPingCache.createIndicatorForConnection(
clientConnection.id,
IndicatorType.LAST_PING,
Utils.getMillisecondsNow());
}
} catch (final Exception e) {
log.error("Unexpected error while trying to initialize distributed indicator value cache for: {}",
clientConnection,
e);
}
}
public List<? extends IndicatorValue> getIndicatorValues(final ClientConnection clientConnection) {
final List<ClientIndicator> result = new ArrayList<>();
if (clientConnection.examId == null) {
return result;
}
try {
final Collection<Indicator> examIndicators = this.indicatorDAO
.allForExam(clientConnection.examId)
.getOrThrow();
for (final Indicator indicatorDef : examIndicators) {
try {
final ClientIndicator indicator = this.applicationContext
.getBean(indicatorDef.type.name(), ClientIndicator.class);
indicator.init(
indicatorDef,
clientConnection.id,
clientConnection.status.clientActiveStatus,
true);
result.add(indicator);
} catch (final Exception e) {
log.error("Failed to create IndicatorValue for indicator: {}", indicatorDef, e);
}
}
} catch (final Exception e) {
log.error("Failed to create IndicatorValues for ClientConnection: {}", clientConnection);
}
return Collections.unmodifiableList(result);
}
public List<ClientIndicator> createFor(final ClientConnection clientConnection) {
final List<ClientIndicator> result = new ArrayList<>(); final List<ClientIndicator> result = new ArrayList<>();
if (clientConnection.examId == null) { if (clientConnection.examId == null) {
@ -73,7 +152,6 @@ public class ClientIndicatorFactory {
.getOrThrow(); .getOrThrow();
boolean pingIndicatorAvailable = false; boolean pingIndicatorAvailable = false;
for (final Indicator indicatorDef : examIndicators) { for (final Indicator indicatorDef : examIndicators) {
try { try {
@ -88,11 +166,12 @@ public class ClientIndicatorFactory {
indicatorDef, indicatorDef,
clientConnection.id, clientConnection.id,
clientConnection.status.clientActiveStatus, clientConnection.status.clientActiveStatus,
this.enableCaching || enableCachingOverride); this.enableCaching);
result.add(indicator); result.add(indicator);
} catch (final Exception e) { } catch (final Exception e) {
log.warn("No Indicator with type: {} found as registered bean. Ignore this one.", indicatorDef.type, log.warn("No Indicator with type: {} found as registered bean. Ignore this one.",
indicatorDef.type,
e); e);
} }
} }
@ -117,7 +196,7 @@ public class ClientIndicatorFactory {
indicator, indicator,
clientConnection.id, clientConnection.id,
clientConnection.status.clientActiveStatus, clientConnection.status.clientActiveStatus,
this.enableCaching || enableCachingOverride); this.enableCaching);
result.add(pingIndicator); result.add(pingIndicator);
} }

View file

@ -148,9 +148,7 @@ public class ExamSessionCacheService {
if (clientConnection == null) { if (clientConnection == null) {
return null; return null;
} else { } else {
return this.internalClientConnectionDataFactory.createClientConnectionData( return this.internalClientConnectionDataFactory.createClientConnectionData(clientConnection);
clientConnection,
this.getRunningExam(clientConnection.examId) != null);
} }
} }

View file

@ -292,8 +292,8 @@ public class ExamSessionServiceImpl implements ExamSessionService {
throw new IllegalStateException("Missing exam identifier or requested exam is not running"); throw new IllegalStateException("Missing exam identifier or requested exam is not running");
} }
if (log.isDebugEnabled()) { if (log.isTraceEnabled()) {
log.debug("Trying to get exam from InMemorySEBConfig"); log.trace("Trying to get exam from InMemorySEBConfig");
} }
final InMemorySEBConfig sebConfigForExam = this.examSessionCacheService final InMemorySEBConfig sebConfigForExam = this.examSessionCacheService
@ -306,14 +306,14 @@ public class ExamSessionServiceImpl implements ExamSessionService {
try { try {
if (log.isDebugEnabled()) { if (log.isTraceEnabled()) {
log.debug("SEB exam configuration download request, start writing SEB exam configuration"); log.trace("SEB exam configuration download request, start writing SEB exam configuration");
} }
out.write(sebConfigForExam.getData()); out.write(sebConfigForExam.getData());
if (log.isDebugEnabled()) { if (log.isTraceEnabled()) {
log.debug("SEB exam configuration download request, finished writing SEB exam configuration"); log.trace("SEB exam configuration download request, finished writing SEB exam configuration");
} }
} catch (final IOException e) { } catch (final IOException e) {

View file

@ -32,17 +32,7 @@ public class InternalClientConnectionDataFactory {
this.sebClientNotificationService = sebClientNotificationService; this.sebClientNotificationService = sebClientNotificationService;
} }
public ClientConnectionDataInternal createClientConnectionData( public ClientConnectionDataInternal createClientConnectionData(final ClientConnection clientConnection) {
final ClientConnection clientConnection,
final boolean examRunning) {
// if the exam is not running, we just create a cached indicator anyways
if (!examRunning) {
return new ClientConnectionDataInternal(
clientConnection,
() -> false,
this.clientIndicatorFactory.createFor(clientConnection, true));
}
if (clientConnection.status == ConnectionStatus.CLOSED if (clientConnection.status == ConnectionStatus.CLOSED
|| clientConnection.status == ConnectionStatus.DISABLED) { || clientConnection.status == ConnectionStatus.DISABLED) {

View file

@ -74,9 +74,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
private final SEBClientConfigDAO sebClientConfigDAO; private final SEBClientConfigDAO sebClientConfigDAO;
private final SEBClientInstructionService sebInstructionService; private final SEBClientInstructionService sebInstructionService;
private final ExamAdminService examAdminService; private final ExamAdminService examAdminService;
private final ClientIndicatorFactory clientIndicatorFactory;
// TODO get rid of this dependency and use application events for signaling client connection state changes
private final DistributedIndicatorValueService distributedPingCache; private final DistributedIndicatorValueService distributedPingCache;
private final ClientIndicatorFactory clientIndicatorFactory;
private final boolean isDistributedSetup; private final boolean isDistributedSetup;
protected SEBClientConnectionServiceImpl( protected SEBClientConnectionServiceImpl(
@ -85,8 +84,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final SEBClientConfigDAO sebClientConfigDAO, final SEBClientConfigDAO sebClientConfigDAO,
final SEBClientInstructionService sebInstructionService, final SEBClientInstructionService sebInstructionService,
final ExamAdminService examAdminService, final ExamAdminService examAdminService,
final ClientIndicatorFactory clientIndicatorFactory, final DistributedIndicatorValueService distributedPingCache,
final DistributedIndicatorValueService distributedPingCache) { final ClientIndicatorFactory clientIndicatorFactory) {
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.examSessionCacheService = examSessionService.getExamSessionCacheService(); this.examSessionCacheService = examSessionService.getExamSessionCacheService();
@ -168,6 +167,11 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null)) null))
.getOrThrow(); .getOrThrow();
// initialize distributed indicator value caches if possible and needed
if (clientConnection.examId != null && this.isDistributedSetup) {
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
}
// load client connection data into cache // load client connection data into cache
final ClientConnectionDataInternal activeClientConnection = this.examSessionService final ClientConnectionDataInternal activeClientConnection = this.examSessionService
.getConnectionDataInternal(connectionToken); .getConnectionDataInternal(connectionToken);
@ -265,6 +269,11 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null)) null))
.getOrThrow(); .getOrThrow();
// initialize distributed indicator value caches if possible and needed
if (examId != null && this.isDistributedSetup) {
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
}
final ClientConnectionDataInternal activeClientConnection = final ClientConnectionDataInternal activeClientConnection =
reloadConnectionCache(connectionToken); reloadConnectionCache(connectionToken);
@ -405,6 +414,11 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
// check exam integrity for established connection // check exam integrity for established connection
checkExamIntegrity(establishedClientConnection.examId); checkExamIntegrity(establishedClientConnection.examId);
// initialize distributed indicator value caches if possible and needed
if (examId != null && this.isDistributedSetup) {
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
}
// if proctoring is enabled for exam, mark for room update // if proctoring is enabled for exam, mark for room update
if (proctoringEnabled) { if (proctoringEnabled) {
this.clientConnectionDAO.markForProctoringUpdate(updatedClientConnection.id); this.clientConnectionDAO.markForProctoringUpdate(updatedClientConnection.id);
@ -709,7 +723,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
public Result<ClientConnectionData> getIndicatorValues(final ClientConnection clientConnection) { public Result<ClientConnectionData> getIndicatorValues(final ClientConnection clientConnection) {
return Result.tryCatch(() -> new ClientConnectionData( return Result.tryCatch(() -> new ClientConnectionData(
clientConnection, clientConnection,
this.clientIndicatorFactory.createFor(clientConnection, true))); this.clientIndicatorFactory.getIndicatorValues(clientConnection)));
} }
private void checkExamRunning(final Long examId) { private void checkExamRunning(final Long examId) {
@ -879,13 +893,6 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
connection.getIndicatorMapping(EventType.ERROR_LOG) connection.getIndicatorMapping(EventType.ERROR_LOG)
.forEach(indicator -> indicator.notifyValueChange(clientEventRecord)); .forEach(indicator -> indicator.notifyValueChange(clientEventRecord));
} }
if (this.isDistributedSetup) {
// mark for update and flush the cache
this.clientConnectionDAO.save(connection.clientConnection);
this.examSessionCacheService.evictClientConnection(
connection.clientConnection.connectionToken);
}
} }
}; };
} }

View file

@ -69,38 +69,25 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
this.cachingEnabled = cachingEnabled; this.cachingEnabled = cachingEnabled;
if (!this.cachingEnabled && this.active) { if (!this.cachingEnabled && this.active) {
try {
this.ditributedIndicatorValueRecordId = this.ditributedIndicatorValueRecordId = this.distributedIndicatorValueService
this.distributedIndicatorValueService.initIndicatorForConnection( .getIndicatorForConnection(connectionId, getType());
connectionId,
getType(),
initValue());
} catch (final Exception e) {
tryRecoverIndicatorRecord();
}
} }
this.currentValue = computeValueAt(Utils.getMillisecondsNow()); this.currentValue = computeValueAt(Utils.getMillisecondsNow());
this.initialized = true; this.initialized = true;
} }
protected long initValue() {
return 0;
}
protected void tryRecoverIndicatorRecord() { protected void tryRecoverIndicatorRecord() {
this.ditributedIndicatorValueRecordId = this.distributedIndicatorValueService.getIndicatorForConnection(
if (log.isWarnEnabled()) {
log.warn("*** Missing indicator value record for connection: {}. Try to recover...", this.connectionId);
}
try {
this.ditributedIndicatorValueRecordId = this.distributedIndicatorValueService.initIndicatorForConnection(
this.connectionId, this.connectionId,
getType(), getType());
initValue());
} catch (final Exception e) { if (this.ditributedIndicatorValueRecordId == null) {
log.error("Failed to recover indicator value record for connection: {}", this.connectionId, e); log.warn("Failed to recover from missing indicator value cache record: {} type: {}",
this.connectionId,
getType());
} }
} }

View file

@ -129,70 +129,39 @@ public class DistributedIndicatorValueService implements DisposableBean {
} }
} }
/** This initializes a SEB client indicator on the persistent storage for a given SEB client /** This creates a distributed indicator value cache record for a given SEB connection and indicator
* connection identifier and of given IndicatorType. * if it not already exists and returns the PK for the specified distributed indicator value cache record
* If there is already such an indicator for the specified SEB client connection identifier and type,
* this returns the id of the existing one.
* *
* @param connectionId SEB client connection identifier * @param connectionId the client connection identifier
* @param type indicator type * @param type the indicator type
* @param value the initial indicator value * @param value the initialization value
* @return SEB client indicator value identifier (PK) */ * @return the PK of the created or existing distributed indicator value cache record or null when a unexpected
* error happened */
@Transactional @Transactional
public Long initIndicatorForConnection( public Long createIndicatorForConnection(
final Long connectionId, final Long connectionId,
final IndicatorType type, final IndicatorType type,
final Long value) { final long initValue) {
try { if (!this.webserviceInfo.isDistributed()) {
log.warn("No distributed setup, skip createIndicatorForConnection");
if (log.isDebugEnabled()) {
log.trace("*** Initialize indicator value record for SEB connection: {}", connectionId);
}
synchronized (this) {
Long recordId = null;
try {
recordId = this.clientIndicatorValueMapper
.indicatorRecordIdByConnectionId(connectionId, type);
} catch (final Exception e) {
// There is already more then one indicator record entry!!!
// delete the second one and work on with the first one
log.warn("Duplicate indicator entry detected for connectionId: {}, type: {} --> try to recover",
connectionId, type);
try {
final List<ClientIndicatorRecord> records = this.clientIndicatorRecordMapper.selectByExample()
.where(ClientIndicatorRecordDynamicSqlSupport.clientConnectionId,
isEqualTo(connectionId))
.and(ClientIndicatorRecordDynamicSqlSupport.type, isEqualTo(type.id))
.build()
.execute();
if (records.size() > 1) {
this.clientIndicatorRecordMapper.deleteByPrimaryKey(records.get(1).getId());
}
return records.get(0).getId();
} catch (final Exception ee) {
log.error("Failed to recover from duplicate indicator entry: ", ee);
return null;
}
}
if (recordId == null) {
if (!this.webserviceInfo.isMaster()) {
if (log.isDebugEnabled()) {
log.debug("Skip indicator record init because this is no master instance");
}
return null; return null;
} }
try {
// first check if the record already exists
final Long recId = this.clientIndicatorValueMapper.indicatorRecordIdByConnectionId(
connectionId,
type);
if (recId != null) {
log.debug("Distributed indicator value cache already exists for: {}, {}", connectionId, type);
return recId;
}
// if not, create new one and return PK
final ClientIndicatorRecord clientEventRecord = new ClientIndicatorRecord( final ClientIndicatorRecord clientEventRecord = new ClientIndicatorRecord(
null, connectionId, type.id, value); null, connectionId, type.id, initValue);
this.clientIndicatorRecordMapper.insert(clientEventRecord); this.clientIndicatorRecordMapper.insert(clientEventRecord);
try { try {
@ -211,18 +180,38 @@ public class DistributedIndicatorValueService implements DisposableBean {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException("Detected multiple client indicator value entries"); throw new RuntimeException("Detected multiple client indicator value entries");
} }
} catch (final Exception e) {
log.error(
"Failed to initialize distributed indicator value cache in persistent store. connectionId: {} type: {}",
connectionId, type, e);
return null;
}
} }
return recordId; /** Get the distributed indicator value cache record PK for a given SEB connection and indicator if available.
* If not existing for the specified connection and indicator this return null
*
* @param connectionId the client connection identifier
* @param type the indicator type
* @return the indicator value cache record PK or null of not defined */
@Transactional(readOnly = true)
public Long getIndicatorForConnection(final Long connectionId, final IndicatorType type) {
try {
return this.clientIndicatorValueMapper
.indicatorRecordIdByConnectionId(connectionId, type);
}
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to initialize indicator value for connection -> {}", connectionId, e); if (log.isDebugEnabled()) {
log.debug("Failed to get indicator PK for connection: {} type: {} cause: {}",
connectionId,
type,
e.getMessage());
}
// force rollback return null;
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException("Failed to initialize indicator value for connection -> " + connectionId, e);
} }
} }
@ -235,7 +224,7 @@ public class DistributedIndicatorValueService implements DisposableBean {
try { try {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("*** Delete indicator value record for SEB connection: {}", connectionId); log.debug("Delete indicator value record for SEB connection: {}", connectionId);
} }
final Collection<ClientIndicatorValueRecord> records = this.clientIndicatorValueMapper final Collection<ClientIndicatorValueRecord> records = this.clientIndicatorValueMapper

View file

@ -20,7 +20,6 @@ import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
@Lazy @Lazy
@ -40,11 +39,6 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
this.cachingEnabled = true; this.cachingEnabled = true;
} }
@Override
protected long initValue() {
return Utils.getMillisecondsNow();
}
@Override @Override
public void init( public void init(
final Indicator indicatorDefinition, final Indicator indicatorDefinition,

View file

@ -374,6 +374,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
examId, examId,
connectionToken, connectionToken,
e); e);
return null; return null;
} }
} }

View file

@ -555,6 +555,11 @@ public class ZoomProctoringService implements ExamProctoringService {
credentials, credentials,
roomName); roomName);
final int statusCodeValue = createUser.getStatusCodeValue();
if (statusCodeValue >= 400) {
throw new RuntimeException("Failed to create new Zoom user for room: " + createUser.getBody());
}
final UserResponse userResponse = this.jsonMapper.readValue( final UserResponse userResponse = this.jsonMapper.readValue(
createUser.getBody(), createUser.getBody(),
UserResponse.class); UserResponse.class);

View file

@ -55,7 +55,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
final String accessToken = super.obtainAccessToken("test", "test", "SEBClient"); final String accessToken = super.obtainAccessToken("test", "test", "SEBClient");
assertNotNull(accessToken); assertNotNull(accessToken);
final MockHttpServletResponse createConnection = super.createConnection(accessToken, 1L, null); final MockHttpServletResponse createConnection = super.createConnection(accessToken, 1L, 2L);
assertNotNull(createConnection); assertNotNull(createConnection);
// check correct response // check correct response
@ -79,7 +79,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
assertTrue(records.size() == 1); assertTrue(records.size() == 1);
final ClientConnectionRecord clientConnectionRecord = records.get(0); final ClientConnectionRecord clientConnectionRecord = records.get(0);
assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId())); assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId()));
assertNull(clientConnectionRecord.getExamId()); assertEquals("2", clientConnectionRecord.getExamId().toString());
assertEquals("CONNECTION_REQUESTED", String.valueOf(clientConnectionRecord.getStatus())); assertEquals("CONNECTION_REQUESTED", String.valueOf(clientConnectionRecord.getStatus()));
assertEquals(connectionToken, clientConnectionRecord.getConnectionToken()); assertEquals(connectionToken, clientConnectionRecord.getConnectionToken());
assertNotNull(clientConnectionRecord.getClientAddress()); assertNotNull(clientConnectionRecord.getClientAddress());
@ -321,7 +321,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
final String accessToken = super.obtainAccessToken("test", "test", "SEBClient"); final String accessToken = super.obtainAccessToken("test", "test", "SEBClient");
assertNotNull(accessToken); assertNotNull(accessToken);
final MockHttpServletResponse createConnection = super.createConnection(accessToken, 1L, 2L); final MockHttpServletResponse createConnection = super.createConnection(accessToken, 1L, null);
assertNotNull(createConnection); assertNotNull(createConnection);
final String connectionToken = createConnection.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); final String connectionToken = createConnection.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN);
@ -356,7 +356,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
// check correct stored (no changes) // check correct stored (no changes)
final List<ClientConnectionRecord> records = this.clientConnectionRecordMapper final List<ClientConnectionRecord> records = this.clientConnectionRecordMapper
.selectByExample() .selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.examId, SqlBuilder.isEqualTo(2L)) .where(ClientConnectionRecordDynamicSqlSupport.examId, SqlBuilder.isNull())
.build() .build()
.execute(); .execute();