/** * <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.course.editor; import java.io.File; import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.UUID; import org.junit.Assert; import org.junit.Test; import org.olat.core.id.Identity; import org.olat.core.util.FileUtils; import org.olat.core.util.StringHelper; import org.olat.core.util.nodes.INode; import org.olat.course.CourseFactory; import org.olat.course.CourseModule; import org.olat.course.ICourse; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.CourseNode; import org.olat.course.tree.CourseEditorTreeModel; import org.olat.course.tree.CourseEditorTreeNode; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryImportExport; import org.olat.repository.RepositoryService; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; import org.springframework.beans.factory.annotation.Autowired; /** * These unit tests are related to OO-249. the main goal is to make the * publish process robust against the different AssertException and to * handle to possible corruption without triggering a red screen which * make the course corrupt, uneditable, lost. * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ public class PublishProcessTest extends OlatTestCase { private static final Locale locale = Locale.GERMAN; @Autowired private OLATResourceManager olatResourceManager; @Autowired private RepositoryService repositoryService; /** * Publish process without error * @throws URISyntaxException */ @Test public void testPublishProcess() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course.zip"); //change node 1 ICourse course = CourseFactory.openCourseEditSession(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); CourseEditorTreeNode node1 = (CourseEditorTreeNode)cetm.getRootNode().getChildAt(0); node1.getCourseNode().setShortTitle("Node 1 prime"); cetm.nodeConfigChanged(node1); CourseFactory.saveCourseEditorTreeModel(course.getResourceableId()); CourseFactory.closeCourseEditSession(course.getResourceableId(), true); //publish the course List<String> nodeIds = Collections.singletonList(node1.getIdent()); publishCourse(nodeIds, re, author); //check the change ICourse reloadedCourse = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); Assert.assertEquals(3, reloadedCourse.getRunStructure().getRootNode().getChildCount()); INode runNode1 = reloadedCourse.getRunStructure().getRootNode().getChildAt(0); Assert.assertNotNull(runNode1); CourseNode runNode1Impl = (CourseNode)runNode1; Assert.assertEquals("Node 1 prime", runNode1Impl.getShortTitle()); } /** * Publish an unchanged course. We try to publish a node which * was not changed nor deleted. * * @throws URISyntaxException */ @Test public void testPublishANotPublishedNode() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course.zip"); //change node 1 ICourse course = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); CourseEditorTreeNode node1 = (CourseEditorTreeNode)cetm.getRootNode().getChildAt(0); //publish the course and must survive this without exception //as the course has no changes but we try to publish it List<String> nodeIds = Collections.singletonList(node1.getIdent()); publishCourse(nodeIds, re, author); } /** * Publish a course with a node marked as new but the node * exists already in the run structure. * * @throws URISyntaxException */ @Test public void testPublishANotReallyNewNode() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course_err1_new.zip"); //change node 1 ICourse course = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); CourseEditorTreeNode node1 = (CourseEditorTreeNode)cetm.getRootNode().getChildAt(0); //publish the course and must survive this without exception //as the course has no changes but we try to publish it List<String> nodeIds = Collections.singletonList(node1.getIdent()); publishCourse(nodeIds, re, author); //check the change ICourse reloadedCourse = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); Assert.assertEquals(3, reloadedCourse.getRunStructure().getRootNode().getChildCount()); INode runNode1 = reloadedCourse.getRunStructure().getRootNode().getChildAt(0); Assert.assertNotNull(runNode1); CourseNode runNode1Impl = (CourseNode)runNode1; Assert.assertEquals("Node 1 not really new", runNode1Impl.getShortTitle()); } /** * Publish a course with a node marked as new and deleted but the * node exists already in the run structure. * * @throws URISyntaxException */ @Test public void testPublishANotReallyNewNodeButDeleted() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course_err2_new_deleted.zip"); //change node 1 ICourse course = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); CourseEditorTreeNode node1 = (CourseEditorTreeNode)cetm.getRootNode().getChildAt(0); //publish the course and must survive this without exception //as the course has no changes but we try to publish it List<String> nodeIds = Collections.singletonList(node1.getIdent()); publishCourse(nodeIds, re, author); //check the change ICourse reloadedCourse = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); Assert.assertEquals(2, reloadedCourse.getRunStructure().getRootNode().getChildCount()); INode runNode2 = reloadedCourse.getRunStructure().getRootNode().getChildAt(0); Assert.assertNotNull(runNode2); CourseNode runNode2Impl = (CourseNode)runNode2; Assert.assertEquals("Node 2", runNode2Impl.getShortTitle()); } /** * Publish a course with a node marked as not new but the * node dosn't exist in the run structure. * * @throws URISyntaxException */ @Test public void testPublishNewNodeButNotMarkedAsSuch() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course_err3_not_new.zip"); //change node 1 ICourse course = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); CourseEditorTreeNode node1 = (CourseEditorTreeNode)cetm.getRootNode().getChildAt(0); //publish the course and must survive this without exception //as the course has no changes but we try to publish it List<String> nodeIds = Collections.singletonList(node1.getIdent()); publishCourse(nodeIds, re, author); //check the change ICourse reloadedCourse = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); Assert.assertEquals(3, reloadedCourse.getRunStructure().getRootNode().getChildCount()); INode runNode1 = reloadedCourse.getRunStructure().getRootNode().getChildAt(0); Assert.assertNotNull(runNode1); CourseNode runNode1Impl = (CourseNode)runNode1; Assert.assertEquals("Node 1 from hell", runNode1Impl.getShortTitle()); } /** * Publish a course with a node marked as not new and deleted but * the node doesn't exist in the run structure. * * @throws URISyntaxException */ @Test public void testPublishNewNodeButNotMarkedAsSuchAndDeleted() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course_err4_not_new_deleted.zip"); //change node 1 ICourse course = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); CourseEditorTreeNode node1 = (CourseEditorTreeNode)cetm.getRootNode().getChildAt(0); //publish the course and must survive this without exception //as the course has no changes but we try to publish it List<String> nodeIds = Collections.singletonList(node1.getIdent()); publishCourse(nodeIds, re, author); //check the change ICourse reloadedCourse = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); Assert.assertEquals(2, reloadedCourse.getRunStructure().getRootNode().getChildCount()); INode runNode1 = reloadedCourse.getRunStructure().getRootNode().getChildAt(0); Assert.assertNotNull(runNode1); CourseNode runNode1Impl = (CourseNode)runNode1; Assert.assertEquals("Node 2", runNode1Impl.getShortTitle()); } /** * Publish a course with a node marked as not new but * the node doesn't exist in the run structure. The node * itself is not published. * * @throws URISyntaxException */ @Test public void testPublishNewNodeNotMarkedAsSuchAndNotPublished() throws URISyntaxException { Identity author = JunitTestHelper.createAndPersistIdentityAsAdmin("publisher-" + UUID.randomUUID().toString()); RepositoryEntry re = deployTestCourse("simple_course_err5_not_new_or_published.zip"); //change node 1 ICourse course = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); //publish the course and must survive this without exception //as the course has no changes but we try to publish it List<String> nodeIds = Collections.singletonList(cetm.getRootNode().getIdent()); publishCourse(nodeIds, re, author); //check the change ICourse reloadedCourse = CourseFactory.loadCourse(re.getOlatResource().getResourceableId()); Assert.assertEquals(2, reloadedCourse.getRunStructure().getRootNode().getChildCount()); INode runNode1 = reloadedCourse.getRunStructure().getRootNode().getChildAt(0); Assert.assertNotNull(runNode1); CourseNode runNode1Impl = (CourseNode)runNode1; Assert.assertEquals("Node 2", runNode1Impl.getShortTitle()); } private void publishCourse(List<String> nodeIds, RepositoryEntry re, Identity author) { ICourse course = CourseFactory.openCourseEditSession(re.getOlatResource().getResourceableId()); CourseEditorTreeModel cetm = course.getEditorTreeModel(); PublishProcess pp = PublishProcess.getInstance(course, cetm, locale); // create publish node list pp.createPublishSetFor(nodeIds); PublishSetInformations set = pp.testPublishSet(locale); StatusDescription[] sds = set.getWarnings(); Assert.assertNotNull(sds); Assert.assertEquals(0, sds.length); pp.applyPublishSet(author, locale, true); CourseFactory.closeCourseEditSession(course.getResourceableId(), true); } private RepositoryEntry deployTestCourse(String filename) throws URISyntaxException { URL courseUrl = PublishProcessTest.class.getResource(filename); Assert.assertNotNull(courseUrl); File courseFile = new File(courseUrl.toURI()); //deploy a course String softKey = UUID.randomUUID().toString().replace("-", "").substring(0, 30); RepositoryEntry re = deployRawCourseFromZIP(courseFile, softKey, 1); Assert.assertNotNull(re); return re; } /** * This method copy the edtiro tree model and run structure as is during the import. * @param exportedCourseZIPFile * @param softKey * @param access * @return */ private RepositoryEntry deployRawCourseFromZIP(File exportedCourseZIPFile, String softKey, int access) { // create the course instance OLATResource newCourseResource = olatResourceManager.createOLATResourceInstance(CourseModule.class); ICourse course = CourseFactory.importCourseFromZip(newCourseResource, exportedCourseZIPFile); // course is now also in course cache! if (course == null) { return null; } File courseExportData = course.getCourseExportDataDir().getBasefile(); // get the export data directory // create the repository entry RepositoryEntryImportExport importExport = new RepositoryEntryImportExport(courseExportData); if(!StringHelper.containsNonWhitespace(softKey)) { softKey = importExport.getSoftkey(); } RepositoryEntry re = repositoryService.create(importExport.getInitialAuthor(), importExport.getResourceName(), importExport.getDisplayName(), importExport.getDescription(), newCourseResource); // ok, continue import re.setSoftkey(softKey); // set access configuration re.setAccess(access); // save the repository entry re = repositoryService.update(re); //import groups course = CourseFactory.openCourseEditSession(course.getResourceableId()); // create group management CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager(); // import groups cgm.importCourseBusinessGroups(courseExportData); CourseFactory.closeCourseEditSession(course.getResourceableId(), true); // cleanup export data FileUtils.deleteDirsAndFiles(courseExportData, true, true); return re; } }