From f4c771dcb471d8d19b725d76dea64b2699a78f75 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 29 May 2015 12:07:40 +0200
Subject: [PATCH] no-jira: tests to generate imsmanifest, assessment-item and
 assessment-test programmatically

---
 pom.xml                                       |   5 +
 .../persistence/_spring/core_persistence.xml  |   2 +-
 .../org/olat/ims/qti21/QTI21Constants.java    |   4 +
 .../java/org/olat/ims/qti21/QTI21Service.java |   2 +-
 .../org/olat/ims/qti21/manager/EventDAO.java  |   2 +-
 .../ims/qti21/manager/QTI21ServiceImpl.java   |   2 +-
 .../olat/ims/qti21/manager/SessionDAO.java    |   2 +-
 .../qti21/model/{ => jpa}/CandidateEvent.java |   5 +-
 .../model/{ => jpa}/UserTestSessionImpl.java  |   2 +-
 .../model/xml/AssessmentItemPackage.java      |  14 +
 .../model/xml/AssessmentTestPackage.java      |   5 +
 .../ims/qti21/model/xml/ManifestPackage.java  |  87 +++++
 .../handlers/QTI21AssessmentTestHandler.java  |  53 ++-
 .../ui/AssessmentItemDisplayController.java   |   2 +-
 .../ui/AssessmentTestDisplayController.java   |   2 +-
 .../ims/qti21/ui/CandidateSessionContext.java |   2 +-
 .../AssessmentItemComponentRenderer.java      |   2 +-
 .../AssessmentTestComponentRenderer.java      |   2 +-
 .../AssessmentItemEditorController.java       |  56 ++-
 .../editor/SingleChoiceEditorController.java  |  38 +++
 .../ui/editor/UnkownItemEditorController.java |  38 +++
 .../_content/assessment_item_editor.html      |   4 +-
 .../editor/_i18n/LocalStrings_de.properties   |   3 +
 .../editor/_i18n/LocalStrings_en.properties   |   3 +
 .../model/xml/AssessmentItemPackageTest.java  | 319 ++++++++++++++++++
 .../model/xml/AssessmentTestPackageTest.java  | 151 +++++++++
 .../qti21/model/xml/ManifestPackageTest.java  |  46 +++
 .../xml/assessment-item-single-choice.xml     |  67 ++++
 .../QTI21AssessmentTestHandlerTest.java       |  15 +
 29 files changed, 914 insertions(+), 21 deletions(-)
 rename src/main/java/org/olat/ims/qti21/model/{ => jpa}/CandidateEvent.java (92%)
 rename src/main/java/org/olat/ims/qti21/model/{ => jpa}/UserTestSessionImpl.java (99%)
 create mode 100644 src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemPackage.java
 create mode 100644 src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestPackage.java
 create mode 100644 src/main/java/org/olat/ims/qti21/model/xml/ManifestPackage.java
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/editor/SingleChoiceEditorController.java
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/editor/UnkownItemEditorController.java
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties
 create mode 100644 src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java
 create mode 100644 src/test/java/org/olat/ims/qti21/model/xml/AssessmentTestPackageTest.java
 create mode 100644 src/test/java/org/olat/ims/qti21/model/xml/ManifestPackageTest.java
 create mode 100644 src/test/java/org/olat/ims/qti21/model/xml/assessment-item-single-choice.xml
 create mode 100644 src/test/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandlerTest.java

diff --git a/pom.xml b/pom.xml
index 133905bc713..db18db2d939 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1872,6 +1872,11 @@
 				-->
 			</exclusions>
 		</dependency>
+		<dependency>
+			<groupId>org.openolat.imscp</groupId>
+			<artifactId>manifest</artifactId>
+			<version>1.1-SNAPSHOT</version>
+		</dependency>
 		<dependency>
 			<groupId>rome</groupId>
 			<artifactId>rome</artifactId>
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 b36eaa7ebfc..e4f0bb7550b 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
@@ -130,7 +130,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.ims.qti21.model.jpa.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/QTI21Constants.java b/src/main/java/org/olat/ims/qti21/QTI21Constants.java
index 42081da4fe8..5c90dddbe1e 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Constants.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Constants.java
@@ -29,6 +29,10 @@ import uk.ac.ed.ph.jqtiplus.types.Identifier;
  */
 public class QTI21Constants {
 	
+	public static final String TOOLNAME = "OpenOLAT";
+	
+	public static final String TOOLVERSION = "v1.0";
+	
 	public static final String SCORE = "SCORE";
 	
 	public static final Identifier SCORE_IDENTIFIER = Identifier.assumedLegal(SCORE);
diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java
index 9b4d0917c36..22850a07ef9 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Service.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java
@@ -26,9 +26,9 @@ import java.util.List;
 
 import org.olat.basesecurity.IdentityRef;
 import org.olat.core.id.Identity;
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 
diff --git a/src/main/java/org/olat/ims/qti21/manager/EventDAO.java b/src/main/java/org/olat/ims/qti21/manager/EventDAO.java
index 579f969649f..45d473469e3 100644
--- a/src/main/java/org/olat/ims/qti21/manager/EventDAO.java
+++ b/src/main/java/org/olat/ims/qti21/manager/EventDAO.java
@@ -19,9 +19,9 @@
  */
 package org.olat.ims.qti21.manager;
 
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.springframework.stereotype.Service;
 import org.w3c.dom.Document;
 
diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
index b073af080b5..8c52fe80606 100644
--- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
+++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
@@ -41,9 +41,9 @@ import org.olat.ims.qti21.QTI21ContentPackage;
 import org.olat.ims.qti21.QTI21Module;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.UserTestSession;
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java
index 2f4b76e6026..1de61e3171f 100644
--- a/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java
+++ b/src/main/java/org/olat/ims/qti21/manager/SessionDAO.java
@@ -26,7 +26,7 @@ import org.olat.basesecurity.IdentityRef;
 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.ims.qti21.model.jpa.UserTestSessionImpl;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/main/java/org/olat/ims/qti21/model/CandidateEvent.java b/src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java
similarity index 92%
rename from src/main/java/org/olat/ims/qti21/model/CandidateEvent.java
rename to src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java
index 954d07768e5..4420515701a 100644
--- a/src/main/java/org/olat/ims/qti21/model/CandidateEvent.java
+++ b/src/main/java/org/olat/ims/qti21/model/jpa/CandidateEvent.java
@@ -17,10 +17,13 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.ims.qti21.model;
+package org.olat.ims.qti21.model.jpa;
 
 import java.util.Date;
 
+import org.olat.ims.qti21.model.CandidateItemEventType;
+import org.olat.ims.qti21.model.CandidateTestEventType;
+
 /**
  * 
  * Initial date: 19.05.2015<br>
diff --git a/src/main/java/org/olat/ims/qti21/model/UserTestSessionImpl.java b/src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java
similarity index 99%
rename from src/main/java/org/olat/ims/qti21/model/UserTestSessionImpl.java
rename to src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java
index 82232b9c418..9e9ddfd7222 100644
--- a/src/main/java/org/olat/ims/qti21/model/UserTestSessionImpl.java
+++ b/src/main/java/org/olat/ims/qti21/model/jpa/UserTestSessionImpl.java
@@ -17,7 +17,7 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.ims.qti21.model;
+package org.olat.ims.qti21.model.jpa;
 
 import java.util.Date;
 
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemPackage.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemPackage.java
new file mode 100644
index 00000000000..3f68c1793b9
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemPackage.java
@@ -0,0 +1,14 @@
+package org.olat.ims.qti21.model.xml;
+
+import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
+
+public class AssessmentItemPackage {
+	
+	public ResolvedAssessmentItem createSingleChoice() {
+		
+		ResolvedAssessmentItem item = new ResolvedAssessmentItem(null, null);
+		return item;
+		
+	}
+
+}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestPackage.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestPackage.java
new file mode 100644
index 00000000000..94c8d780521
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestPackage.java
@@ -0,0 +1,5 @@
+package org.olat.ims.qti21.model.xml;
+
+public class AssessmentTestPackage {
+
+}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/ManifestPackage.java b/src/main/java/org/olat/ims/qti21/model/xml/ManifestPackage.java
new file mode 100644
index 00000000000..b23b5066ab0
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/model/xml/ManifestPackage.java
@@ -0,0 +1,87 @@
+package org.olat.ims.qti21.model.xml;
+
+import java.io.OutputStream;
+import java.util.UUID;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.imscp.xml.manifest.FileType;
+import org.olat.imscp.xml.manifest.ManifestMetadataType;
+import org.olat.imscp.xml.manifest.ManifestType;
+import org.olat.imscp.xml.manifest.ObjectFactory;
+import org.olat.imscp.xml.manifest.OrganizationsType;
+import org.olat.imscp.xml.manifest.ResourceType;
+import org.olat.imscp.xml.manifest.ResourcesType;
+
+public class ManifestPackage {
+	
+	private static final OLog log = Tracing.createLoggerFor(ManifestPackage.class);
+	private static final ObjectFactory objectFactory = new ObjectFactory();
+	
+	public static ManifestType createEmptyManifest() {
+		ManifestType manifestType = objectFactory.createManifestType();
+        ManifestMetadataType metadataType = objectFactory.createManifestMetadataType();
+        metadataType.setSchema("QTIv2.1 Package");
+        metadataType.setSchemaversion("1.0.0");
+        manifestType.setMetadata(metadataType);
+        
+        OrganizationsType organisationsType = objectFactory.createOrganizationsType();
+        manifestType.setOrganizations(organisationsType);
+
+        ResourcesType resourcesType = objectFactory.createResourcesType();
+        manifestType.setResources(resourcesType);
+        return manifestType;
+	}
+	
+	public static String appendAssessmentTest(ManifestType manifest) {
+		String testId = "id" + UUID.randomUUID().toString();
+        String testFileName = testId + ".xml";
+       
+        ResourceType testResourceType = objectFactory.createResourceType();
+        testResourceType.setIdentifier(testId);
+        testResourceType.setType("imsqti_test_xmlv2p1");
+        testResourceType.setHref(testFileName);
+        manifest.getResources().getResource().add(testResourceType);
+
+        appendFile(testResourceType, testFileName);
+		return testFileName;
+	}
+	
+	public static String appendAssessmentItem(ManifestType manifest) {
+		String itemId = "id" + UUID.randomUUID().toString();
+        String itemFileName = itemId + ".xml";
+        
+        ResourceType itemResourceType = objectFactory.createResourceType();
+        itemResourceType.setIdentifier(itemId);
+        itemResourceType.setType("imsqti_item_xmlv2p1");
+        itemResourceType.setHref(itemFileName);
+        manifest.getResources().getResource().add(itemResourceType);
+        
+        appendFile(itemResourceType, itemFileName);
+		return itemFileName;
+	}
+	
+
+	public static void appendFile(ResourceType resource, String href) {
+		FileType itemFileType = objectFactory.createFileType();
+        itemFileType.setHref(href);
+        resource.getFile().add(itemFileType);
+	}
+	
+	public static void write(ManifestType manifest, OutputStream out) {
+        try {
+			JAXBContext context = JAXBContext.newInstance("org.olat.imscp.xml.manifest");
+			Marshaller marshaller = context.createMarshaller();
+			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+			marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.imsglobal.org/xsd/imscp_v1p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/qtiv2p1_imscpv1p2_v1p0.xsd");
+
+			marshaller.marshal(objectFactory.createManifest(manifest), out);
+		} catch (JAXBException e) {
+			log.error("", e);
+		}
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
index 826c9ed2f86..2f0aefba518 100644
--- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
+++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
@@ -21,6 +21,11 @@ package org.olat.ims.qti21.repository.handlers;
 
 import java.io.File;
 import java.util.Locale;
+import java.util.UUID;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.persistence.DBFactory;
@@ -33,15 +38,25 @@ import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
 import org.olat.core.util.coordinate.LockResult;
 import org.olat.course.assessment.AssessmentMode;
 import org.olat.fileresource.FileResourceManager;
 import org.olat.fileresource.types.FileResource;
 import org.olat.fileresource.types.ImsQTI21Resource;
 import org.olat.fileresource.types.ResourceEvaluation;
+import org.olat.ims.qti21.model.xml.ManifestPackage;
 import org.olat.ims.qti21.ui.AssessmentTestDisplayController;
 import org.olat.ims.qti21.ui.InMemoryOutcomesListener;
 import org.olat.ims.qti21.ui.editor.AssessmentTestComposerController;
+import org.olat.imscp.xml.manifest.FileType;
+import org.olat.imscp.xml.manifest.ManifestMetadataType;
+import org.olat.imscp.xml.manifest.ManifestType;
+import org.olat.imscp.xml.manifest.ObjectFactory;
+import org.olat.imscp.xml.manifest.OrganizationsType;
+import org.olat.imscp.xml.manifest.ResourceType;
+import org.olat.imscp.xml.manifest.ResourcesType;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryService;
 import org.olat.repository.handlers.EditionSupport;
@@ -61,6 +76,8 @@ import org.springframework.stereotype.Service;
  */
 @Service
 public class QTI21AssessmentTestHandler extends FileHandler {
+	
+	private static final OLog log = Tracing.createLoggerFor(QTI21AssessmentTestHandler.class);
 
 	@Override
 	public String getSupportedType() {
@@ -69,7 +86,7 @@ public class QTI21AssessmentTestHandler extends FileHandler {
 
 	@Override
 	public boolean isCreate() {
-		return false;
+		return true;
 	}
 
 	@Override
@@ -78,10 +95,38 @@ public class QTI21AssessmentTestHandler extends FileHandler {
 	}
 
 	@Override
-	public RepositoryEntry createResource(Identity initialAuthor, String displayname, String description,
-			Object createObject, Locale locale) {
-		return null;
+	public RepositoryEntry createResource(Identity initialAuthor, String displayname, String description, Object createObject, Locale locale) {
+		ImsQTI21Resource ores = new ImsQTI21Resource();
+		
+		RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class);
+		OLATResource resource = OLATResourceManager.getInstance().findOrPersistResourceable(ores);
+		RepositoryEntry re = repositoryService.create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS);
+		DBFactory.getInstance().commit();
+		
+		File repositoryDir = new File(FileResourceManager.getInstance().getFileResourceRoot(re.getOlatResource()), FileResourceManager.ZIPDIR);
+		if(!repositoryDir.exists()) {
+			
+		}
+		return re;
+	}
+	
+	public void createMinimalAssessmentTest() {
+        ManifestType manifestType = ManifestPackage.createEmptyManifest();
+        String testFilename = ManifestPackage.appendAssessmentTest(manifestType);
+        String itemFilename = ManifestPackage.appendAssessmentItem(manifestType);	
+        ManifestPackage.write(manifestType, System.out);
+        
+        //create basic assessment test
+        
+        
+        //create single choice
+		
+		
+		
+		
 	}
+	
+	
 
 	@Override
 	public boolean isPostCreateWizardAvailable() {
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
index b4c7fa8477f..4cea09589ff 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentItemDisplayController.java
@@ -43,8 +43,8 @@ import org.olat.fileresource.types.ImsQTI21Resource;
 import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.UserTestSession;
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateItemEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.olat.ims.qti21.ui.components.AssessmentItemFormItem;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
index a0d72b5ff6f..e11d1472548 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
@@ -44,9 +44,9 @@ import org.olat.ims.qti21.OutcomesListener;
 import org.olat.ims.qti21.QTI21Constants;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.UserTestSession;
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateItemEventType;
 import org.olat.ims.qti21.model.CandidateTestEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.olat.ims.qti21.ui.components.AssessmentTestFormItem;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/main/java/org/olat/ims/qti21/ui/CandidateSessionContext.java b/src/main/java/org/olat/ims/qti21/ui/CandidateSessionContext.java
index b92414af7d2..08680ecea36 100644
--- a/src/main/java/org/olat/ims/qti21/ui/CandidateSessionContext.java
+++ b/src/main/java/org/olat/ims/qti21/ui/CandidateSessionContext.java
@@ -22,7 +22,7 @@ package org.olat.ims.qti21.ui;
 import java.util.Date;
 
 import org.olat.ims.qti21.UserTestSession;
-import org.olat.ims.qti21.model.CandidateEvent;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 
 public interface CandidateSessionContext {
 	
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
index f60046bfbd0..52a37e09c59 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentItemComponentRenderer.java
@@ -36,8 +36,8 @@ import org.olat.core.gui.render.URLBuilder;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.ims.qti21.UserTestSession;
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateItemEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.olat.ims.qti21.ui.CandidateSessionContext;
 import org.olat.ims.qti21.ui.rendering.AbstractRenderingOptions;
 import org.olat.ims.qti21.ui.rendering.AbstractRenderingRequest;
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
index 377cdd74d05..0ede59aa9c4 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentTestComponentRenderer.java
@@ -36,8 +36,8 @@ import org.olat.core.gui.render.URLBuilder;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.ims.qti21.UserTestSession;
-import org.olat.ims.qti21.model.CandidateEvent;
 import org.olat.ims.qti21.model.CandidateTestEventType;
+import org.olat.ims.qti21.model.jpa.CandidateEvent;
 import org.olat.ims.qti21.ui.CandidateSessionContext;
 import org.olat.ims.qti21.ui.rendering.AbstractRenderingOptions;
 import org.olat.ims.qti21.ui.rendering.AbstractRenderingRequest;
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java
index 5683498d144..035f0308a8b 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentItemEditorController.java
@@ -20,16 +20,23 @@
 package org.olat.ims.qti21.ui.editor;
 
 import java.io.File;
+import java.util.List;
 
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.tabbedpane.TabbedPane;
 import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
+import org.olat.ims.qti21.QTI21Constants;
 import org.olat.ims.qti21.ui.AssessmentItemDisplayController;
 import org.olat.repository.RepositoryEntry;
 
+import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction;
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction;
 import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef;
 import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
 
@@ -42,8 +49,11 @@ import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
 public class AssessmentItemEditorController extends BasicController {
 	
 	private final ResolvedAssessmentItem resolvedAssessmentItem;
+	
+	private final TabbedPane tabbedPane;
 	private final VelocityContainer mainVC;
 	
+	private FormBasicController itemEditor;
 	private AssessmentItemDisplayController displayCtrl;
 	
 	public AssessmentItemEditorController(UserRequest ureq, WindowControl wControl, RepositoryEntry testEntry,
@@ -52,13 +62,55 @@ public class AssessmentItemEditorController extends BasicController {
 		this.resolvedAssessmentItem = resolvedAssessmentItem;
 		
 		mainVC = createVelocityContainer("assessment_item_editor");
+		tabbedPane = new TabbedPane("itemTabs", getLocale());
+		tabbedPane.addListener(this);
+		mainVC.put("tabbedpane", tabbedPane);
+
+		initItemEditor(ureq);
 		
 		displayCtrl = new AssessmentItemDisplayController(ureq, getWindowControl(), testEntry, resolvedAssessmentItem, itemRef, unzippedDirectory);
 		listenTo(displayCtrl);
-		mainVC.put("display", displayCtrl.getInitialComponent());
+		tabbedPane.addTab("Preview", displayCtrl.getInitialComponent());
 		
 		putInitialPanel(mainVC);
-		
+	}
+	
+	private void initItemEditor(UserRequest ureq) {
+
+		AssessmentItem item = resolvedAssessmentItem.getItemLookup().getRootNodeHolder().getRootNode();
+		if(QTI21Constants.TOOLNAME.equals(item.getToolName())) {
+			//we have create this one
+			List<Interaction> interactions = item.getItemBody().findInteractions();
+			
+			boolean choice = false;
+			boolean unkown = false;
+			
+			if(interactions != null && interactions.size() > 0) {
+				for(Interaction interaction: interactions) {
+					if(interaction instanceof ChoiceInteraction) {
+						choice = true;
+					} else {
+						unkown = true;
+					}	
+				}	
+			}
+			
+			if(choice && !unkown) {
+				itemEditor = new SingleChoiceEditorController(ureq, getWindowControl());
+				listenTo(itemEditor);
+				tabbedPane.addTab("Choice", itemEditor.getInitialComponent());
+			} else if(unkown) {
+				initItemCreatedByUnkownEditor(ureq);
+			}
+		} else {
+			initItemCreatedByUnkownEditor(ureq);
+		}
+	}
+	
+	private void initItemCreatedByUnkownEditor(UserRequest ureq) {
+		itemEditor = new UnkownItemEditorController(ureq, getWindowControl());
+		listenTo(itemEditor);
+		tabbedPane.addTab("Unkown", itemEditor.getInitialComponent());
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/SingleChoiceEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/SingleChoiceEditorController.java
new file mode 100644
index 00000000000..6448b1ffd14
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/SingleChoiceEditorController.java
@@ -0,0 +1,38 @@
+package org.olat.ims.qti21.ui.editor;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+
+/**
+ * 
+ * Initial date: 26.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SingleChoiceEditorController extends FormBasicController {
+	
+	public SingleChoiceEditorController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormTitle("editor.sc.title");
+		//
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/UnkownItemEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/UnkownItemEditorController.java
new file mode 100644
index 00000000000..f593a1eedf7
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/UnkownItemEditorController.java
@@ -0,0 +1,38 @@
+package org.olat.ims.qti21.ui.editor;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+
+/**
+ * 
+ * Initial date: 26.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class UnkownItemEditorController extends FormBasicController {
+	
+	public UnkownItemEditorController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormTitle("editor.unkown.title");
+		//
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_content/assessment_item_editor.html b/src/main/java/org/olat/ims/qti21/ui/editor/_content/assessment_item_editor.html
index c3e3485de69..23b14550deb 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/_content/assessment_item_editor.html
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/_content/assessment_item_editor.html
@@ -1,3 +1 @@
-[Assessment item editor]
-
-$r.render("display")
\ No newline at end of file
+$r.render("tabbedpane")
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties
new file mode 100644
index 00000000000..fc8aa54fd70
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties
@@ -0,0 +1,3 @@
+#Mon Mar 02 09:54:04 CET 2009
+editor.sc.title=Single choice
+editor.unkown.title=Unkown interaction
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties
new file mode 100644
index 00000000000..d186f30e906
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties
@@ -0,0 +1,3 @@
+#Sat Jan 22 17:01:28 CET 2011
+editor.sc.title=Single choice
+editor.unkown.title=Unkown interaction
\ No newline at end of file
diff --git a/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java b/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java
new file mode 100644
index 00000000000..e60183f47c8
--- /dev/null
+++ b/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java
@@ -0,0 +1,319 @@
+package org.olat.ims.qti21.model.xml;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
+
+import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
+import uk.ac.ed.ph.jqtiplus.group.NodeGroupList;
+import uk.ac.ed.ph.jqtiplus.group.item.ItemBodyGroup;
+import uk.ac.ed.ph.jqtiplus.group.item.interaction.PromptGroup;
+import uk.ac.ed.ph.jqtiplus.group.item.interaction.choice.SimpleChoiceGroup;
+import uk.ac.ed.ph.jqtiplus.group.item.response.declaration.ResponseDeclarationGroup;
+import uk.ac.ed.ph.jqtiplus.group.item.response.processing.ResponseProcessingGroup;
+import uk.ac.ed.ph.jqtiplus.group.outcome.declaration.OutcomeDeclarationGroup;
+import uk.ac.ed.ph.jqtiplus.node.QtiNode;
+import uk.ac.ed.ph.jqtiplus.node.content.ItemBody;
+import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun;
+import uk.ac.ed.ph.jqtiplus.node.content.xhtml.text.P;
+import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue;
+import uk.ac.ed.ph.jqtiplus.node.expression.general.Correct;
+import uk.ac.ed.ph.jqtiplus.node.expression.general.Variable;
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.IsNull;
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.Match;
+import uk.ac.ed.ph.jqtiplus.node.expression.operator.Sum;
+import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
+import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse;
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction;
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleChoice;
+import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseCondition;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseElse;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseElseIf;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseIf;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseProcessing;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.SetOutcomeValue;
+import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration;
+import uk.ac.ed.ph.jqtiplus.node.shared.FieldValue;
+import uk.ac.ed.ph.jqtiplus.node.shared.declaration.DefaultValue;
+import uk.ac.ed.ph.jqtiplus.reading.AssessmentObjectXmlLoader;
+import uk.ac.ed.ph.jqtiplus.reading.QtiXmlReader;
+import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
+import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
+import uk.ac.ed.ph.jqtiplus.types.ComplexReferenceIdentifier;
+import uk.ac.ed.ph.jqtiplus.types.Identifier;
+import uk.ac.ed.ph.jqtiplus.validation.ItemValidationResult;
+import uk.ac.ed.ph.jqtiplus.value.BaseType;
+import uk.ac.ed.ph.jqtiplus.value.Cardinality;
+import uk.ac.ed.ph.jqtiplus.value.FloatValue;
+import uk.ac.ed.ph.jqtiplus.value.IdentifierValue;
+import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator;
+
+public class AssessmentItemPackageTest {
+	
+	private static final OLog log = Tracing.createLoggerFor(AssessmentItemPackageTest.class);
+	
+	@Test
+	public void loadAssessmentItem() throws URISyntaxException {
+		QtiXmlReader qtiXmlReader = new QtiXmlReader(new JqtiExtensionManager());
+		QtiSerializer qtiSerializer = new QtiSerializer(new JqtiExtensionManager());
+		
+		
+		URL testUrl = AssessmentItemPackageTest.class.getResource("assessment-item-single-choice.xml");
+		ResourceLocator fileResourceLocator = new PathResourceLocator(Paths.get(testUrl.toURI()));
+        AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, fileResourceLocator);
+
+        ResolvedAssessmentItem item = assessmentObjectXmlLoader.loadAndResolveAssessmentItem(testUrl.toURI());
+		Assert.assertNotNull(item);
+		
+		AssessmentItem assessmentItem = item.getItemLookup().getRootNodeHolder().getRootNode();
+		Assert.assertNotNull(assessmentItem);
+		
+		qtiSerializer.serializeJqtiObject(assessmentItem, System.out);
+	}
+
+	@Test
+	public void buildAssessmentItem() throws URISyntaxException {
+		
+		QtiSerializer qtiSerializer = new QtiSerializer(new JqtiExtensionManager());
+		
+		AssessmentItem assessmentItem = new AssessmentItem();
+		assessmentItem.setIdentifier("id" + UUID.randomUUID());
+		assessmentItem.setTitle("Physicists");
+		assessmentItem.setAdaptive(Boolean.FALSE);
+		assessmentItem.setTimeDependent(Boolean.FALSE);
+		NodeGroupList nodeGroups = assessmentItem.getNodeGroups();
+
+		//single choice
+		Identifier deBroglieId = Identifier.parseString("id" + UUID.randomUUID().toString());
+		Identifier maxPlanckId = Identifier.parseString("id" + UUID.randomUUID().toString());
+		
+	
+		//define correct answer
+		ResponseDeclarationGroup responseDeclarations = nodeGroups.getResponseDeclarationGroup();
+		ResponseDeclaration responseDeclaration = new ResponseDeclaration(assessmentItem);
+		responseDeclaration.setIdentifier(Identifier.parseString("RESPONSE_1"));
+		responseDeclaration.setCardinality(Cardinality.SINGLE);
+		responseDeclaration.setBaseType(BaseType.IDENTIFIER);
+		responseDeclarations.getResponseDeclarations().add(responseDeclaration);
+		
+		CorrectResponse correctResponse = new CorrectResponse(responseDeclaration);
+		responseDeclaration.setCorrectResponse(correctResponse);
+		
+		FieldValue fieldValue = new FieldValue(correctResponse);
+		IdentifierValue identifierValue = new IdentifierValue(maxPlanckId);
+		fieldValue.setSingleValue(identifierValue);
+		correctResponse.getFieldValues().add(fieldValue);
+		
+		//outcomes
+		OutcomeDeclarationGroup outcomeDeclarations = nodeGroups.getOutcomeDeclarationGroup();
+
+		// outcome score
+		OutcomeDeclaration scoreOutcomeDeclaration = new OutcomeDeclaration(assessmentItem);
+		scoreOutcomeDeclaration.setIdentifier(Identifier.parseString("SCORE"));
+		scoreOutcomeDeclaration.setCardinality(Cardinality.SINGLE);
+		scoreOutcomeDeclaration.setBaseType(BaseType.FLOAT);
+		outcomeDeclarations.getOutcomeDeclarations().add(scoreOutcomeDeclaration);
+		
+		DefaultValue scoreDefaultVal = new DefaultValue(scoreOutcomeDeclaration);
+		scoreOutcomeDeclaration.setDefaultValue(scoreDefaultVal);
+		
+		FieldValue scoreDefaultFieldVal = new FieldValue(scoreDefaultVal, FloatValue.ZERO);
+		scoreDefaultVal.getFieldValues().add(scoreDefaultFieldVal);
+
+		// outcome max score
+		OutcomeDeclaration maxScoreOutcomeDeclaration = new OutcomeDeclaration(assessmentItem);
+		maxScoreOutcomeDeclaration.setIdentifier(Identifier.parseString("MAXSCORE"));
+		maxScoreOutcomeDeclaration.setCardinality(Cardinality.SINGLE);
+		maxScoreOutcomeDeclaration.setBaseType(BaseType.FLOAT);
+		outcomeDeclarations.getOutcomeDeclarations().add(maxScoreOutcomeDeclaration);
+		
+		DefaultValue maxScoreDefaultVal = new DefaultValue(maxScoreOutcomeDeclaration);
+		maxScoreOutcomeDeclaration.setDefaultValue(maxScoreDefaultVal);
+		
+		FieldValue maxScoreDefaultFieldVal = new FieldValue(maxScoreDefaultVal, new FloatValue(1.0f));
+		maxScoreDefaultVal.getFieldValues().add(maxScoreDefaultFieldVal);
+		
+		// outcome feedback
+		OutcomeDeclaration feedbackOutcomeDeclaration = new OutcomeDeclaration(assessmentItem);
+		feedbackOutcomeDeclaration.setIdentifier(Identifier.parseString("FEEDBACKBASIC"));
+		feedbackOutcomeDeclaration.setCardinality(Cardinality.SINGLE);
+		feedbackOutcomeDeclaration.setBaseType(BaseType.IDENTIFIER);
+		outcomeDeclarations.getOutcomeDeclarations().add(feedbackOutcomeDeclaration);
+		
+		DefaultValue feedbackDefaultVal = new DefaultValue(feedbackOutcomeDeclaration);
+		feedbackOutcomeDeclaration.setDefaultValue(feedbackDefaultVal);
+		
+		FieldValue feedbackDefaultFieldVal = new FieldValue(feedbackDefaultVal, new IdentifierValue("empty"));
+		feedbackDefaultVal.getFieldValues().add(feedbackDefaultFieldVal);
+		
+		
+		//the interaction
+		ItemBodyGroup itemBodyGroup = nodeGroups.getItemBodyGroup();
+		itemBodyGroup.setItemBody(new ItemBody(assessmentItem));
+		
+		ItemBody itemBody = itemBodyGroup.getItemBody();
+		
+		P question = getParagraph(itemBody, "Wo is the greatest physicist of all time?");
+		itemBodyGroup.getItemBody().getBlocks().add(question);
+		
+		ChoiceInteraction choiceInteraction = new ChoiceInteraction(itemBody);
+		choiceInteraction.setMaxChoices(1);
+		choiceInteraction.setShuffle(true);
+		choiceInteraction.setResponseIdentifier(Identifier.parseString("RESPONSE_1"));
+		itemBodyGroup.getItemBody().getBlocks().add(choiceInteraction);
+		
+		PromptGroup prompts = new PromptGroup(choiceInteraction);
+		choiceInteraction.getNodeGroups().add(prompts);
+		
+		SimpleChoiceGroup singleChoices = new SimpleChoiceGroup(choiceInteraction);
+		choiceInteraction.getNodeGroups().add(singleChoices);
+
+		SimpleChoice firstChoice = new SimpleChoice(choiceInteraction);
+		firstChoice.setIdentifier(deBroglieId);
+		P firstChoiceText = getParagraph(firstChoice, "Louis de Broglie");
+		firstChoice.getFlowStatics().add(firstChoiceText);
+		singleChoices.getSimpleChoices().add(firstChoice);
+		
+		SimpleChoice secondChoice = new SimpleChoice(choiceInteraction);
+		secondChoice.setIdentifier(maxPlanckId);
+		P secondChoiceText = getParagraph(secondChoice, "Max Planck");
+		secondChoice.getFlowStatics().add(secondChoiceText);
+		singleChoices.getSimpleChoices().add(secondChoice);
+		
+		//response processing
+		ResponseProcessingGroup responsesProcessing = nodeGroups.getResponseProcessingGroup();
+		ResponseProcessing responseProcessing = new ResponseProcessing(assessmentItem);
+		responsesProcessing.setResponseProcessing(responseProcessing);
+		
+		ResponseCondition rule = new ResponseCondition(responseProcessing);
+		
+		//if no response
+		ResponseIf responseIf = new ResponseIf(rule);
+		rule.setResponseIf(responseIf);
+		
+		IsNull isNull = new IsNull(responseIf);
+		responseIf.getExpressions().add(isNull);
+		
+		Variable variable = new Variable(isNull);
+		variable.setIdentifier(ComplexReferenceIdentifier.parseString("RESPONSE_1"));
+		isNull.getExpressions().add(variable);
+		
+		{
+			SetOutcomeValue feedbackVar = new SetOutcomeValue(responseIf);
+			feedbackVar.setIdentifier(Identifier.parseString("FEEDBACKBASIC"));
+			BaseValue feedbackVal = new BaseValue(feedbackVar);
+			feedbackVal.setBaseTypeAttrValue(BaseType.IDENTIFIER);
+			feedbackVal.setSingleValue(new IdentifierValue("empty"));
+			feedbackVar.setExpression(feedbackVal);
+			responseIf.getResponseRules().add(feedbackVar);
+		}
+		
+		//else if correct response
+		ResponseElseIf responseElseIf = new ResponseElseIf(rule);
+		rule.getResponseElseIfs().add(responseElseIf);
+		
+		//match 
+		{
+			Match match = new Match(responseElseIf);
+			responseElseIf.getExpressions().add(match);
+			
+			Variable responseVar = new Variable(match);
+			responseVar.setIdentifier(ComplexReferenceIdentifier.parseString("RESPONSE_1"));
+			match.getExpressions().add(responseVar);
+			
+			Correct correct = new Correct(match);
+			correct.setIdentifier(ComplexReferenceIdentifier.parseString("RESPONSE_1"));
+			match.getExpressions().add(correct);
+		}
+
+		// outcome score
+		{
+			SetOutcomeValue scoreOutcomeVar = new SetOutcomeValue(responseIf);
+			scoreOutcomeVar.setIdentifier(Identifier.parseString("SCORE"));
+			responseElseIf.getResponseRules().add(scoreOutcomeVar);
+			
+			Sum sum = new Sum(scoreOutcomeVar);
+			scoreOutcomeVar.getExpressions().add(sum);
+			
+			Variable scoreVar = new Variable(sum);
+			scoreVar.setIdentifier(ComplexReferenceIdentifier.parseString("SCORE"));
+			sum.getExpressions().add(scoreVar);
+			
+			Variable maxScoreVar = new Variable(sum);
+			maxScoreVar.setIdentifier(ComplexReferenceIdentifier.parseString("MAXSCORE"));
+			sum.getExpressions().add(maxScoreVar);
+		}
+		
+		// outcome feedback
+		{
+			SetOutcomeValue correctFeedbackVar = new SetOutcomeValue(responseIf);
+			correctFeedbackVar.setIdentifier(Identifier.parseString("FEEDBACKBASIC"));
+			BaseValue correctFeedbackVal = new BaseValue(correctFeedbackVar);
+			correctFeedbackVal.setBaseTypeAttrValue(BaseType.IDENTIFIER);
+			correctFeedbackVal.setSingleValue(new IdentifierValue("correct"));
+			correctFeedbackVar.setExpression(correctFeedbackVal);
+			responseElseIf.getResponseRules().add(correctFeedbackVar);
+		}
+		
+		
+		// else failed
+		ResponseElse responseElse = new ResponseElse(rule);
+		rule.setResponseElse(responseElse);
+		{// feedback incorrect
+			SetOutcomeValue incorrectFeedbackVar = new SetOutcomeValue(responseIf);
+			incorrectFeedbackVar.setIdentifier(Identifier.parseString("FEEDBACKBASIC"));
+			BaseValue incorrectFeedbackVal = new BaseValue(incorrectFeedbackVar);
+			incorrectFeedbackVal.setBaseTypeAttrValue(BaseType.IDENTIFIER);
+			incorrectFeedbackVal.setSingleValue(new IdentifierValue("incorrect"));
+			incorrectFeedbackVar.setExpression(incorrectFeedbackVal);
+			responseElse.getResponseRules().add(incorrectFeedbackVar);
+		}
+		
+		
+		
+		responseProcessing.getResponseRules().add(rule);
+		
+		
+		
+		
+		qtiSerializer.serializeJqtiObject(assessmentItem, System.out);
+		System.out.println("\n-------------");
+		
+		File outputFile = new File("/Users/srosse/Desktop/generated_item.xml");
+		if(outputFile.exists()) {
+			outputFile.delete();
+			outputFile = new File("/Users/srosse/Desktop/generated_item.xml");
+		}
+		try(FileOutputStream out = new FileOutputStream(outputFile)) {
+			qtiSerializer.serializeJqtiObject(assessmentItem, out);	
+		} catch(Exception e) {
+			log.error("", e);
+		}
+
+		QtiXmlReader qtiXmlReader = new QtiXmlReader(new JqtiExtensionManager());
+		ResourceLocator fileResourceLocator = new PathResourceLocator(outputFile.toPath());
+        AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, fileResourceLocator);
+        ItemValidationResult item = assessmentObjectXmlLoader.loadResolveAndValidateItem(outputFile.toURI());
+        System.out.println("Has errors: " + item.hasErrors());
+	}
+	
+	private P getParagraph(QtiNode parent, String content) {
+		P paragraph = new P(parent);
+		TextRun text = new TextRun(paragraph, content);
+		paragraph.getInlines().add(text);
+		return paragraph;
+	}
+	
+	
+
+}
diff --git a/src/test/java/org/olat/ims/qti21/model/xml/AssessmentTestPackageTest.java b/src/test/java/org/olat/ims/qti21/model/xml/AssessmentTestPackageTest.java
new file mode 100644
index 00000000000..616dcdfe4db
--- /dev/null
+++ b/src/test/java/org/olat/ims/qti21/model/xml/AssessmentTestPackageTest.java
@@ -0,0 +1,151 @@
+package org.olat.ims.qti21.model.xml;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.fileresource.types.ImsQTI21Resource.PathResourceLocator;
+
+import uk.ac.ed.ph.jqtiplus.JqtiExtensionManager;
+import uk.ac.ed.ph.jqtiplus.node.content.variable.RubricBlock;
+import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration;
+import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef;
+import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection;
+import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest;
+import uk.ac.ed.ph.jqtiplus.node.test.ItemSessionControl;
+import uk.ac.ed.ph.jqtiplus.node.test.NavigationMode;
+import uk.ac.ed.ph.jqtiplus.node.test.Ordering;
+import uk.ac.ed.ph.jqtiplus.node.test.SubmissionMode;
+import uk.ac.ed.ph.jqtiplus.node.test.TestPart;
+import uk.ac.ed.ph.jqtiplus.node.test.TimeLimits;
+import uk.ac.ed.ph.jqtiplus.node.test.View;
+import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeProcessing;
+import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.SetOutcomeValue;
+import uk.ac.ed.ph.jqtiplus.notification.Notification;
+import uk.ac.ed.ph.jqtiplus.reading.AssessmentObjectXmlLoader;
+import uk.ac.ed.ph.jqtiplus.reading.QtiXmlReader;
+import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
+import uk.ac.ed.ph.jqtiplus.types.Identifier;
+import uk.ac.ed.ph.jqtiplus.validation.TestValidationResult;
+import uk.ac.ed.ph.jqtiplus.value.BaseType;
+import uk.ac.ed.ph.jqtiplus.value.Cardinality;
+import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator;
+
+public class AssessmentTestPackageTest {
+	
+	private static final OLog log = Tracing.createLoggerFor(AssessmentTestPackageTest.class);
+	
+	@Test
+	public void buildAssessmentTest() throws URISyntaxException {
+
+		QtiSerializer qtiSerializer = new QtiSerializer(new JqtiExtensionManager());
+
+		AssessmentTest assessmentTest = new AssessmentTest();
+		assessmentTest.setIdentifier("id" + UUID.randomUUID());
+		assessmentTest.setTitle("My test");
+		assessmentTest.setToolName("OpenOLAT");
+		assessmentTest.setToolVersion("11.0");
+		
+		//outcome declarations
+		OutcomeDeclaration scoreOutcomeDeclaration = new OutcomeDeclaration(assessmentTest);
+		scoreOutcomeDeclaration.setIdentifier(Identifier.parseString("SCORE"));
+		scoreOutcomeDeclaration.setCardinality(Cardinality.SINGLE);
+		scoreOutcomeDeclaration.setBaseType(BaseType.FLOAT);
+		assessmentTest.getOutcomeDeclarations().add(scoreOutcomeDeclaration);
+		
+		
+		//time limits
+		TimeLimits timeLimits = new TimeLimits(assessmentTest);
+		timeLimits.setAllowLateSubmission(Boolean.FALSE);
+		timeLimits.setMinimum(0.0d);
+		timeLimits.setMaximum(1800.0);
+		assessmentTest.setTimeLimits(timeLimits);
+		
+		//test parts
+		TestPart part = new TestPart(assessmentTest);
+		part.setIdentifier(Identifier.parseString("id" + UUID.randomUUID()));
+		part.setNavigationMode(NavigationMode.NONLINEAR);
+		part.setSubmissionMode(SubmissionMode.INDIVIDUAL);
+		assessmentTest.getTestParts().add(part);
+		
+		//part -> item session control
+		ItemSessionControl itemSessionControl = new ItemSessionControl(part);
+		itemSessionControl.setAllowComment(Boolean.TRUE);
+		itemSessionControl.setAllowReview(Boolean.TRUE);
+		itemSessionControl.setAllowSkipping(Boolean.TRUE);
+		itemSessionControl.setMaxAttempts(12);
+		itemSessionControl.setShowFeedback(Boolean.TRUE);
+		itemSessionControl.setShowSolution(Boolean.TRUE);
+		part.setItemSessionControl(itemSessionControl);
+		
+		// section
+		AssessmentSection section = new AssessmentSection(part);
+		section.setFixed(Boolean.TRUE);
+		section.setVisible(Boolean.TRUE);
+		section.setTitle("My section");
+		section.setIdentifier(Identifier.parseString("id" + UUID.randomUUID()));
+		part.getAssessmentSections().add(section);
+		
+		Ordering ordering = new Ordering(section);
+		ordering.setShuffle(false);
+		section.setOrdering(ordering);
+		
+		RubricBlock rubrickBlock = new RubricBlock(section);
+		rubrickBlock.setViews(Collections.singletonList(View.CANDIDATE));
+		section.getRubricBlocks().add(rubrickBlock);
+		
+		AssessmentItemRef item = new AssessmentItemRef(section);
+		item.setIdentifier(Identifier.parseString("id" + UUID.randomUUID()));
+		item.setHref(new URI("test-item.xml"));
+		section.getSectionParts().add(item);
+		
+		
+		
+		
+		
+		//outcome processing
+		OutcomeProcessing outcomeProcessing = new OutcomeProcessing(assessmentTest);
+		assessmentTest.setOutcomeProcessing(outcomeProcessing);
+		
+		SetOutcomeValue outcomeRule = new SetOutcomeValue(outcomeProcessing);
+		
+		
+		outcomeProcessing.getOutcomeRules().add(outcomeRule);
+		
+		//feedbacks
+		assessmentTest.getTestFeedbacks();
+		
+		
+		qtiSerializer.serializeJqtiObject(assessmentTest, System.out);
+		System.out.println("\n-------------");
+		
+		File outputFile = new File("/Users/srosse/Desktop/QTI 2.1/generated_test.xml");
+		if(outputFile.exists()) {
+			outputFile.delete();
+			outputFile = new File("/Users/srosse/Desktop/QTI 2.1/generated_test.xml");
+		}
+		try(FileOutputStream out = new FileOutputStream(outputFile)) {
+			qtiSerializer.serializeJqtiObject(assessmentTest, out);	
+		} catch(Exception e) {
+			log.error("", e);
+		}
+
+		QtiXmlReader qtiXmlReader = new QtiXmlReader(new JqtiExtensionManager());
+		ResourceLocator fileResourceLocator = new PathResourceLocator(outputFile.toPath());
+        AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, fileResourceLocator);
+        TestValidationResult test = assessmentObjectXmlLoader.loadResolveAndValidateTest(outputFile.toURI());
+
+        System.out.println("Has errors: " + test.hasErrors());
+        for(Notification notification: test.getErrors()) {
+        	System.out.println(notification.getQtiNode() + " : " + notification.getMessage());
+        }
+        Assert.assertFalse(test.hasErrors());
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/olat/ims/qti21/model/xml/ManifestPackageTest.java b/src/test/java/org/olat/ims/qti21/model/xml/ManifestPackageTest.java
new file mode 100644
index 00000000000..e931a16ef48
--- /dev/null
+++ b/src/test/java/org/olat/ims/qti21/model/xml/ManifestPackageTest.java
@@ -0,0 +1,46 @@
+package org.olat.ims.qti21.model.xml;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.imscp.xml.manifest.ManifestType;
+
+import uk.ac.ed.ph.jqtiplus.utils.contentpackaging.ContentPackageResource;
+import uk.ac.ed.ph.jqtiplus.utils.contentpackaging.ImsManifestException;
+import uk.ac.ed.ph.jqtiplus.utils.contentpackaging.QtiContentPackageExtractor;
+import uk.ac.ed.ph.jqtiplus.utils.contentpackaging.QtiContentPackageSummary;
+import uk.ac.ed.ph.jqtiplus.xmlutils.XmlResourceNotFoundException;
+
+public class ManifestPackageTest {
+	
+	@Test
+	public void makeManifest() throws XmlResourceNotFoundException, ImsManifestException, IOException {
+		ManifestType manifestType = ManifestPackage.createEmptyManifest();
+        String testFilename = ManifestPackage.appendAssessmentTest(manifestType);
+        String itemFilename = ManifestPackage.appendAssessmentItem(manifestType);	
+        Assert.assertNotNull(testFilename);
+        Assert.assertNotNull(itemFilename);
+        
+        File tmpDir = new File("/HotCoffee/tmp/imsmanifest/");
+        if(!tmpDir.exists()) {
+        	tmpDir.mkdirs();
+        }
+        
+        
+        FileOutputStream out = new FileOutputStream(new File(tmpDir, "imsmanifest.xml"));
+        ManifestPackage.write(manifestType, out);
+        out.flush();
+        out.close(); 
+        
+        QtiContentPackageExtractor extractor = new QtiContentPackageExtractor(tmpDir);
+        QtiContentPackageSummary summary = extractor.parse();
+        List<ContentPackageResource> items = summary.getItemResources();
+        List<ContentPackageResource> tests = summary.getTestResources();
+        Assert.assertEquals(1, items.size());
+        Assert.assertEquals(1, tests.size());
+	}
+}
diff --git a/src/test/java/org/olat/ims/qti21/model/xml/assessment-item-single-choice.xml b/src/test/java/org/olat/ims/qti21/model/xml/assessment-item-single-choice.xml
new file mode 100644
index 00000000000..75292ef68e0
--- /dev/null
+++ b/src/test/java/org/olat/ims/qti21/model/xml/assessment-item-single-choice.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_v2p1p1.xsd http://www.w3.org/1998/Math/MathML http://www.w3.org/Math/XMLSchema/mathml2/mathml2.xsd" identifier="id1399ad03-4981-4240-8a39-dfd9857bf0e8_1" title="Single choice" adaptive="false" timeDependent="false">
+	<responseDeclaration identifier="RESPONSE_1" cardinality="single" baseType="identifier">
+		<correctResponse>
+			<value>id2c67a95f-608b-4d29-b0dc-1aba7f2f8b76_1</value>
+		</correctResponse>
+	</responseDeclaration>
+	<outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
+		<defaultValue>
+			<value>0</value>
+		</defaultValue>
+	</outcomeDeclaration>
+	<outcomeDeclaration identifier="MAXSCORE" cardinality="single" baseType="float">
+		<defaultValue>
+			<value>1</value>
+		</defaultValue>
+	</outcomeDeclaration>
+	<outcomeDeclaration identifier="FEEDBACKBASIC" cardinality="single" baseType="identifier">
+		<defaultValue>
+			<value>empty</value>
+		</defaultValue>
+	</outcomeDeclaration>
+	<itemBody>
+		<p>Wo is a <strong>physicist</strong>?</p>
+		<choiceInteraction responseIdentifier="RESPONSE_1" shuffle="true" maxChoices="1">
+			<simpleChoice identifier="id2c67a95f-608b-4d29-b0dc-1aba7f2f8b76_1">
+				<p>Masamune Shirow</p>
+			</simpleChoice>
+			<simpleChoice identifier="id9c2a3e00-7759-4d14-8194-4b8fef39bf31">
+				<p>Alfred Bogart</p>
+			</simpleChoice>
+			<simpleChoice identifier="id2df244d9-6c2e-4856-9e18-48ad452d03ea">
+				<p>Louis de Broglie</p>
+			</simpleChoice>
+		</choiceInteraction>
+	</itemBody>
+	<responseProcessing>
+		<responseCondition>
+			<responseIf>
+				<isNull>
+					<variable identifier="RESPONSE_1" /> 
+				</isNull>
+				<setOutcomeValue identifier="FEEDBACKBASIC">
+					<baseValue baseType="identifier">empty</baseValue>
+				</setOutcomeValue>
+			</responseIf>
+			<responseElseIf>
+				<match>
+					<variable identifier="RESPONSE_1" /><correct identifier="RESPONSE_1" /> 
+				</match>
+				<setOutcomeValue identifier="SCORE">
+					<sum>
+						<variable identifier="SCORE" /><variable identifier="MAXSCORE" /> 
+					</sum>
+				</setOutcomeValue>
+				<setOutcomeValue identifier="FEEDBACKBASIC">
+					<baseValue baseType="identifier">correct</baseValue>
+				</setOutcomeValue>
+			</responseElseIf>
+			<responseElse>
+				<setOutcomeValue identifier="FEEDBACKBASIC">
+					<baseValue baseType="identifier">incorrect</baseValue>
+				</setOutcomeValue>
+			</responseElse>
+		</responseCondition>
+	</responseProcessing>
+</assessmentItem>
\ No newline at end of file
diff --git a/src/test/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandlerTest.java b/src/test/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandlerTest.java
new file mode 100644
index 00000000000..6c4cb5b658b
--- /dev/null
+++ b/src/test/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandlerTest.java
@@ -0,0 +1,15 @@
+package org.olat.ims.qti21.repository.handlers;
+
+import org.junit.Test;
+
+public class QTI21AssessmentTestHandlerTest {
+	
+	@Test
+	public void createImsManfest() {
+		
+		QTI21AssessmentTestHandler handler = new QTI21AssessmentTestHandler();
+		handler.createMinimalAssessmentTest();
+		
+	}
+
+}
-- 
GitLab