/**
* <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.qti.qpool;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.olat.core.commons.persistence.DB;
import org.olat.core.id.Identity;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.xml.XMLParser;
import org.olat.ims.qti.QTIConstants;
import org.olat.ims.qti.qpool.QTIImportProcessor.DocInfos;
import org.olat.ims.qti.qpool.QTIImportProcessor.ItemInfos;
import org.olat.ims.resources.IMSEntityResolver;
import org.olat.modules.qpool.QuestionItem;
import org.olat.modules.qpool.QuestionItemFull;
import org.olat.modules.qpool.QuestionStatus;
import org.olat.modules.qpool.QuestionType;
import org.olat.modules.qpool.manager.QEducationalContextDAO;
import org.olat.modules.qpool.manager.QItemTypeDAO;
import org.olat.modules.qpool.manager.QLicenseDAO;
import org.olat.modules.qpool.manager.QPoolFileStorage;
import org.olat.modules.qpool.manager.QuestionItemDAO;
import org.olat.modules.qpool.manager.TaxonomyLevelDAO;
import org.olat.modules.qpool.model.QEducationalContext;
import org.olat.modules.qpool.model.QItemType;
import org.olat.modules.qpool.model.QuestionItemImpl;
import org.olat.test.JunitTestHelper;
import org.olat.test.OlatTestCase;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 08.03.2013<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class QTIImportProcessorTest extends OlatTestCase {
private static Identity owner;
@Autowired
private DB dbInstance;
@Autowired
private QPoolFileStorage qpoolFileStorage;
@Autowired
private QItemTypeDAO qItemTypeDao;
@Autowired
private QLicenseDAO qLicenseDao;
@Autowired
private QuestionItemDAO questionItemDao;
@Autowired
private TaxonomyLevelDAO taxonomyLevelDao;
@Autowired
private QEducationalContextDAO qEduContextDao;
@Before
public void setup() {
if(owner == null) {
owner = JunitTestHelper.createAndPersistIdentityAsUser("QTI-imp-owner-" + UUID.randomUUID().toString());
}
}
/**
* This test check every methods of the import process in details
* @throws IOException
* @throws URISyntaxException
*/
@Test
public void testImport_SC() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("mchc_i_002.xml");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao,qpoolFileStorage, dbInstance);
List<DocInfos> docInfoList = proc.getDocInfos();
Assert.assertNotNull(docInfoList);
Assert.assertEquals(1, docInfoList.size());
DocInfos docInfos = docInfoList.get(0);
Assert.assertNotNull(docInfos);
Assert.assertNotNull(docInfos.getFilename());
Assert.assertNotNull(docInfos.getDocument());
Assert.assertEquals("mchc_i_002.xml", docInfos.getFilename());
//get the question DOM's
List<ItemInfos> itemInfos = proc.getItemList(docInfos);
Assert.assertNotNull(itemInfos);
Assert.assertEquals(1, itemInfos.size());
//process item, files...
QuestionItemImpl item = proc.processItem(docInfos, itemInfos.get(0), null);
Assert.assertNotNull(item);
dbInstance.commitAndCloseSession();
proc.processFiles(item, itemInfos.get(0), null);
//reload and check what is saved
QuestionItemFull reloadItem = questionItemDao.loadById(item.getKey());
Assert.assertNotNull(reloadItem);
Assert.assertNotNull(reloadItem.getCreationDate());
Assert.assertNotNull(reloadItem.getLastModified());
Assert.assertEquals(QuestionStatus.draft, reloadItem.getQuestionStatus());
Assert.assertEquals(QTIConstants.QTI_12_FORMAT, reloadItem.getFormat());
//title
Assert.assertEquals("Standard Multiple Choice with Images Item", reloadItem.getTitle());
//description -> qticomment
Assert.assertEquals("This is a multiple-choice example with image content. The rendering is a standard radio button style. No response processing is incorporated.", reloadItem.getDescription());
//question type
Assert.assertNotNull(reloadItem.getType());
Assert.assertEquals(QuestionType.SC.name().toLowerCase(), reloadItem.getType().getType());
//check that the file is storead
VFSContainer itemDir = qpoolFileStorage.getContainer(reloadItem.getDirectory());
Assert.assertNotNull(itemDir);
VFSItem qtiLeaf = itemDir.resolve(reloadItem.getRootFilename());
Assert.assertNotNull(qtiLeaf);
Assert.assertTrue(qtiLeaf instanceof VFSLeaf);
Assert.assertTrue(qtiLeaf.exists());
Assert.assertEquals(itemFile.length(), ((VFSLeaf)qtiLeaf).getSize());
}
@Test
public void testImport_FIB() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("fibi_i_001.xml");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(1, items.size());
dbInstance.commitAndCloseSession();
//reload and check what is saved
QuestionItem reloadItem = questionItemDao.loadById(items.get(0).getKey());
Assert.assertNotNull(reloadItem);
Assert.assertNotNull(reloadItem.getCreationDate());
Assert.assertNotNull(reloadItem.getLastModified());
Assert.assertEquals(QuestionStatus.draft, reloadItem.getQuestionStatus());
Assert.assertEquals(QTIConstants.QTI_12_FORMAT, reloadItem.getFormat());
//title
Assert.assertEquals("Standard FIB numerical Item", reloadItem.getTitle());
//description -> qticomment
Assert.assertEquals("This is a standard numerical fill-in-blank (integer) example. No response processing is incorporated.", reloadItem.getDescription());
//question type
Assert.assertEquals(QuestionType.FIB.name().toLowerCase(), reloadItem.getType().getType());
}
/**
* This test check every methods of the import process in details
* @throws IOException
* @throws URISyntaxException
*/
@Test
public void testImport_OpenOLATTest_extractItems() throws IOException, URISyntaxException {
URL testUrl = QTIImportProcessorTest.class.getResource("oo_test_qti.xml");
Assert.assertNotNull(testUrl);
File testFile = new File(testUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, testFile.getName(), testFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<DocInfos> docInfoList = proc.getDocInfos();
Assert.assertNotNull(docInfoList);
Assert.assertEquals(1, docInfoList.size());
DocInfos docInfos = docInfoList.get(0);
Assert.assertNotNull(docInfos);
Assert.assertNotNull(docInfos.getFilename());
Assert.assertNotNull(docInfos.getDocument());
Assert.assertEquals("oo_test_qti.xml", docInfos.getFilename());
//get the question DOM's
List<ItemInfos> itemElements = proc.getItemList(docInfos);
Assert.assertNotNull(itemElements);
Assert.assertEquals(4, itemElements.size());
}
@Test
public void testImport_OpenOLATTest_process() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("oo_test_qti.xml");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(4, items.size());
dbInstance.commitAndCloseSession();
//check
int sc = 0;
int mc = 0;
int kprim = 0;
int fib = 0;
for(QuestionItem item:items) {
Assert.assertEquals(QTIConstants.QTI_12_FORMAT, item.getFormat());
QItemType itemType = item.getType();
Assert.assertNotNull(itemType);
QuestionType type = QuestionType.valueOf(itemType.getType().toUpperCase());
if(type != null) {
switch(type) {
case SC: sc++; break;
case MC: mc++; break;
case KPRIM: kprim++; break;
case FIB: fib++; break;
default: {
Assert.fail("No question type");
}
}
}
}
Assert.assertEquals("1 single choice", 1, sc);
Assert.assertEquals("1 multiple choice", 1, mc);
Assert.assertEquals("1 krpim", 1, kprim);
Assert.assertEquals("1 fill-in-blanck", 1, fib);
//check the files
for(QuestionItem item:items) {
QuestionItemFull itemFull = (QuestionItemFull)item;
String dir = itemFull.getDirectory();
String file = itemFull.getRootFilename();
VFSContainer itemContainer = qpoolFileStorage.getContainer(dir);
Assert.assertNotNull(itemContainer);
VFSItem itemLeaf = itemContainer.resolve(file);
Assert.assertNotNull(itemLeaf);
Assert.assertTrue(itemLeaf instanceof VFSLeaf);
//try to parse it
InputStream is = ((VFSLeaf)itemLeaf).getInputStream();
XMLParser xmlParser = new XMLParser(new IMSEntityResolver());
Document doc = xmlParser.parse(is, false);
Node itemNode = doc.selectSingleNode("questestinterop/item");
Assert.assertNotNull(itemNode);
}
}
@Test
public void testImport_OpenOLATTest_processAttachments_mattext() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("oo_test_qti_attachments.zip");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(2, items.size());
dbInstance.commitAndCloseSession();
//check the files
for(QuestionItem item:items) {
QuestionItemFull itemFull = (QuestionItemFull)item;
String dir = itemFull.getDirectory();
String file = itemFull.getRootFilename();
VFSContainer itemContainer = qpoolFileStorage.getContainer(dir);
Assert.assertNotNull(itemContainer);
VFSItem itemLeaf = itemContainer.resolve(file);
Assert.assertNotNull(itemLeaf);
Assert.assertTrue(itemLeaf instanceof VFSLeaf);
//try to parse it
InputStream is = ((VFSLeaf)itemLeaf).getInputStream();
XMLParser xmlParser = new XMLParser(new IMSEntityResolver());
Document doc = xmlParser.parse(is, false);
Node itemNode = doc.selectSingleNode("questestinterop/item");
Assert.assertNotNull(itemNode);
//check the attachments
if(itemFull.getType().getType().equalsIgnoreCase(QuestionType.SC.name())) {
Assert.assertTrue(exists(itemFull, "media/image1.gif"));
Assert.assertTrue(exists(itemFull, "media/image2.gif"));
Assert.assertFalse(exists(itemFull, "media/image3.gif"));
} else if(itemFull.getType().getType().equalsIgnoreCase(QuestionType.MC.name())) {
Assert.assertFalse(exists(itemFull, "media/image1.gif"));
Assert.assertTrue(exists(itemFull, "media/image2.gif"));
Assert.assertTrue(exists(itemFull, "media/image3.gif"));
} else {
Assert.fail();
}
}
}
@Test
public void testImport_QTI12_processAttachments_matimg() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("mchc_asmimr_106.zip");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(3, items.size());
dbInstance.commitAndCloseSession();
//check the files
for(QuestionItem item:items) {
QuestionItemFull itemFull = (QuestionItemFull)item;
String dir = itemFull.getDirectory();
String file = itemFull.getRootFilename();
VFSContainer itemContainer = qpoolFileStorage.getContainer(dir);
Assert.assertNotNull(itemContainer);
VFSItem itemLeaf = itemContainer.resolve(file);
Assert.assertNotNull(itemLeaf);
Assert.assertTrue(itemLeaf instanceof VFSLeaf);
//try to parse it
InputStream is = ((VFSLeaf)itemLeaf).getInputStream();
XMLParser xmlParser = new XMLParser(new IMSEntityResolver());
Document doc = xmlParser.parse(is, false);
Node itemNode = doc.selectSingleNode("questestinterop/item");
Assert.assertNotNull(itemNode);
//check the attachments
if("Rivers in Germany".equals(itemFull.getTitle())) {
Assert.assertFalse(exists(itemFull, "image1.gif"));
Assert.assertFalse(exists(itemFull, "image2.gif"));
Assert.assertFalse(exists(itemFull, "image3.gif"));
Assert.assertTrue(exists(itemFull, "images/image02.jpg"));
} else if("Capital of France".equals(itemFull.getTitle())) {
Assert.assertTrue(exists(itemFull, "image1.gif"));
Assert.assertTrue(exists(itemFull, "image2.gif"));
Assert.assertFalse(exists(itemFull, "image3.gif"));
Assert.assertFalse(exists(itemFull, "images/image02.jpg"));
} else if("Rivers in France question".equals(itemFull.getTitle())) {
Assert.assertFalse(exists(itemFull, "image1.gif"));
Assert.assertTrue(exists(itemFull, "image2.gif"));
Assert.assertTrue(exists(itemFull, "image3.gif"));
Assert.assertFalse(exists(itemFull, "images/image02.jpg"));
} else {
Assert.fail();
}
}
}
@Test
public void testImport_QTI12_multipleItems() throws IOException, URISyntaxException {
URL itemsUrl = QTIImportProcessorTest.class.getResource("multiple_items.zip");
Assert.assertNotNull(itemsUrl);
File itemFile = new File(itemsUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(2, items.size());
dbInstance.commitAndCloseSession();
//check the files
for(QuestionItem item:items) {
QuestionItemFull itemFull = (QuestionItemFull)item;
String dir = itemFull.getDirectory();
String file = itemFull.getRootFilename();
VFSContainer itemContainer = qpoolFileStorage.getContainer(dir);
Assert.assertNotNull(itemContainer);
VFSItem itemLeaf = itemContainer.resolve(file);
Assert.assertNotNull(itemLeaf);
Assert.assertTrue(itemLeaf instanceof VFSLeaf);
//try to parse it
InputStream is = ((VFSLeaf)itemLeaf).getInputStream();
XMLParser xmlParser = new XMLParser(new IMSEntityResolver());
Document doc = xmlParser.parse(is, false);
Node itemNode = doc.selectSingleNode("questestinterop/item");
Assert.assertNotNull(itemNode);
//check the attachments
if("Export (blue)".equals(itemFull.getTitle())) {
Assert.assertTrue(exists(itemFull, "media/blue.png"));
Assert.assertFalse(exists(itemFull, "media/purple.png"));
} else if("Export (purple)".equals(itemFull.getTitle())) {
Assert.assertFalse(exists(itemFull, "media/blue.png"));
Assert.assertTrue(exists(itemFull, "media/purple.png"));
} else {
Assert.fail();
}
}
}
@Test
public void testImport_QTI12_metadata() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("mchc_i_001.xml");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(1, items.size());
dbInstance.commitAndCloseSession();
//check metadata
QuestionItem item = items.get(0);
Assert.assertEquals("Standard Multiple Choice Item", item.getTitle());
//qmd_levelofdifficulty
QEducationalContext level = item.getEducationalContext();
Assert.assertNotNull(level);
Assert.assertEquals("basic", level.getLevel());
//qmd_toolvendor
Assert.assertEquals("QTITools", item.getEditor());
}
@Test
public void testImport_QTI12_sidecarMetadata() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("qitem_metadatas.zip");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
Assert.assertEquals(1, items.size());
dbInstance.commitAndCloseSession();
//reload and check metadata
QuestionItem item = questionItemDao.loadById(items.get(0).getKey());
Assert.assertEquals("Une information en plus", item.getAdditionalInformations());
Assert.assertEquals("formative", item.getAssessmentType());
Assert.assertEquals("large", item.getCoverage());
Assert.assertEquals(0, new BigDecimal("-0.1").compareTo(item.getDifferentiation()));
Assert.assertEquals(0, new BigDecimal("0.45").compareTo(item.getDifficulty()));
Assert.assertEquals("OpenOLAT", item.getEditor());
Assert.assertEquals("9.4", item.getEditorVersion());
QEducationalContext level = item.getEducationalContext();
Assert.assertNotNull(level);
Assert.assertEquals("University", level.getLevel());
Assert.assertEquals("P5DT4H3M2S", item.getEducationalLearningTime());
Assert.assertEquals("IMS QTI 1.2", item.getFormat());
Assert.assertEquals("6bae65ac-f333-40ba-bdd0-13b54d016d59", item.getMasterIdentifier());
Assert.assertFalse("6bae65ac-f333-40ba-bdd0-13b54d016d59".equals(item.getIdentifier()));
Assert.assertEquals("sc", item.getItemType());
Assert.assertEquals("1.01", item.getItemVersion());
Assert.assertEquals("question export import Pluton", item.getKeywords());
Assert.assertEquals("de", item.getLanguage());
Assert.assertEquals(1, item.getNumOfAnswerAlternatives());
Assert.assertNotNull(item.getQuestionStatus());
Assert.assertEquals("review", item.getQuestionStatus().name());
Assert.assertEquals(0, new BigDecimal("0.56").compareTo(item.getStdevDifficulty()));
Assert.assertEquals("/Physique/Astronomie/Astrophysique", item.getTaxonomicPath());
Assert.assertEquals("Une question sur Pluton", item.getTitle());
Assert.assertEquals(0, item.getUsage());
}
@Test
public void testImport_QTI12_film() throws IOException, URISyntaxException {
URL itemUrl = QTIImportProcessorTest.class.getResource("sc_with_film.xml");
Assert.assertNotNull(itemUrl);
File itemFile = new File(itemUrl.toURI());
//get the document informations
QTIImportProcessor proc = new QTIImportProcessor(owner, Locale.ENGLISH, itemFile.getName(), itemFile,
questionItemDao, qItemTypeDao, qEduContextDao, taxonomyLevelDao, qLicenseDao, qpoolFileStorage, dbInstance);
List<QuestionItem> items = proc.process();
Assert.assertNotNull(items);
List<DocInfos> docInfoList = proc.getDocInfos();
Assert.assertNotNull(docInfoList);
Assert.assertEquals(1, docInfoList.size());
DocInfos docInfos = docInfoList.get(0);
List<ItemInfos> itemInfos = proc.getItemList(docInfos);
Assert.assertNotNull(itemInfos);
Assert.assertEquals(1, itemInfos.size());
Element el = itemInfos.get(0).getItemEl();
List<String> materials = proc.getMaterials(el);
Assert.assertNotNull(materials);
Assert.assertEquals(1, materials.size());
Assert.assertEquals("media/filmH264.mp4", materials.get(0));
}
private boolean exists(QuestionItemFull itemFull, String path) {
String dir = itemFull.getDirectory();
VFSContainer itemContainer = qpoolFileStorage.getContainer(dir);
Assert.assertNotNull(itemContainer);
VFSItem itemLeaf = itemContainer.resolve(path);
return (itemLeaf instanceof VFSLeaf);
}
}