/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.orders;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE;
import static org.libreplan.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE;
import static org.libreplan.web.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_FILE;
import static org.libreplan.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE;
import static org.libreplan.web.test.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Resource;
import org.easymock.EasyMock;
import org.hibernate.SessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.libreplan.business.IDataBootstrap;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.externalcompanies.daos.IExternalCompanyDAO;
import org.libreplan.business.externalcompanies.entities.ExternalCompany;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.daos.ITaskSourceDAO;
import org.libreplan.business.requirements.entities.CriterionRequirement;
import org.libreplan.business.requirements.entities.DirectCriterionRequirement;
import org.libreplan.business.resources.daos.ICriterionDAO;
import org.libreplan.business.resources.daos.ICriterionTypeDAO;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionType;
import org.libreplan.business.resources.entities.ResourceEnum;
import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.web.calendars.BaseCalendarModel;
import org.libreplan.web.planner.order.PlanningStateCreator;
import org.libreplan.web.planner.order.PlanningStateCreator.PlanningState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.zk.ui.Desktop;
/**
* Tests for {@link OrderModel}.
* <br />
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE,
WEBAPP_SPRING_CONFIG_FILE, WEBAPP_SPRING_CONFIG_TEST_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_FILE,
WEBAPP_SPRING_SECURITY_CONFIG_TEST_FILE })
/**
* This annotation drops context and force it to reload.
* Action described above prevents tests from falling
* due to "Row was updated or deleted by another transaction" exception.
* Also this trick clears cache and that's why there is no troubles with commands caching
* in the PlanningState.getSaveCommand() method.
*/
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class OrderModelTest {
public static OrderVersion setupVersionUsing(IScenarioManager scenarioManager, Order order) {
Scenario current = scenarioManager.getCurrent();
OrderVersion result = OrderVersion.createInitialVersion(current);
order.setVersionForScenario(current, result);
return result;
}
@Resource
private IDataBootstrap defaultAdvanceTypesBootstrapListener;
@Resource
private IDataBootstrap configurationBootstrap;
@Resource
private IDataBootstrap scenariosBootstrap;
@BeforeTransaction
public void loadRequiredData() {
defaultAdvanceTypesBootstrapListener.loadRequiredData();
configurationBootstrap.loadRequiredData();
scenariosBootstrap.loadRequiredData();
}
public static Date year(int year) {
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.set(Calendar.YEAR, year);
return calendar.getTime();
}
@Autowired
private IOrderModel orderModel;
@Autowired
private IOrderDAO orderDAO;
@Autowired
private ITaskSourceDAO taskSourceDAO;
@Autowired
private ICriterionTypeDAO criterionTypeDAO;
@Autowired
private SessionFactory sessionFactory;
@Autowired
private IAdHocTransactionService adHocTransaction;
@Autowired
private ICriterionDAO criterionDAO;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IExternalCompanyDAO externalCompanyDAO;
@Autowired
private PlanningStateCreator planningStateCreator;
private Criterion criterion;
private Desktop mockDesktop() {
return EasyMock.createNiceMock(Desktop.class);
}
private PlanningState createPlanningStateFor(final Order newOrder) {
return adHocTransaction.runOnAnotherReadOnlyTransaction(new IOnTransaction<PlanningState>() {
@Override
public PlanningState execute() {
return planningStateCreator.createOn(mockDesktop(), newOrder);
}
});
}
private Order createValidOrder() {
Order order = Order.create();
order.setDescription("description");
order.setInitDate(year(2000));
order.setName("name");
order.setResponsible("responsible");
order.setCode("code-" + UUID.randomUUID());
BaseCalendar calendar = adHocTransaction.runOnReadOnlyTransaction(new IOnTransaction<BaseCalendar>() {
@Override
public BaseCalendar execute() {
BaseCalendar result =
configurationDAO.getConfigurationWithReadOnlyTransaction().getDefaultCalendar();
BaseCalendarModel.forceLoadBaseCalendar(result);
return result;
}
});
order.setCalendar(calendar);
return order;
}
private ExternalCompany createValidExternalCompany() {
ExternalCompany externalCompany = ExternalCompany.create(
UUID.randomUUID().toString(),
UUID.randomUUID().toString());
externalCompanyDAO.save(externalCompany);
return externalCompany;
}
@Test
@Transactional
@Rollback(false)
public void testNotRollback() {
// Just to do not make rollback in order to have the default
// configuration, needed for prepareForCreate in order to autogenerate the order code
}
@Test
@Transactional
public void testCreation() throws ValidationException {
Order order = createValidOrder();
order.setCustomer(createValidExternalCompany());
orderModel.setPlanningState(createPlanningStateFor(order));
orderModel.save();
assertTrue(orderDAO.exists(order.getId()));
}
private Order givenOrderFromPrepareForCreate() {
adHocTransaction.runOnAnotherReadOnlyTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
orderModel.prepareForCreate(mockDesktop());
return null;
}
});
Order order = orderModel.getOrder();
order.setName("name");
order.setCode("code");
order.setInitDate(new Date());
order.setCustomer(createValidExternalCompany());
return order;
}
@Test
@Transactional
public void testCreationUsingPrepareForCreate() {
Order order = givenOrderFromPrepareForCreate();
orderModel.save();
assertTrue(orderDAO.exists(order.getId()));
}
@Test
@Transactional
public void createOrderWithScheduledOrderLine() {
Order order = givenOrderFromPrepareForCreate();
OrderElement line = OrderLine.createOrderLineWithUnfixedPercentage(20);
order.add(line);
line.setName(UUID.randomUUID().toString());
line.setCode(UUID.randomUUID().toString());
assert line.getSchedulingState().isSomewhatScheduled();
orderModel.save();
assertTrue(orderDAO.exists(order.getId()));
TaskSource lineTaskSource = line.getTaskSource();
assertTrue(taskSourceDAO.exists(lineTaskSource.getId()));
}
@Test
@Transactional
public void ifAnOrderLineIsScheduledItsTypeChanges() {
Order order = givenOrderFromPrepareForCreate();
OrderElement line = OrderLine.createOrderLineWithUnfixedPercentage(20);
line.useSchedulingDataFor(order.getCurrentOrderVersion());
line.getSchedulingState().unschedule();
order.add(line);
assertFalse(order.getSchedulingState().isSomewhatScheduled());
line.getSchedulingState().schedule();
assertTrue(order.getSchedulingState().isSomewhatScheduled());
}
@Test
@Transactional
public void testListing() {
List<Order> orderList = orderDAO.getOrders();
Order newOrder = createValidOrder();
orderDAO.save(newOrder);
assertThat(orderDAO.getOrders().size(), equalTo(orderList.size() + 1));
}
@Test
@Transactional
public void testRemove() {
Order order = createValidOrder();
orderModel.setPlanningState(createPlanningStateFor(order));
orderModel.save();
assertTrue(orderDAO.exists(order.getId()));
orderModel.remove(order);
assertFalse(orderDAO.exists(order.getId()));
}
@Test(expected = ValidationException.class)
@Transactional
public void shouldSendValidationExceptionIfEndDateIsBeforeThanStartingDate() throws ValidationException {
Order order = createValidOrder();
order.setDeadline(year(0));
orderModel.setPlanningState(createPlanningStateFor(order));
orderModel.save();
}
@Test
@Transactional
public void testFind() throws InstanceNotFoundException {
Order order = createValidOrder();
orderModel.setPlanningState(createPlanningStateFor(order));
orderModel.save();
assertThat(orderDAO.find(order.getId()), notNullValue());
}
@Test
@Transactional
public void testOrderPreserved() throws ValidationException, InstanceNotFoundException {
final Order order = createValidOrder();
orderModel.setPlanningState(createPlanningStateFor(order));
final OrderElement[] containers = new OrderLineGroup[10];
for (int i = 0; i < containers.length; i++) {
containers[i] = adHocTransaction.runOnTransaction(new IOnTransaction<OrderLineGroup>() {
@Override
public OrderLineGroup execute() {
return OrderLineGroup.create();
}
});
containers[i].setName("bla");
containers[i].setCode("code-" + UUID.randomUUID());
order.add(containers[i]);
}
OrderLineGroup container = (OrderLineGroup) containers[0];
final OrderElement[] orderElements = new OrderElement[10];
for (int i = 0; i < orderElements.length; i++) {
OrderLine leaf = createValidLeaf("bla");
orderElements[i] = leaf;
container.add(leaf);
}
for (int i = 1; i < containers.length; i++) {
OrderLineGroup orderLineGroup = (OrderLineGroup) containers[i];
OrderLine leaf = createValidLeaf("foo");
orderLineGroup.add(leaf);
}
orderModel.save();
adHocTransaction.runOnTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
try {
Order reloaded = orderDAO.find(order.getId());
List<OrderElement> elements = reloaded.getOrderElements();
for (OrderElement orderElement : elements) {
assertThat(orderElement.getIndirectAdvanceAssignments().size(), equalTo(2));
}
for (int i = 0; i < containers.length; i++) {
assertThat(elements.get(i).getId(), equalTo(containers[i].getId()));
}
OrderLineGroup container = (OrderLineGroup) reloaded.getOrderElements().iterator().next();
List<OrderElement> children = container.getChildren();
for (int i = 0; i < orderElements.length; i++) {
assertThat(children.get(i).getId(), equalTo(orderElements[i].getId()));
}
for (int i = 1; i < containers.length; i++) {
OrderLineGroup orderLineGroup = (OrderLineGroup) containers[i];
assertThat(orderLineGroup.getChildren().size(), equalTo(1));
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
orderModel.remove(order);
}
private OrderLine createValidLeaf(String parameter) {
OrderLine result = OrderLine.create();
result.setName(parameter);
result.setCode("code-" + UUID.randomUUID());
HoursGroup hoursGroup = HoursGroup.create(result);
hoursGroup.setCode("hoursGroupCode-" + UUID.randomUUID());
hoursGroup.setWorkingHours(0);
result.addHoursGroup(hoursGroup);
return result;
}
@Test
public void testAddingOrderElement() {
defaultAdvanceTypesBootstrapListener.loadRequiredData();
configurationBootstrap.loadRequiredData();
scenariosBootstrap.loadRequiredData();
final Order order = createValidOrder();
orderModel.setPlanningState(createPlanningStateFor(order));
OrderLineGroup container = adHocTransaction.runOnTransaction(new IOnTransaction<OrderLineGroup>() {
@Override
public OrderLineGroup execute() {
return OrderLineGroup.create();
}
});
order.add(container);
container.setName("bla");
container.setCode("code-" + UUID.randomUUID());
OrderLine leaf = OrderLine.create();
leaf.setName("leaf");
leaf.setCode("code-" + UUID.randomUUID());
container.add(leaf);
HoursGroup hoursGroup = HoursGroup.create(leaf);
hoursGroup.setCode("hoursGroupName");
hoursGroup.setWorkingHours(3);
leaf.addHoursGroup(hoursGroup);
orderModel.save();
adHocTransaction.runOnTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
try {
Order reloaded = orderDAO.find(order.getId());
assertFalse(order == reloaded);
assertThat(reloaded.getOrderElements().size(), equalTo(1));
OrderLineGroup containerReloaded = (OrderLineGroup) reloaded
.getOrderElements().get(0);
assertThat(containerReloaded.getHoursGroups().size(),
equalTo(1));
assertThat(containerReloaded.getChildren().size(),
equalTo(1));
OrderElement leaf = containerReloaded.getChildren().get(0);
assertThat(leaf.getHoursGroups().size(), equalTo(1));
orderModel.remove(order);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return null;
}
});
}
@Test
@Transactional
public void testManyToManyHoursGroupCriterionMapping() {
givenCriterion();
final Order order = createValidOrder();
orderModel.setPlanningState(createPlanningStateFor(order));
OrderLine orderLine = OrderLine.create();
orderLine.setName("Order element");
orderLine.setCode("000000000");
order.add(orderLine);
HoursGroup hoursGroup = HoursGroup.create(orderLine);
hoursGroup.setCode("hoursGroupName");
hoursGroup.setWorkingHours(10);
HoursGroup hoursGroup2 = HoursGroup.create(orderLine);
hoursGroup2.setCode("hoursGroupName2");
hoursGroup2.setWorkingHours(5);
orderLine.addHoursGroup(hoursGroup);
CriterionRequirement criterionRequirement = DirectCriterionRequirement.create(criterion);
hoursGroup.addCriterionRequirement(criterionRequirement);
orderModel.save();
adHocTransaction.runOnTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
try {
sessionFactory.getCurrentSession().flush();
Order reloaded = orderDAO.find(order.getId());
List<OrderElement> orderElements = reloaded.getOrderElements();
assertThat(orderElements.size(), equalTo(1));
List<HoursGroup> hoursGroups = orderElements.get(0).getHoursGroups();
assertThat(hoursGroups.size(), equalTo(1));
Set<CriterionRequirement> criterionRequirements = hoursGroups.get(0).getCriterionRequirements();
assertThat(criterionRequirements.size(), equalTo(1));
Set<Criterion> criterions = hoursGroups.get(0).getValidCriterions();
assertThat(criterions.size(), equalTo(1));
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
return null;
}
});
}
private void givenCriterion() throws ValidationException {
this.criterion = adHocTransaction.runOnTransaction(new IOnTransaction<Criterion>() {
@Override
public Criterion execute() {
CriterionType criterionType = CriterionType.create(
"test" + UUID.randomUUID(), "");
criterionType.setResource(ResourceEnum.WORKER);
criterionTypeDAO.save(criterionType);
Criterion criterion = Criterion.create("Test"
+ UUID.randomUUID(), criterionType);
try {
criterionDAO.save(criterion);
} catch (ValidationException e) {
throw new RuntimeException(e);
}
return criterion;
}
});
this.criterion.dontPoseAsTransientObjectAnymore();
this.criterion.getType().dontPoseAsTransientObjectAnymore();
}
@Test(expected = ValidationException.class)
@Transactional
public void testAtLeastOneHoursGroup() {
Order order = createValidOrder();
orderModel.setPlanningState(createPlanningStateFor(order));
OrderLine orderLine = OrderLine.create();
orderLine.setName(randomize("foo" + new Random().nextInt()));
orderLine.setCode(randomize("000000000"));
order.add(orderLine);
orderModel.save();
}
private static String randomize(String original) {
return original + new Random().nextInt();
}
}