View Javadoc

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