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