View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.routeheader;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.joda.time.DateTime;
20  import org.junit.Test;
21  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
22  import org.kuali.rice.kew.api.KewApiConstants;
23  import org.kuali.rice.kew.api.KewApiServiceLocator;
24  import org.kuali.rice.kew.api.WorkflowDocument;
25  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
26  import org.kuali.rice.kew.api.WorkflowRuntimeException;
27  import org.kuali.rice.kew.api.action.ActionRequest;
28  import org.kuali.rice.kew.api.action.ActionRequestType;
29  import org.kuali.rice.kew.api.action.RequestedActions;
30  import org.kuali.rice.kew.api.document.Document;
31  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
32  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
33  import org.kuali.rice.kew.docsearch.TestXMLSearchableAttributeString;
34  import org.kuali.rice.kew.docsearch.service.DocumentSearchService;
35  import org.kuali.rice.kew.doctype.ApplicationDocumentStatus;
36  import org.kuali.rice.kew.doctype.bo.DocumentType;
37  import org.kuali.rice.kew.doctype.service.DocumentTypeService;
38  import org.kuali.rice.kew.service.KEWServiceLocator;
39  import org.kuali.rice.kew.test.KEWTestCase;
40  import org.kuali.rice.kim.api.identity.Person;
41  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
42  import org.kuali.rice.krad.util.ObjectUtils;
43  
44  import java.util.List;
45  import java.util.Set;
46  
47  import static org.junit.Assert.*;
48  import static org.junit.Assert.assertEquals;
49  
50  public class AppDocStatusTest extends KEWTestCase {
51      	    
52      protected void loadTestData() throws Exception {
53      	super.loadTestData();
54          loadXmlFile("AppDocStatusTestConfig.xml");
55      }
56          
57      /**
58       * 
59       * This method performs several positive tests related to Application Document Status
60       * For these tests the doctype definition defines a valid set of statuses.
61       * It also defines two status transitions in the route path
62       * It tests:
63       * 	- That the AppDocStatus is properly set by the workflow engine during
64       *    appropriate transitions.
65       *  - That the AppDocStatus may be retrieved by the client API
66       *  - That the AppDocStatus may be set by the client API
67       *  - That a history of AppDocStatus transitions is created.
68       * 
69       */
70      @Test public void testValidAppDocStatus() throws Exception {
71      	// Create document
72      	WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestAppDocStatusDoc2");
73      	document.saveDocumentData();
74      	assertNotNull(document.getDocumentId());
75      	assertTrue("Document should be initiatied", document.isInitiated());
76      	assertTrue("Invalid route level.", document.getNodeNames().contains("Initiated"));
77      	
78      	// route document to first stop and check status, etc.
79      	document.route("Test Routing.");    	
80      	String appDocStatus = document.getDocument().getApplicationDocumentStatus();
81      	assertTrue("Application Document Status:" + appDocStatus +" is invalid", "Approval in Progress".equalsIgnoreCase(appDocStatus));
82          
83          // should have generated a request to "bmcgough"
84      	document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), document.getDocumentId());
85          assertTrue("Document should be enroute", document.isEnroute());
86      	Set<String> nodeNames = document.getNodeNames();
87      	assertEquals("Wrong number of node names.", 1, nodeNames.size());
88      	assertTrue("Wrong node name.", document.getNodeNames().contains("DestinationApproval"));
89  
90      	// check action request
91          List<ActionRequest> requests = document.getRootActionRequests();
92          assertEquals(1, requests.size());
93          ActionRequest request = requests.get(0);
94          assertEquals(getPrincipalIdForName("bmcgough"), request.getPrincipalId());
95          assertEquals(ActionRequestType.APPROVE, request.getActionRequested());
96          assertEquals("DestinationApproval", request.getNodeName());
97          assertTrue(document.isApprovalRequested());
98          
99          // approve the document to send it to its next route node
100         document.approve("Test approve by bmcgough");
101         
102         // check status 
103         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("temay"), document.getDocumentId());
104         Document rh = document.getDocument();
105     	appDocStatus = rh.getApplicationDocumentStatus();
106     	assertTrue("Application Document Status:" + appDocStatus +" is invalid", "Submitted".equalsIgnoreCase(appDocStatus));
107         
108         // should have generated a request to "temay"
109     	assertTrue("Document should be enroute", document.isEnroute());
110     	nodeNames = document.getNodeNames();
111     	assertEquals("Wrong number of node names.", 1, nodeNames.size());
112     	assertTrue("Wrong node name.", nodeNames.contains("TravelerApproval"));
113     	document.approve("Test approve by temay");
114     	
115     	// update the AppDocStatus via client API
116         document.setApplicationDocumentStatus("Completed");
117         document.saveDocumentData();
118 
119         // get a refreshed document and check it out
120         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("temay"), document.getDocumentId());
121 //        assertTrue("Document should be processed.", document.isProcessed());        
122         rh = document.getDocument();
123     	appDocStatus = rh.getApplicationDocumentStatus();
124     	assertTrue("Application Document Status:" + appDocStatus +" is invalid", "Completed".equalsIgnoreCase(appDocStatus));
125     	
126         // check app doc status transition history
127         List<org.kuali.rice.kew.api.document.DocumentStatusTransition> history = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatusTransitionHistory(
128                 document.getDocumentId());
129         
130         assertEquals(3, history.size());
131     	assertTrue("First History record has incorrect status", "Approval In Progress".equalsIgnoreCase(history.get(0).getNewStatus()));
132     	assertTrue("Second History record has incorrect old status", "Approval In Progress".equalsIgnoreCase(
133                 history.get(1).getOldStatus()));
134     	assertTrue("Second History record has incorrect new status", "Submitted".equalsIgnoreCase(history.get(1).getNewStatus()));
135     	assertTrue("Third History record has incorrect old status", "Submitted".equalsIgnoreCase(history.get(2).getOldStatus()));
136     	assertTrue("Third History record has incorrect new status", "Completed".equalsIgnoreCase(history.get(2).getNewStatus()));
137                
138     	// TODO when we are able to, we should also verify the RouteNodeInstances are correct
139         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("ewestfal"), document.getDocumentId());
140     	assertTrue("Document should be final.", document.isFinal());
141     }        
142 
143     /**
144      * 
145      * This method is similar to the above test, except that the doctype definition
146      * does NOT specify a valid set of values.  This means that the value can be any valid string.
147      * 
148      * @throws Exception
149      */
150     @Test public void testAppDocStatusValuesNotDefined() throws Exception {
151     	// Create document
152     	WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestAppDocStatusDoc1");
153     	document.saveDocumentData();
154     	assertNotNull(document.getDocumentId());
155     	assertTrue("Document should be initiatied", document.isInitiated());
156     	assertTrue("Invalid route level.", document.getNodeNames().contains("Initiated"));
157     	
158     	// route document to first stop and check status, etc.
159     	document.route("Test Routing.");    	
160     	Document rh = document.getDocument();
161     	String appDocStatus = rh.getApplicationDocumentStatus();
162     	assertTrue("Application Document Status:" + appDocStatus +" is invalid", "Approval in Progress".equalsIgnoreCase(appDocStatus));
163         
164         // should have generated a request to "bmcgough"
165     	document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), document.getDocumentId());
166         assertTrue("Document should be enroute", document.isEnroute());
167     	Set<String> nodeNames = document.getNodeNames();
168     	assertEquals("Wrong number of node names.", 1, nodeNames.size());
169     	assertTrue("Wrong node name.", nodeNames.contains("step1"));
170 
171     	// check action request
172         List<ActionRequest> requests = document.getRootActionRequests();
173         assertEquals(1, requests.size());
174         ActionRequest request = requests.get(0);
175         assertEquals(getPrincipalIdForName("bmcgough"), request.getPrincipalId());
176         assertEquals(ActionRequestType.APPROVE, request.getActionRequested());
177         assertEquals("step1", request.getNodeName());
178         assertTrue(document.isApprovalRequested());
179         
180         // approve the document to send it to its next route node
181         document.approve("Test approve by bmcgough");
182         
183         // check status 
184         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("temay"), document.getDocumentId());
185         rh = document.getDocument();
186     	appDocStatus = rh.getApplicationDocumentStatus();
187     	assertTrue("Application Document Status:" + appDocStatus +" is invalid", "Submitted".equalsIgnoreCase(appDocStatus));
188         
189         // should have generated a request to "temay"
190     	assertTrue("Document should be enroute", document.isEnroute());
191     	nodeNames = document.getNodeNames();
192     	assertEquals("Wrong number of node names.", 1, nodeNames.size());
193     	assertTrue("Wrong node name.", nodeNames.contains("step2"));
194     	document.approve("Test approve by temay");
195     	
196     	// update the AppDocStatus via client API
197         document.setApplicationDocumentStatus("Some Random Value");
198         document.saveDocumentData();
199 
200         // get a refreshed document and check it out
201         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("temay"), document.getDocumentId());
202 //        assertTrue("Document should be processed.", document.isProcessed());        
203         rh = document.getDocument();
204     	appDocStatus = rh.getApplicationDocumentStatus();
205     	assertTrue("Application Document Status:" + appDocStatus +" is invalid", "Some Random Value".equalsIgnoreCase(appDocStatus));
206     	
207         // check app doc status transition history
208         List<org.kuali.rice.kew.api.document.DocumentStatusTransition> history = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatusTransitionHistory(
209                 document.getDocumentId());
210         
211         assertEquals(3, history.size());
212     	assertTrue("First History record has incorrect status", "Approval In Progress".equalsIgnoreCase(history.get(0)
213                 .getNewStatus()));
214     	assertTrue("Second History record has incorrect old status", "Approval In Progress".equalsIgnoreCase(
215                 history.get(1).getOldStatus()));
216     	assertTrue("Second History record has incorrect new status", "Submitted".equalsIgnoreCase(history.get(1)
217                 .getNewStatus()));
218     	assertTrue("Third History record has incorrect old status", "Submitted".equalsIgnoreCase(history.get(2).getOldStatus()));
219     	assertTrue("Third History record has incorrect new status", "Some Random Value".equalsIgnoreCase(history.get(2)
220                 .getNewStatus()));
221                
222     	// TODO when we are able to, we should also verify the RouteNodeInstances are correct
223         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("ewestfal"), document.getDocumentId());
224     	assertTrue("Document should be final.", document.isFinal());
225     }        
226 
227     /**
228      * 
229      * This test attempts to set an invalid status value for a document that has a valid set
230      * of statuses defined.
231      * It expects to throw a WorkflowRuntimeException when attempting to set the invalid status value.
232      * 
233      * @throws Exception
234      */
235     @Test public void testInvalidAppDocStatusValue() throws Exception {
236     	WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestAppDocStatusDoc2");
237     	document.saveDocumentData();
238     	assertNotNull(document.getDocumentId());
239     	assertTrue("Document should be initiatied", document.isInitiated());
240     	assertTrue("Invalid route level.", document.getNodeNames().contains("Initiated"));
241     	    	
242     	// update the AppDocStatus via client API
243     	boolean gotException = false;
244     	try {
245     		document.setApplicationDocumentStatus("BAD STATUS");
246     		document.saveDocumentData();
247     	} catch (Throwable t){
248     		gotException = true;
249     		WorkflowRuntimeException ex = new WorkflowRuntimeException();
250     		assertEquals("WrongExceptionType", t.getClass(), ex.getClass());
251     	} finally {
252     		assertTrue("Expected WorkflowRuntimeException not thrown.", gotException);
253     		
254     	}
255     }
256 
257     /**
258      *
259      * This method is similar to the above test, except that the doctype definition
260      * INHERITS a valid set of application document status values.
261      *
262      * @throws Exception
263      */
264     @Test public void testValidInheritedAppDocStatus() throws Exception {
265         // Create document
266         WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestAppDocStatusDoc3");
267         document.saveDocumentData();
268         assertNotNull(document.getDocumentId());
269         assertTrue("Document should be initiatied", document.isInitiated());
270         assertTrue("Invalid route level.", document.getNodeNames().contains("Initiated"));
271         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName("TestAppDocStatusDoc3");
272         assertTrue(ObjectUtils.isNotNull(documentType));
273         assertTrue(ObjectUtils.isNotNull(documentType.getValidApplicationStatuses()));
274         assertEquals(6,documentType.getValidApplicationStatuses().size());
275         LOG.info("valid application status size: " + documentType.getValidApplicationStatuses().size());
276         assertTrue(ObjectUtils.isNotNull(documentType.getApplicationStatusCategories()));
277         assertEquals(0,documentType.getApplicationStatusCategories().size());
278     }
279 
280     /**
281      *
282      * This method an inherited valid set of values and categories.
283      *
284      * @throws Exception
285      */
286     @Test public void testValidInheritedAppDocStatusWithCategories() throws Exception {
287         // Create document
288         WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestAppDocStatusDoc5");
289         document.saveDocumentData();
290         assertNotNull(document.getDocumentId());
291         assertTrue("Document should be initiatied", document.isInitiated());
292         assertTrue("Invalid route level.", document.getNodeNames().contains("Initiated"));
293         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName("TestAppDocStatusDoc5");
294         assertTrue(ObjectUtils.isNotNull(documentType));
295         assertTrue(ObjectUtils.isNotNull(documentType.getValidApplicationStatuses()));
296         LOG.info("valid application status size: " + documentType.getValidApplicationStatuses().size());
297         assertEquals(6,documentType.getValidApplicationStatuses().size());
298         assertTrue(ObjectUtils.isNotNull(documentType.getApplicationStatusCategories()));
299         assertEquals(2,documentType.getApplicationStatusCategories().size());
300     }
301 
302     /**
303      *
304      * This method tests a valid set of application document status values that are not inherited due to KEW status (KULRICE-8943).
305      *
306      * @throws Exception
307      */
308     @Test public void testInheritedAppDocStatusWithKEWStatus() throws Exception {
309         // Create document
310         WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestAppDocStatusDoc6");
311         document.saveDocumentData();
312         assertNotNull(document.getDocumentId());
313         assertTrue("Document should be initiatied", document.isInitiated());
314         assertTrue("Invalid route level.", document.getNodeNames().contains("Initiated"));
315         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName("TestAppDocStatusDoc6");
316         assertTrue(ObjectUtils.isNotNull(documentType));
317         assertTrue(ObjectUtils.isNotNull(documentType.getValidApplicationStatuses()));
318         assertEquals(0,documentType.getValidApplicationStatuses().size());
319         LOG.info("valid application status size: " + documentType.getValidApplicationStatuses().size());
320         assertTrue(ObjectUtils.isNotNull(documentType.getApplicationStatusCategories()));
321         assertEquals(0,documentType.getApplicationStatusCategories().size());
322     }
323 
324     @Test public void testSearching() throws InterruptedException {
325         String documentTypeName = "TestAppDocStatusDoc1";
326 
327         String initiatorNetworkId = "rkirkend";
328         Person initiator = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(initiatorNetworkId);
329         String approverNetworkId = "bmcgough";
330         Person approver = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(approverNetworkId);
331         String travelerNetworkId = "temay";
332         Person traveler = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(travelerNetworkId);
333 
334         WorkflowDocument workflowDocument = WorkflowDocumentFactory.createDocument(initiator.getPrincipalId(), documentTypeName);
335         workflowDocument.setTitle("Routing style");
336 
337         // no status, not routed
338         assertAppDocStatuses(workflowDocument.getDocumentId(), new String [] { });
339         assertSearchStatus(documentTypeName, initiator, "Approval in Progress", 0, 0);
340         assertSearchStatus(documentTypeName, initiator, "Submitted", 0, 0);
341 
342         workflowDocument.route("routing this document.");
343 
344         DocumentRouteHeaderValue drhv = KEWServiceLocator.getRouteHeaderService().getRouteHeader(workflowDocument.getDocumentId());
345         
346         // should be in approval status
347         assertAppDocStatuses(workflowDocument.getDocumentId(), new String [] { "Approval in Progress" });
348         assertSearchStatus(documentTypeName, initiator, "Approval in Progress", 1, 0); // one document currently in "Approval in Progress" status
349         assertSearchStatus(documentTypeName, initiator, "Approval in Progress", 1, drhv.getRouteStatusDate().getTime()); // one transition to "Approval in Progress" status around the time of routing
350         assertSearchStatus(documentTypeName, initiator, "Submitted", 0, 0); // none in "Submitted" status
351 
352         // approve it out of the "Approval in Progress" state
353         workflowDocument = WorkflowDocumentFactory.loadDocument(approver.getPrincipalId(), workflowDocument.getDocumentId());
354         RequestedActions actions = workflowDocument.getRequestedActions();
355         assertTrue(actions.isApproveRequested());
356         workflowDocument.approve("destination approval");
357 
358         assertAppDocStatuses(workflowDocument.getDocumentId(), new String [] { "Approval in Progress", "Submitted" });
359         assertSearchStatus(documentTypeName, approver, "Approval in Progress", 0, 0); // no documents currently in "Approval in Progress" status
360         assertSearchStatus(documentTypeName, approver, "Approval in Progress", 1, drhv.getRouteStatusDate().getTime()); // one transition to "Approval in Progress" status around the time of routing
361         assertSearchStatus(documentTypeName, approver, "Submitted", 1, 0); // one document currently in "Submitted" status
362         assertSearchStatus(documentTypeName, approver, "Submitted", 1, drhv.getDateLastModified().getMillis()); // one transition to "Submitted" status around the time of approval
363 
364         // approve it out of the "Approval in Progress" state
365         workflowDocument = WorkflowDocumentFactory.loadDocument(traveler.getPrincipalId(), workflowDocument.getDocumentId());
366         actions = workflowDocument.getRequestedActions();
367         assertTrue(actions.isApproveRequested());
368         workflowDocument.approve("travel approval");
369 
370         // no final status, so no transition
371         assertAppDocStatuses(workflowDocument.getDocumentId(), new String [] { "Approval in Progress", "Submitted" });
372         assertSearchStatus(documentTypeName, traveler, "Approval in Progress", 0, 0); // no documents currently in "Approval in Progress" status
373         assertSearchStatus(documentTypeName, traveler, "Approval in Progress", 1, drhv.getRouteStatusDate().getTime()); // one transition to "Approval in Progress" status around the time of routing
374         assertSearchStatus(documentTypeName, traveler, "Submitted", 1, 0); // one document currently in "Submitted" status
375         assertSearchStatus(documentTypeName, traveler, "Submitted", 1, drhv.getDateLastModified().getMillis()); // one transition to "Submitted" status around the time of approval
376     }
377 
378     /**
379      * Verifies the DocumentSearchService finds documents with a given app document status
380      * @param documentTypeName the doc type
381      * @param user user for lookup
382      * @param appDocStatus the app doc status target
383      * @param expected the expected number of results
384      * @param changed the time the transition occurred (used for from/to range)
385      */
386     protected void assertSearchStatus(String documentTypeName, Person user, String appDocStatus, int expected, long changed) {
387         DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
388         criteria.setDocumentTypeName(documentTypeName);
389         criteria.setApplicationDocumentStatus(appDocStatus);
390         if (changed != 0) {
391             criteria.setDateApplicationDocumentStatusChangedFrom(new DateTime(changed - 200));
392             criteria.setDateApplicationDocumentStatusChangedTo(new DateTime(changed + 200));
393         }
394         DocumentSearchResults results = KEWServiceLocator.getDocumentSearchService().lookupDocuments(user.getPrincipalId(), criteria.build());
395         assertEquals("Search results should have " + expected + " documents.", expected, results.getSearchResults().size());
396     }
397 
398     /**
399      * Verifies the document application document status history
400      * @param documentId the doc id
401      * @param appDocStatuses list of app doc statuses in chronological order
402      */
403     protected void assertAppDocStatuses(String documentId, String[] appDocStatuses) {
404         DocumentRouteHeaderValue drhv = KEWServiceLocator.getRouteHeaderService().getRouteHeader(documentId);
405 
406         String curStatus = KewApiConstants.UNKNOWN_STATUS;
407         if (appDocStatuses.length > 0) {
408             curStatus = appDocStatuses[appDocStatuses.length - 1];
409         }
410         assertEquals(curStatus, drhv.getAppDocStatus());
411 
412         List<DocumentStatusTransition> transitions = drhv.getAppDocStatusHistory();
413         assertEquals(appDocStatuses.length, transitions.size());
414         for (int i = 0; i < appDocStatuses.length; i++) {
415             DocumentStatusTransition trans = transitions.get(i);
416             assertEquals(appDocStatuses[i], trans.getNewAppDocStatus());
417             String prevStatus = null;
418             if (i > 0) {
419                 prevStatus = appDocStatuses[i - 1];
420             }
421             assertEquals(prevStatus, trans.getOldAppDocStatus());
422         }
423     }
424 }