diff --git a/pom.xml b/pom.xml index c6709ec7901288cfac4654326cde1a459600b9b0..133905bc713ef8e64ffefe8688d2f3d2540ea8e2 100644 --- a/pom.xml +++ b/pom.xml @@ -1818,6 +1818,16 @@ <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> + <!-- + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>saxon9</artifactId> + </exclusion> + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>saxon9-dom</artifactId> + </exclusion> + --> </exclusions> </dependency> <dependency> @@ -1829,6 +1839,16 @@ <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> + <!-- + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>saxon9</artifactId> + </exclusion> + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>saxon9-dom</artifactId> + </exclusion> + --> </exclusions> </dependency> <dependency> @@ -1840,6 +1860,16 @@ <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> + <!-- + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>saxon9</artifactId> + </exclusion> + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>saxon9-dom</artifactId> + </exclusion> + --> </exclusions> </dependency> <dependency> diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml index b6b176d1bc59592edc8bbc952e5e6c120344d539..58e94283bac6ed2cceee8478f4a3bc4cb562bd27 100644 --- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml +++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml @@ -129,6 +129,7 @@ <class>org.olat.instantMessaging.model.InstantMessageNotificationImpl</class> <class>org.olat.ims.qti.statistics.model.QTIStatisticResult</class> <class>org.olat.ims.qti.statistics.model.QTIStatisticResultSet</class> + <class>org.olat.ims.qti21.model.UserTestSessionImpl</class> <class>org.olat.modules.qpool.model.PoolImpl</class> <class>org.olat.modules.qpool.model.PoolToItem</class> <class>org.olat.modules.qpool.model.PoolItemShortView</class> diff --git a/src/main/java/org/olat/ims/qti21/OnyxToQTIWorks.java b/src/main/java/org/olat/ims/qti21/OnyxToQTIWorks.java new file mode 100644 index 0000000000000000000000000000000000000000..8012be98bf825608fb893e5fbdd4307ca9cce72c --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/OnyxToQTIWorks.java @@ -0,0 +1,15 @@ +package org.olat.ims.qti21; + +/** + * + * + * //TODO <rubricBlock><div></div></rubricBlock>: add a div + * //TODO <prompt><p></p></prompt>: remove the p's + * + * Initial date: 11.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class OnyxToQTIWorks { + +} diff --git a/src/main/java/org/olat/ims/qti21/QTI21Module.java b/src/main/java/org/olat/ims/qti21/QTI21Module.java index 34c3470d56db8b9e05df9844c7bd64cd91d9d407..39b5e3dc34aeb4ced9cefe36a59c6e386638df66 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Module.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Module.java @@ -46,6 +46,8 @@ public class QTI21Module extends AbstractSpringModule { @Override public void init() { RepositoryHandlerFactory.registerHandler(assessmentHandler, 10); + //Saxon is mandatory, JQTI need XSLT 2.0 + //XsltFactoryUtilities.SAXON_TRANSFORMER_FACTORY_CLASS_NAME; } @Override diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java new file mode 100644 index 0000000000000000000000000000000000000000..000e3324386124963e8f88f38202cdd3c70864bd --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java @@ -0,0 +1,36 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21; + +import org.olat.core.id.Identity; +import org.olat.repository.RepositoryEntry; + +/** + * + * Initial date: 12.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface QTI21Service { + + + public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, Identity identity); + +} diff --git a/src/main/java/org/olat/ims/qti21/UserTestSession.java b/src/main/java/org/olat/ims/qti21/UserTestSession.java new file mode 100644 index 0000000000000000000000000000000000000000..85d9a1b4391b2951cd16b29ad00592a901aa0a7d --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/UserTestSession.java @@ -0,0 +1,33 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21; + +import org.olat.core.id.CreateInfo; +import org.olat.core.id.ModifiedInfo; + +/** + * + * Initial date: 12.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface UserTestSession extends CreateInfo, ModifiedInfo { + +} diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b0190ed7036622aa485f2987bb16f46ab102f308 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -0,0 +1,32 @@ +package org.olat.ims.qti21.manager; + +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.UserTestSession; +import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 12.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI21ServiceImpl implements QTI21Service { + + @Autowired + private DB dbInstance; + @Autowired + private TestSessionDAO testSessionDAO; + + + @Override + public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, Identity identity) { + return testSessionDAO.createTestSession(testEntry, courseEntry, identity); + } + + + + +} diff --git a/src/main/java/org/olat/ims/qti21/manager/TestSessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/TestSessionDAO.java new file mode 100644 index 0000000000000000000000000000000000000000..04127ba6a02ff78b0f1256b05b2d51bf4e8015fb --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/manager/TestSessionDAO.java @@ -0,0 +1,40 @@ +package org.olat.ims.qti21.manager; + +import java.util.Date; + +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.olat.ims.qti21.UserTestSession; +import org.olat.ims.qti21.model.UserTestSessionImpl; +import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 12.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class TestSessionDAO { + + @Autowired + private DB dbInstance; + + + public UserTestSession createTestSession(RepositoryEntry testEntry, RepositoryEntry courseEntry, Identity identity) { + UserTestSessionImpl testSession = new UserTestSessionImpl(); + Date now = new Date(); + testSession.setCreationDate(now); + testSession.setLastModified(now); + testSession.setTestEntry(testEntry); + testSession.setCourseEntry(courseEntry); + testSession.setAuthorMode(false); + testSession.setExploded(false); + testSession.setIdentity(identity); + dbInstance.getCurrentEntityManager().persist(testSession); + return testSession; + } + +} diff --git a/src/main/java/org/olat/ims/qti21/model/CandidateTestEventType.java b/src/main/java/org/olat/ims/qti21/model/CandidateTestEventType.java new file mode 100644 index 0000000000000000000000000000000000000000..5baa104543294c93fb2b6ab2d87a1203cadecfda --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/model/CandidateTestEventType.java @@ -0,0 +1,111 @@ +/* Copyright (c) 2012-2013, University of Edinburgh. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * * Neither the name of the University of Edinburgh nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * This software is derived from (and contains code from) QTItools and MathAssessEngine. + * QTItools is (c) 2008, University of Southampton. + * MathAssessEngine is (c) 2010, University of Edinburgh. + */ +package org.olat.ims.qti21.model; + +import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode; +import uk.ac.ed.ph.jqtiplus.node.test.TestPart; + +/** + * Encapsulates the test-specific events that can arise when delivering + * a test within a {@link CandidateSession} + * + * @author David McKain + */ +public enum CandidateTestEventType { + + /* NB: Observe maximum length for mapped column set in CandidateEvent */ + //1234567890123456789012345678901234567890 + + /** + * Test has been entered. + * <p> + * (In test having one {@link TestPart}, we also attempt + * to enter this automatically. Otherwise, the first {@link TestPart} has to be + * entered explicitly by the candidate via {@link #ADVANCE_TEST_PART}.) + */ + ENTER_TEST, + + /** + * Presentation of navigation menu for the current {@link TestPart}, when in nonlinear + * mode and while the {@link TestPart} is still interacting. + */ + SELECT_MENU, + + /** + * Ends the current {@link TestPart} and moves it into review state + */ + END_TEST_PART, + + /** + * Exits the current {@link TestPart} (if currently inside one), then advances to the next + * available {@link TestPart}, or exits the test if there are no more available. + */ + ADVANCE_TEST_PART, + + /** + * Exits the test, either after the combined {@link TestPart} and test feedback page + * (for single-part tests), or the standalone test feedback page for multi-part tests. + */ + EXIT_TEST, + + /** Selection of a particular item for interaction (in {@link NavigationMode#NONLINEAR}) */ + SELECT_ITEM, + + /** + * Finish interaction of item (in {@link NavigationMode#LINEAR}), moving to the next + * enterable item in the testPart. + */ + FINISH_ITEM, + + /** + * Finish interaction of item (in {@link NavigationMode#LINEAR}), with no further + * enterable items in the testPart, thus causing the current testPart to end. + */ + FINISH_FINAL_ITEM, + + /** Item Event within the currently-selected item */ + ITEM_EVENT, + + /** Return to Test Part review (while already in review state, i.e. after {@link #REVIEW_ITEM} */ + REVIEW_TEST_PART, + + /** Review of a particular item */ + REVIEW_ITEM, + + /** Solution of a particular item (in review state) */ + SOLUTION_ITEM, + + ; + +} diff --git a/src/main/java/org/olat/ims/qti21/model/UserTestSessionImpl.java b/src/main/java/org/olat/ims/qti21/model/UserTestSessionImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..310f7605169b5541bdaece612f97ead390d3e679 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/model/UserTestSessionImpl.java @@ -0,0 +1,213 @@ +package org.olat.ims.qti21.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.GenericGenerator; +import org.olat.basesecurity.IdentityImpl; +import org.olat.core.id.Identity; +import org.olat.core.id.Persistable; +import org.olat.ims.qti21.UserTestSession; +import org.olat.repository.RepositoryEntry; + +/** + * This a custom implementation of CandidateSession + * + * + * Initial date: 12.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="qtitestsession") +@Table(name="o_qti_test_session") +public class UserTestSessionImpl implements UserTestSession, Persistable { + + private static final long serialVersionUID = -6069133323360142500L; + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "hilo") + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) + private Date lastModified; + + @ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_entry", nullable=false, insertable=true, updatable=false) + private RepositoryEntry testEntry; + + @ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=true) + @JoinColumn(name="fk_course", nullable=true, insertable=true, updatable=false) + private RepositoryEntry courseEntry; + + + @ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false) + private Identity identity; + + /** Is this session running in author mode? (I.e. providing debugging information) */ + @Column(name="q_author_mode", nullable=false, insertable=true, updatable=true) + private boolean authorMode; + + /** + * Timestamp indicating when the session has been <strong>finished</strong>. + * This is a QTIWorks specific concept with the following meaning: + * <ul> + * <li> + * A test is marked as finished once the candidate gets to the end of the last + * enterable testPart. At this time, the outcome variables are finalised and will + * be sent back to the LTI TC (if appropriate). A test only finishes once. + * </li> + * <li> + * A standalone item is marked as finished once the item session ends. At this time, + * the outcome variables are sent back to the LTI TC (if appropriate). These variables + * are normally final, but it is currently possible for items to reopen. The session can + * finish again in this case. + * </li> + * </ul> + */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name="q_finish_time", nullable=true, insertable=true, updatable=true) + private Date finishTime; + + /** + * Timestamp indicating when the session has been terminated. Session termination can + * occur in two ways: + * <ul> + * <li>When the candidate naturally exits the session</li> + * <li>When the instructor explicitly terminates the session</li> + * </ul> + * Once terminated, a session is no longer available to the candidate. + */ + @Temporal(TemporalType.TIMESTAMP) + @Column(name="q_termination_time", nullable=true, insertable=true, updatable=true) + private Date terminationTime; + + /** + * Flag to indicate if this session blew up while running, either because + * the assessment was not runnable, or because of a logic error. + */ + @Column(name="q_exploded", nullable=false, insertable=true, updatable=true) + private boolean exploded; + + @Override + public Long getKey() { + return key; + } + + public void setKey(Long key) { + this.key = key; + } + + @Override + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public RepositoryEntry getTestEntry() { + return testEntry; + } + + public void setTestEntry(RepositoryEntry testEntry) { + this.testEntry = testEntry; + } + + public RepositoryEntry getCourseEntry() { + return courseEntry; + } + + public void setCourseEntry(RepositoryEntry courseEntry) { + this.courseEntry = courseEntry; + } + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + public boolean isAuthorMode() { + return authorMode; + } + + public void setAuthorMode(boolean authorMode) { + this.authorMode = authorMode; + } + + public boolean isExploded() { + return exploded; + } + + public void setExploded(boolean exploded) { + this.exploded = exploded; + } + + public Date getFinishTime() { + return finishTime; + } + + public void setFinishTime(Date finishTime) { + this.finishTime = finishTime; + } + + public Date getTerminationTime() { + return terminationTime; + } + + public void setTerminationTime(Date terminationTime) { + this.terminationTime = terminationTime; + } + + @Override + public int hashCode() { + return key == null ? -86534687 : key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof UserTestSessionImpl) { + UserTestSessionImpl session = (UserTestSessionImpl)obj; + return getKey() != null && getKey().equals(session.getKey()); + } + return false; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java index 1cdb66de645eac93ea420d17ffc1fd17821a533f..887e795413de13e7b1bcdcdfd7787381d289d3dc 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21DisplayController.java @@ -40,11 +40,14 @@ import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator; import org.olat.ims.qti21.QTI21ContentPackage; +import org.olat.ims.qti21.UserTestSession; import org.olat.ims.qti21.RequestTimestampContext; +import org.olat.ims.qti21.model.CandidateTestEventType; import org.olat.repository.RepositoryEntry; import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager; import uk.ac.ed.ph.jqtiplus.JqtiPlus; +import uk.ac.ed.ph.jqtiplus.exception.QtiCandidateStateException; import uk.ac.ed.ph.jqtiplus.node.AssessmentObjectType; import uk.ac.ed.ph.jqtiplus.node.result.AssessmentResult; import uk.ac.ed.ph.jqtiplus.notification.NotificationLevel; @@ -61,6 +64,7 @@ import uk.ac.ed.ph.jqtiplus.running.TestProcessingInitializer; import uk.ac.ed.ph.jqtiplus.running.TestSessionController; import uk.ac.ed.ph.jqtiplus.running.TestSessionControllerSettings; import uk.ac.ed.ph.jqtiplus.state.TestPlan; +import uk.ac.ed.ph.jqtiplus.state.TestPlanNode; import uk.ac.ed.ph.jqtiplus.state.TestPlanNodeKey; import uk.ac.ed.ph.jqtiplus.state.TestProcessingMap; import uk.ac.ed.ph.jqtiplus.state.TestSessionState; @@ -82,9 +86,12 @@ public class QTI21DisplayController extends FormBasicController { private QTI21FormItem qtiEl; private TestSessionController testSessionController; - - private RequestTimestampContext requestTimestampContext = new RequestTimestampContext(); + private JqtiExtensionManager jqtiExtensionManager = new JqtiExtensionManager(); + private RequestTimestampContext requestTimestampContext = new RequestTimestampContext(); + private CandidateSessionFinisher candidateSessionFinisher = new CandidateSessionFinisher(); + + private UserTestSession candidateSession; public QTI21DisplayController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { super(ureq, wControl, "run"); @@ -136,23 +143,173 @@ public class QTI21DisplayController extends FormBasicController { if(source == qtiEl) { if(event instanceof QTIWorksEvent) { QTIWorksEvent qe = (QTIWorksEvent)event; - switch(qe.getEvent()) { - case selectItem: doSelectItem(qe.getSubCommand()); break; - case testPartNavigation: doTestPartNavigation(); break; - case response: doResponse(qe.getStringResponseMap()); break; - case endTestPart: doEndTestPart(); break; - } + processQTIEvent(qe); } } super.formInnerEvent(ureq, source, event); } + private void processQTIEvent(QTIWorksEvent qe) { + switch(qe.getEvent()) { + case selectItem: + doSelectItem(qe.getSubCommand()); + break; + case finishItem: + doFinish(); + break; + case reviewItem: + doReviewItem(qe.getSubCommand()); + break; + case itemSolution: + doItemSolution(qe.getSubCommand()); + break; + case testPartNavigation: + doTestPartNavigation(); + break; + case response: + doResponse(qe.getStringResponseMap()); + break; + case endTestPart: + doEndTestPart(); + break; + case advanceTestPart: + doAdvanceTestPart(); + break; + case reviewTestPart: + doReviewTestPart(); + break; + case exitTest: + doExitTest(); + break; + } + } + private void doSelectItem(String key) { TestPlanNodeKey nodeKey = TestPlanNodeKey.fromString(key); Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); testSessionController.selectItemNonlinear(requestTimestamp, nodeKey); } + private void doReviewItem(String key) { + TestPlanNodeKey itemKey = TestPlanNodeKey.fromString(key); + Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); + + //Assert.notNull(candidateSessionContext, "candidateSessionContext"); + //Assert.notNull(itemKey, "itemKey"); + //assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST); + //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession(); + + /* Get current JQTI state and create JQTI controller */ + //final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO); + //final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession); + //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + + /* Make sure caller may do this */ + //assertSessionNotTerminated(candidateSession); + try { + if (!testSessionController.mayReviewItem(itemKey)) { + logError("CANNOT_REVIEW_TEST_ITEM", null); + //candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_REVIEW_TEST_ITEM); + return; + } + } catch (final QtiCandidateStateException e) { + logError("CANNOT_REVIEW_TEST_ITEM", e); + // candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_REVIEW_TEST_ITEM); + return; + } catch (final RuntimeException e) { + logError("CANNOT_REVIEW_TEST_ITEM", e); + return;// handleExplosion(e, candidateSession); + } + + /* Record current result state */ + //candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController); + + /* Record and log event */ + //final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession, + // CandidateTestEventType.REVIEW_ITEM, null, itemKey, testSessionState, notificationRecorder); + //candidateAuditLogger.logCandidateEvent(candidateTestEvent); + } + + private void doItemSolution(String key) { + TestPlanNodeKey itemKey = TestPlanNodeKey.fromString(key); + + //Assert.notNull(candidateSessionContext, "candidateSessionContext"); + //assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST); + //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession(); + //Assert.notNull(itemKey, "itemKey"); + + /* Get current JQTI state and create JQTI controller */ + final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO); + //final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession); + //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder); + //final TestSessionState testSessionState = testSessionController.getTestSessionState(); + + /* Make sure caller may do this */ + //assertSessionNotTerminated(candidateSession); + try { + if (!testSessionController.mayAccessItemSolution(itemKey)) { + //candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_SOLUTION_TEST_ITEM); + logError("CANNOT_SOLUTION_TEST_ITEM", null); + return; + } + } + catch (final QtiCandidateStateException e) { + //candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_SOLUTION_TEST_ITEM); + logError("CANNOT_SOLUTION_TEST_ITEM", e); + return; + } catch (final RuntimeException e) { + logError("Exploded", e); + return;// handleExplosion(e, candidateSession); + } + + /* Record current result state */ + //candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController); + + /* Record and log event */ + //final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession, + // CandidateTestEventType.SOLUTION_ITEM, null, itemKey, testSessionState, notificationRecorder); + //candidateAuditLogger.logCandidateEvent(candidateTestEvent); + + } + + //public CandidateSession finishLinearItem(final CandidateSessionContext candidateSessionContext) + // throws CandidateException { + private void doFinish() { + + try { + if (!testSessionController.mayAdvanceItemLinear()) { + logError("CANNOT_FINISH_LINEAR_TEST_ITEM", null); + return; + } + } catch (QtiCandidateStateException e) { + logError("CANNOT_FINISH_LINEAR_TEST_ITEM", e); + return; + } catch (RuntimeException e) { + logError("CANNOT_FINISH_LINEAR_TEST_ITEM", e); + //return handleExplosion(e, candidateSession); + } + + // Update state + final Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); + final TestPlanNode nextItemNode = testSessionController.advanceItemLinear(requestTimestamp); + + // Record current result state + //final AssessmentResult assessmentResult = candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController); + + /* If we ended the testPart and there are now no more available testParts, then finish the session now */ + if (nextItemNode==null && testSessionController.findNextEnterableTestPart()==null) { + //candidateSessionFinisher.finishCandidateSession(candidateSession, assessmentResult); + } + + /* Record and log event + final CandidateTestEventType eventType = nextItemNode!=null ? CandidateTestEventType.FINISH_ITEM : CandidateTestEventType.FINISH_FINAL_ITEM; + final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession, + eventType, null, testSessionState, notificationRecorder); + */ + + } + private void doTestPartNavigation() { final Date requestTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); testSessionController.selectItemNonlinear(requestTimestamp, null); @@ -193,6 +350,145 @@ public class QTI21DisplayController extends FormBasicController { testSessionController.endCurrentTestPart(requestTimestamp); } + private void doAdvanceTestPart() { + + //final CandidateSessionContext candidateSessionContext = getCandidateSessionContext(); + //Assert.notNull(candidateSessionContext, "candidateSessionContext"); + //assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST); + //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession(); + + /* Get current JQTI state and create JQTI controller */ + //final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO); + //final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + + /* Perform action */ + final TestPlanNode nextTestPart; + final Date currentTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); + try { + nextTestPart = testSessionController.enterNextAvailableTestPart(currentTimestamp); + } catch (final QtiCandidateStateException e) { + logError("CANNOT_ADVANCE_TEST_PART", e); + return; + } catch (final RuntimeException e) { + logError("RuntimeException", e); + return;// handleExplosion(e, candidateSession); + } + + CandidateTestEventType eventType; + if (nextTestPart!=null) { + /* Moved into next test part */ + eventType = CandidateTestEventType.ADVANCE_TEST_PART; + } + else { + /* No more test parts. + * + * For single part tests, we terminate the test completely now as the test feedback was shown with the testPart feedback. + * For multi-part tests, we shall keep the test open so that the test feedback can be viewed. + */ + if (testSessionState.getTestPlan().getTestPartNodes().size()==1) { + eventType = CandidateTestEventType.EXIT_TEST; + testSessionController.exitTest(currentTimestamp); + //candidateSession.setTerminationTime(currentTimestamp); + //candidateSessionDao.update(candidateSession); + } + else { + eventType = CandidateTestEventType.ADVANCE_TEST_PART; + } + } + + /* Record current result state */ + //candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController); + + /* Record and log event */ + //final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession, + // eventType, testSessionState, notificationRecorder); + //candidateAuditLogger.logCandidateEvent(candidateTestEvent); + + + + /* + String redirect; + if (candidateSession.isTerminated()) { + // We exited the test + //TODO fire event eXIT + redirect = redirectToExitUrl(candidateSessionContext, xsrfToken); + } + else { + // Moved onto next part + redirect = redirectToRenderSession(xid, xsrfToken); + } + */ + + } + + private void doReviewTestPart() { + //Assert.notNull(candidateSessionContext, "candidateSessionContext"); + //assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST); + //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession(); + + /* Get current JQTI state and create JQTI controller */ + final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO); + //final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession); + //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + + /* Make sure caller may do this */ + //assertSessionNotTerminated(candidateSession); + if (testSessionState.getCurrentTestPartKey()==null || !testSessionState.getCurrentTestPartSessionState().isEnded()) { + + // candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_REVIEW_TEST_PART); + logError("CANNOT_REVIEW_TEST_PART", null); + return; + } + + /* Record and log event */ + //final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession, + // CandidateTestEventType.REVIEW_TEST_PART, null, null, testSessionState, notificationRecorder); + //candidateAuditLogger.logCandidateEvent(candidateTestEvent); + } + + /** + * Exit multi-part tests + */ + private void doExitTest() { + //Assert.notNull(candidateSessionContext, "candidateSessionContext"); + //assertSessionType(candidateSessionContext, AssessmentObjectType.ASSESSMENT_TEST); + //final CandidateSession candidateSession = candidateSessionContext.getCandidateSession(); + + /* Get current JQTI state and create JQTI controller */ + final NotificationRecorder notificationRecorder = new NotificationRecorder(NotificationLevel.INFO); + //final CandidateEvent mostRecentEvent = assertSessionEntered(candidateSession); + //final TestSessionController testSessionController = candidateDataService.createTestSessionController(mostRecentEvent, notificationRecorder); + final TestSessionState testSessionState = testSessionController.getTestSessionState(); + + /* Perform action */ + final Date currentTimestamp = requestTimestampContext.getCurrentRequestTimestamp(); + try { + testSessionController.exitTest(currentTimestamp); + } catch (final QtiCandidateStateException e) { + //candidateAuditLogger.logAndThrowCandidateException(candidateSession, CandidateExceptionReason.CANNOT_EXIT_TEST); + logError("CANNOT_EXIT_TEST", null); + return; + } catch (final RuntimeException e) { + logError("Exploded", null); + return;// handleExplosion(e, candidateSession); + } + + /* Update CandidateSession as appropriate */ + //candidateSession.setTerminationTime(currentTimestamp); + //candidateSessionDao.update(candidateSession); + + /* Record current result state (final) */ + //candidateDataService.computeAndRecordTestAssessmentResult(candidateSession, testSessionController); + + /* Record and log event */ + //final CandidateEvent candidateTestEvent = candidateDataService.recordCandidateTestEvent(candidateSession, + // CandidateTestEventType.EXIT_TEST, testSessionState, notificationRecorder); + //candidateAuditLogger.logCandidateEvent(candidateTestEvent); + + } + //private CandidateSession enterCandidateSession(final CandidateSession candidateSession) private TestSessionController enterSession() { /* Set up listener to record any notifications */ @@ -329,4 +625,23 @@ public class QTI21DisplayController extends FormBasicController { final int requestedLimitIntValue = requestedLimit.intValue(); return requestedLimitIntValue > 0 ? requestedLimitIntValue : JqtiPlus.DEFAULT_TEMPLATE_PROCESSING_LIMIT; } + + private class CandidateSessionFinisher { + + + public void finishCandidateSession(/* final CandidateSession candidateSession,*/ final AssessmentResult assessmentResult) { + /* Mark session as finished */ + //candidateSession.setFinishTime(requestTimestampContext.getCurrentRequestTimestamp()); + + /* Also nullify LIS result info for session. These will be updated later, if pre-conditions match for sending the result back */ + //candidateSession.setLisOutcomeReportingStatus(null); + //candidateSession.setLisScore(null); + // candidateSessionDao.update(candidateSession); + + /* Finally schedule LTI result return (if appropriate and sane) */ + //maybeScheduleLtiOutcomes(candidateSession, assessmentResult); + } + + + } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java b/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java index d88056a62881e74f1cf4e878f9ffcfa1b5d8e1f7..7013804ad8a78b2fe9fbe24212dfbce1f21b6281 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21FormItem.java @@ -113,6 +113,17 @@ public class QTI21FormItem extends FormItemImpl { String sub = uri.substring(selectItem.getPath().length()); QTIWorksEvent event = new QTIWorksEvent(selectItem, sub, this); getRootForm().fireFormEvent(ureq, event); + } else if(uri.startsWith(finishItem.getPath())) { + QTIWorksEvent event = new QTIWorksEvent(finishItem, this); + getRootForm().fireFormEvent(ureq, event); + } else if(uri.startsWith(reviewItem.getPath())) { + String sub = uri.substring(reviewItem.getPath().length()); + QTIWorksEvent event = new QTIWorksEvent(reviewItem, sub, this); + getRootForm().fireFormEvent(ureq, event); + } else if(uri.startsWith(itemSolution.getPath())) { + String sub = uri.substring(itemSolution.getPath().length()); + QTIWorksEvent event = new QTIWorksEvent(itemSolution, sub, this); + getRootForm().fireFormEvent(ureq, event); } else if(uri.startsWith(testPartNavigation.getPath())) { QTIWorksEvent event = new QTIWorksEvent(testPartNavigation, this); getRootForm().fireFormEvent(ureq, event); @@ -124,6 +135,16 @@ public class QTI21FormItem extends FormItemImpl { } else if(uri.startsWith(endTestPart.getPath())) { QTIWorksEvent event = new QTIWorksEvent(endTestPart, this); getRootForm().fireFormEvent(ureq, event); + } else if(uri.startsWith(advanceTestPart.getPath())) { + QTIWorksEvent event = new QTIWorksEvent(advanceTestPart, this); + getRootForm().fireFormEvent(ureq, event); + } else if(uri.startsWith(reviewTestPart.getPath())) { + QTIWorksEvent event = new QTIWorksEvent(reviewTestPart, this); + getRootForm().fireFormEvent(ureq, event); + } else if(uri.startsWith(exitTest.getPath())) { + QTIWorksEvent event = new QTIWorksEvent(exitTest, this); + getRootForm().fireFormEvent(ureq, event); + } } diff --git a/src/main/java/org/olat/ims/qti21/ui/QTIWorksEvent.java b/src/main/java/org/olat/ims/qti21/ui/QTIWorksEvent.java index bc7558627dc0d97967507b7f75c45853f6f92cdb..a1890aac075de62f9a1e478ce08f0b857ce6986e 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTIWorksEvent.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTIWorksEvent.java @@ -18,6 +18,40 @@ public class QTIWorksEvent extends FormEvent { private static final long serialVersionUID = 7767258131971848645L; + public enum Event { + source("source", "source"), + state("state", "state"), + result("result", "result"), + validation("validation", "validation"), + authorview("author-view", "author-view"), + response("response", "response"),//OK + testPartNavigation("test-part-navigation", "test-part-navigation"),//OK + selectItem("select-item", "select-item/"),//OK + finishItem("finish-item", "finish-item"),//impl + reviewTestPart("review-test-part", "review-test-part"),//impl + reviewItem("review-item", "review-item/"),//impl + itemSolution("item-solution", "item-solution/"), + endTestPart("end-test-part", "end-test-part"),//OK + advanceTestPart("advance-test-part", "advance-test-part"),//OK + exitTest("exit-test", "exit-test");//impl + + private final String path; + private final String event; + + private Event(String event, String path) { + this.event = event; + this.path = path; + } + + public String getPath() { + return path; + } + + public String event() { + return event; + } + } + private final Event event; private final String subCommand; private final Map<Identifier, StringResponseData> stringResponseMap; @@ -53,38 +87,5 @@ public class QTIWorksEvent extends FormEvent { return stringResponseMap; } - public enum Event { - source("source", "source"), - state("state", "state"), - result("result", "result"), - validation("validation", "validation"), - authorview("author-view", "author-view"), - response("response", "response"),//OK - testPartNavigation("test-part-navigation", "test-part-navigation"),//OK - selectItem("select-item", "select-item/"),//OK - finishItem("finish-item", "finish-item"), - reviewTestPart("review-test-part", "review-test-part"), - reviewItem("review-item", "review-item"), - itemSolution("item-solution", "item-solution"), - endTestPart("end-test-part", "end-test-part"),//OK - advanceTestPart("advance-test-part", "advance-test-part"), - exitTest("exit-test", "exit-test") - ; - - private final String path; - private final String event; - - private Event(String event, String path) { - this.event = event; - this.path = path; - } - public String getPath() { - return path; - } - - public String event() { - return event; - } - } } diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/run.html b/src/main/java/org/olat/ims/qti21/ui/_content/run.html index ac2ba9b1cf2a96d8c89efab780e20e579cddeda7..3c89670ed09a79d10685ccaae05182b50507d2b7 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_content/run.html +++ b/src/main/java/org/olat/ims/qti21/ui/_content/run.html @@ -1,2 +1 @@ -<h1>Run</h1> $r.render("qtirun") \ No newline at end of file diff --git a/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql b/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql new file mode 100644 index 0000000000000000000000000000000000000000..9439252ea3cc248632fa661fa95dee68ae71c7a7 --- /dev/null +++ b/src/main/resources/database/mysql/alter_10_x_0_to_11_0_0.sql @@ -0,0 +1,20 @@ +create table o_qti_test_session ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + q_exploded bit not null default 0, + q_author_mode bit not null default 0, + q_finish_time datetime, + q_termination_time datetime, + fk_identity bigint not null, + fk_entry bigint not null, + fk_course bigint, + primary key (id) +); +alter table o_qti_test_session ENGINE = InnoDB; + +alter table o_qti_test_session add constraint qti_sess_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +alter table o_qti_test_session add constraint qti_sess_to_course_entry_idx foreign key (fk_course) references o_repositoryentry (repositoryentry_id); +alter table o_qti_test_session add constraint qti_sess_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); + + diff --git a/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql b/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql new file mode 100644 index 0000000000000000000000000000000000000000..fff55f2c2f3d86008e6f09d0d65610117bfb5b87 --- /dev/null +++ b/src/main/resources/database/postgresql/alter_10_x_0_to_11_0_0.sql @@ -0,0 +1,20 @@ +create table o_qti_test_session ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + q_exploded bool default false, + q_author_mode bool default false, + q_finish_time timestamp, + q_termination_time timestamp, + fk_identity int8 not null, + fk_entry int8 not null, + fk_course int8, + primary key (id) +); + +alter table o_qti_test_session add constraint qti_sess_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_testess_to_repo_entry_idx on o_qti_test_session (fk_entry); +alter table o_qti_test_session add constraint qti_sess_to_course_entry_idx foreign key (fk_course) references o_repositoryentry (repositoryentry_id); +create index idx_qti_sess_to_course_entry_idx on o_qti_test_session (fk_course); +alter table o_qti_test_session add constraint qti_sess_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); +create index idx_qti_sess_to_identity_idx on o_qti_test_session (fk_identity); \ No newline at end of file diff --git a/src/main/resources/rendering-xslt/test-testpart-feedback.xsl b/src/main/resources/rendering-xslt/test-testpart-feedback.xsl index ea740719b71feba7fda7dd0fc07628793c3c7c57..4c754d6e87b9e445198ae76b115af1e8f16c3f01 100644 --- a/src/main/resources/rendering-xslt/test-testpart-feedback.xsl +++ b/src/main/resources/rendering-xslt/test-testpart-feedback.xsl @@ -108,7 +108,7 @@ Renders the test(Part) feedback <xsl:if test="$currentTestPart/@navigationMode='nonlinear' or exists($itemSessionState/@entryTime)"> <li class="assessmentItem"> <form action="{$webappContextPath}{$reviewTestItemUrl}/{@key}" method="post"> - <button type="submit"> + <button type="submit" class="btn btn-default"> <xsl:if test="not($reviewable)"> <xsl:attribute name="disabled" select="'disabled'"/> </xsl:if> diff --git a/src/main/resources/rendering-xslt/test-testpart-navigation.xsl b/src/main/resources/rendering-xslt/test-testpart-navigation.xsl index 61a7057a7edd3a9d220a257e2bc808b41e5dafd6..05cab1481c7d8b8686a7311eba796963ee256faf 100644 --- a/src/main/resources/rendering-xslt/test-testpart-navigation.xsl +++ b/src/main/resources/rendering-xslt/test-testpart-navigation.xsl @@ -89,7 +89,7 @@ Renders the navigation for the current testPart <xsl:variable name="itemSessionState" select="$testSessionState/qw:item[@key=current()/@key]/qw:itemSessionState" as="element(qw:itemSessionState)"/> <li class="assessmentItem"> <form action="{$webappContextPath}{$selectTestItemUrl}/{@key}" method="post"> - <button type="submit"> + <button type="submit" class="btn btn-default"> <span class="questionTitle"><xsl:value-of select="@sectionPartTitle"/></span> <xsl:apply-templates select="$itemSessionState" mode="item-status"/> </button> diff --git a/src/test/java/org/olat/ims/qti21/manager/TestSessionDAOTest.java b/src/test/java/org/olat/ims/qti21/manager/TestSessionDAOTest.java new file mode 100644 index 0000000000000000000000000000000000000000..57aae8f9c000c6fdaabbe889e6fb23e7a787c128 --- /dev/null +++ b/src/test/java/org/olat/ims/qti21/manager/TestSessionDAOTest.java @@ -0,0 +1,38 @@ +package org.olat.ims.qti21.manager; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.olat.ims.qti21.UserTestSession; +import org.olat.repository.RepositoryEntry; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 12.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class TestSessionDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private TestSessionDAO testSessionDao; + + @Test + public void createTestSession() { + // prepare a test and a user + RepositoryEntry testEntry = JunitTestHelper.createAndPersistRepositoryEntry(); + Identity assessedIdentity = JunitTestHelper.createAndPersistIdentityAsRndUser("test-session-1"); + dbInstance.commit(); + + UserTestSession testSession = testSessionDao.createTestSession(testEntry, null, assessedIdentity); + Assert.assertNotNull(testSession); + dbInstance.commit(); + } + +}