001 /*
002 * Copyright 2006-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kew.actions;
017
018 import org.apache.commons.lang.ArrayUtils;
019 import org.junit.Test;
020 import org.kuali.rice.kew.actionitem.ActionItem;
021 import org.kuali.rice.kew.api.KewApiConstants;
022 import org.kuali.rice.kew.api.WorkflowDocument;
023 import org.kuali.rice.kew.api.WorkflowDocumentFactory;
024 import org.kuali.rice.kew.api.action.ActionRequest;
025 import org.kuali.rice.kew.api.action.ActionType;
026 import org.kuali.rice.kew.api.action.InvalidActionTakenException;
027 import org.kuali.rice.kew.framework.postprocessor.*;
028 import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
029 import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
030 import org.kuali.rice.kew.service.KEWServiceLocator;
031 import org.kuali.rice.kew.test.KEWTestCase;
032 import org.kuali.rice.kim.api.KimConstants;
033 import org.kuali.rice.kim.api.common.template.Template;
034 import org.kuali.rice.kim.api.permission.Permission;
035 import org.kuali.rice.kim.api.role.Role;
036 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
037
038 import java.util.Collection;
039 import java.util.HashMap;
040 import java.util.List;
041 import java.util.Map;
042
043 import static org.junit.Assert.*;
044
045 public class RecallActionTest extends KEWTestCase {
046 /**
047 * test postprocessor for testing afterActionTaken hook
048 */
049 public static class RecallTestPostProcessor extends DefaultPostProcessor {
050 public static ActionType afterActionTakenType;
051 public static ActionTakenEvent afterActionTakenEvent;
052 @Override
053 public ProcessDocReport afterActionTaken(ActionType performed, ActionTakenEvent event) throws Exception {
054 afterActionTakenType = performed;
055 afterActionTakenEvent = event;
056 return super.afterActionTaken(performed, event);
057 }
058 }
059
060 private static final String RECALL_TEST_DOC = "RecallTest";
061 private static final String RECALL_NOTIFY_TEST_DOC = "RecallWithPrevNotifyTest";
062 private static final String RECALL_NO_PENDING_NOTIFY_TEST_DOC = "RecallWithoutPendingNotifyTest";
063 private static final String RECALL_NOTIFY_THIRDPARTY_TEST_DOC = "RecallWithThirdPartyNotifyTest";
064
065 private String EWESTFAL = null;
066 private String JHOPF = null;
067 private String RKIRKEND = null;
068 private String NATJOHNS = null;
069 private String BMCGOUGH = null;
070
071 protected void loadTestData() throws Exception {
072 loadXmlFile("ActionsConfig.xml");
073 }
074
075 @Override
076 protected void setUpAfterDataLoad() throws Exception {
077 super.setUpAfterDataLoad();
078 EWESTFAL = getPrincipalIdForName("ewestfal");
079 JHOPF = getPrincipalIdForName("jhopf");
080 RKIRKEND = getPrincipalIdForName("rkirkend");
081 NATJOHNS = getPrincipalIdForName("natjohns");
082 BMCGOUGH = getPrincipalIdForName("bmcgough");
083
084 RecallTestPostProcessor.afterActionTakenType = null;
085 RecallTestPostProcessor.afterActionTakenEvent = null;
086 }
087
088 protected void assertAfterActionTakenCalled(ActionType performed, ActionType taken) {
089 assertEquals(performed, RecallTestPostProcessor.afterActionTakenType);
090 assertNotNull(RecallTestPostProcessor.afterActionTakenEvent);
091 assertEquals(taken, RecallTestPostProcessor.afterActionTakenEvent.getActionTaken().getActionTaken());
092 }
093
094 @Test(expected=InvalidActionTakenException.class) public void testCantRecallUnroutedDoc() {
095 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
096 document.recall("recalling", true);
097 }
098
099 @Test public void testRecallAsInitiatorBeforeAnyApprovals() throws Exception {
100 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
101 document.route("");
102
103 document.recall("recalling", true);
104
105 assertTrue("Document should be recalled", document.isRecalled());
106 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.RECALL);
107
108 //verify that the document is truly dead - no more action requests or action items.
109
110 List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
111 assertEquals("Should not have any active requests", 0, requests.size());
112
113 Collection<ActionItem> actionItems = KEWServiceLocator.getActionListService().findByDocumentId(document.getDocumentId());
114 assertEquals("Should not have any action items", 0, actionItems.size());
115 }
116
117 @Test public void testRecallAsInitiatorAfterSingleApproval() throws Exception {
118 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
119 document.route("");
120
121 document = WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId());
122 document.approve("");
123
124 document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
125 document.recall("recalling", true);
126
127 assertTrue("Document should be recalled", document.isRecalled());
128 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.RECALL);
129
130 //verify that the document is truly dead - no more action requests or action items.
131
132 List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
133 assertEquals("Should not have any active requests", 0, requests.size());
134
135 Collection<ActionItem> actionItems = KEWServiceLocator.getActionListService().findByDocumentId(document.getDocumentId());
136 assertEquals("Should not have any action items", 0, actionItems.size());
137
138 // can't recall recalled doc
139 assertFalse(document.getValidActions().getValidActions().contains(ActionType.RECALL));
140 }
141
142 @Test(expected=InvalidActionTakenException.class)
143 public void testRecallInvalidWhenProcessed() throws Exception {
144 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
145 document.route("");
146
147 for (String user: new String[] { JHOPF, EWESTFAL, RKIRKEND, NATJOHNS, BMCGOUGH }) {
148 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
149 document.approve("");
150 }
151
152 document.refresh();
153 assertTrue("Document should be processed", document.isProcessed());
154 assertTrue("Document should be approved", document.isApproved());
155 assertFalse("Document should not be final", document.isFinal());
156
157 document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
158 document.recall("recalling when processed should fail", true);
159 }
160
161 @Test(expected=InvalidActionTakenException.class)
162 public void testRecallInvalidWhenFinal() throws Exception {
163 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
164 document.route("");
165
166 for (String user: new String[] { JHOPF, EWESTFAL, RKIRKEND, NATJOHNS, BMCGOUGH }) {
167 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
168 document.approve("");
169 }
170 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
171 document.acknowledge("");
172
173 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jthomas"), document.getDocumentId());
174 document.fyi();
175
176 for (ActionRequest a: document.getRootActionRequests()) {
177 System.err.println(a);
178 if (a.isAcknowledgeRequest() || a.isFyiRequest()) {
179 System.err.println(a.getPrincipalId());
180 System.err.println(KimApiServiceLocator.getIdentityService().getPrincipal(a.getPrincipalId()).getPrincipalName());
181 }
182 }
183
184 assertFalse("Document should not be processed", document.isProcessed());
185 assertTrue("Document should be approved", document.isApproved());
186 assertTrue("Document should be final", document.isFinal());
187
188 document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
189 document.recall("recalling when processed should fail", true);
190 }
191
192 @Test public void testRecallToActionListAsInitiatorBeforeAnyApprovals() throws Exception {
193 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
194 document.route("");
195
196 document.recall("recalling", false);
197
198 assertTrue("Document should be saved", document.isSaved());
199 assertEquals(1, document.getCurrentNodeNames().size());
200 assertTrue(document.getCurrentNodeNames().contains("AdHoc"));
201 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.COMPLETE);
202
203 // initiator has completion request
204 assertTrue(document.isCompletionRequested());
205 // can't recall saved doc
206 assertFalse(document.getValidActions().getValidActions().contains(ActionType.RECALL));
207
208 // first approver has FYI
209 assertTrue(WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isFYIRequested());
210
211 document.complete("completing");
212
213 assertTrue("Document should be enroute", document.isEnroute());
214
215 assertTrue(WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isApprovalRequested());
216 }
217
218 private static final String PERM_APP_DOC_STATUS = "recallable by admins";
219 private static final String ROUTE_NODE = "NotifyFirst";
220 private static final String ROUTE_STATUS = "R";
221
222 protected Permission createRecallPermission(String docType, String appDocStatus, String routeNode, String routeStatus) {
223 return createPermissionForTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION + " for test case", docType, appDocStatus, routeNode, routeStatus);
224 }
225
226 protected Permission createRouteDocumentPermission(String docType, String appDocStatus, String routeNode, String routeStatus) {
227 return createPermissionForTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.ROUTE_PERMISSION, KewApiConstants.KEW_NAMESPACE, KewApiConstants.ROUTE_PERMISSION + " for test case", docType, appDocStatus, routeNode, routeStatus);
228 }
229
230 protected Permission createPermissionForTemplate(String template_ns, String template_name, String permission_ns, String permission_name, String docType, String appDocStatus, String routeNode, String routeStatus) {
231 Template permTmpl = KimApiServiceLocator.getPermissionService().findPermTemplateByNamespaceCodeAndName(template_ns, template_name);
232 assertNotNull(permTmpl);
233 Permission.Builder permission = Permission.Builder.create(permission_ns, permission_name);
234 permission.setDescription(permission_name);
235 permission.setTemplate(Template.Builder.create(permTmpl));
236 Map<String, String> attrs = new HashMap<String, String>();
237 attrs.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, docType);
238 attrs.put(KimConstants.AttributeConstants.APP_DOC_STATUS, appDocStatus);
239 attrs.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, routeNode);
240 attrs.put(KimConstants.AttributeConstants.ROUTE_STATUS_CODE, routeStatus);
241 permission.setActive(true);
242 permission.setAttributes(attrs);
243
244 // save the permission and check that's it's wired up correctly
245 Permission perm = KimApiServiceLocator.getPermissionService().createPermission(permission.build());
246 assertEquals(perm.getTemplate().getId(), permTmpl.getId());
247 int num = 1;
248 if (appDocStatus != null) num++;
249 if (routeNode != null) num++;
250 if (routeStatus != null) num++;
251 assertEquals(num, perm.getAttributes().size());
252 assertEquals(docType, perm.getAttributes().get(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME));
253 assertEquals(appDocStatus, perm.getAttributes().get(KimConstants.AttributeConstants.APP_DOC_STATUS));
254 assertEquals(routeNode, perm.getAttributes().get(KimConstants.AttributeConstants.ROUTE_NODE_NAME));
255 assertEquals(routeStatus, perm.getAttributes().get(KimConstants.AttributeConstants.ROUTE_STATUS_CODE));
256
257 return perm;
258 }
259
260 // disable the existing Recall Permission assigned to Initiator Role for test purposes
261 protected void disableInitiatorRecallPermission() {
262 Permission p = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName("KR-WKFLW", "Recall Document");
263 Permission.Builder pb = Permission.Builder.create(p);
264 pb.setActive(false);
265 KimApiServiceLocator.getPermissionService().updatePermission(pb.build());
266 }
267
268 /**
269 * Tests that a new permission can be configured with the Recall Permission template and that matching works correctly
270 * against the new permission
271 */
272 @Test public void testRecallPermissionMatching() {
273 disableInitiatorRecallPermission();
274 createRecallPermission(RECALL_TEST_DOC, PERM_APP_DOC_STATUS, ROUTE_NODE, ROUTE_STATUS);
275
276 Map<String, String> details = new HashMap<String, String>();
277 details.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, RECALL_TEST_DOC);
278 details.put(KimConstants.AttributeConstants.APP_DOC_STATUS, PERM_APP_DOC_STATUS);
279 details.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, ROUTE_NODE);
280 details.put(KimConstants.AttributeConstants.ROUTE_STATUS_CODE, ROUTE_STATUS);
281
282 // test all single field mismatches
283 for (Map.Entry<String, String> entry: details.entrySet()) {
284 Map<String, String> testDetails = new HashMap<String, String>(details);
285 // change a single detail to a non-matching value
286 testDetails.put(entry.getKey(), entry.getValue() + " BOGUS ");
287 assertFalse("non-matching " + entry.getKey() + " detail should cause template to not match", KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, testDetails));
288 }
289
290 assertTrue("template should match details", KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, details));
291 }
292
293 @Test public void testRecallPermissionTemplate() throws Exception {
294 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
295 document.route("");
296
297 // nope, technical admins can't recall
298 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
299 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
300
301 // create a recall permission for the RECALL_TEST_DOC doctype
302 Permission perm = createRecallPermission(RECALL_TEST_DOC, PERM_APP_DOC_STATUS, ROUTE_NODE, ROUTE_STATUS);
303
304 // assign the permission to Technical Administrator role
305 Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
306 KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), techadmin.getId());
307
308 // our recall permission is assigned to the technical admin role
309
310 // but the doc will not match...
311 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_NOTIFY_TEST_DOC);
312 document.route(PERM_APP_DOC_STATUS);
313 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
314 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
315
316 // .. the app doc status will not match...
317 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
318 document.route("");
319 // technical admins can't recall since the app doc status is not correct
320 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
321 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
322
323 // ... the node will not match ...
324 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
325 document.route("");
326 WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).approve(""); // approve past notifyfirstnode
327 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
328 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
329
330 // ... the doc status will not match (not recallable anyway) ...
331 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
332 document.route("");
333 document.cancel("cancelled");
334 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
335 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
336
337 // everything should match
338 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
339 document.setApplicationDocumentStatus(PERM_APP_DOC_STATUS);
340 document.route("");
341 // now technical admins can recall by virtue of having the recall permission on this doc
342 assertTrue(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
343 assertTrue(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
344 }
345
346 @Test public void testRecallToActionListAsInitiatorAfterApprovals() throws Exception {
347 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_TEST_DOC);
348 }
349
350 @Test public void testRecallToActionListAsInitiatorWithNotificationAfterApprovals() throws Exception {
351 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NOTIFY_TEST_DOC);
352 }
353
354 @Test public void testRecallToActionListAsInitiatorWithoutPendingNotificationAfterApprovals() throws Exception {
355 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NO_PENDING_NOTIFY_TEST_DOC);
356 }
357
358 @Test public void testRecallToActionListAsInitiatorWithThirdPartyNotificationAfterApprovals() throws Exception {
359 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NOTIFY_THIRDPARTY_TEST_DOC);
360 }
361
362 /**
363 * Tests that the document is returned to the *recaller*'s action list, not the original initiator
364 * @throws Exception
365 */
366 @Test public void testRecallToActionListAsThirdParty() throws Exception {
367 Permission perm = createRecallPermission(RECALL_TEST_DOC, null, null, null);
368 // assign the permission to Technical Administrator role
369 Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
370 KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), techadmin.getId());
371 // recall as 'admin' user
372 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC);
373 }
374
375 // the three tests below test permutations of recall permission and derived role assignment
376 protected void assignRoutePermissionToTechAdmin() {
377 // assign Route Document permission to the Technical Administrator role
378 Permission routePerm = createRouteDocumentPermission(RECALL_TEST_DOC, null, null, null);
379 Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
380 KimApiServiceLocator.getRoleService().assignPermissionToRole(routePerm.getId(), techadmin.getId());
381 }
382 protected void assignRecallPermissionToDocumentRouters() {
383 // assign Recall permission to the Document Router derived role
384 Permission recallPerm = createRecallPermission(RECALL_TEST_DOC, null, null, null);
385 Role documentRouterDerivedRole = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-WKFLW", "Document Router");
386 KimApiServiceLocator.getRoleService().assignPermissionToRole(recallPerm.getId(), documentRouterDerivedRole.getId());
387 }
388 /**
389 * Tests that simply assigning the Route Document permission to the Technical Admin role *without* assigning the
390 * Recall permission to the Document Router derived role, is NOT sufficient to enable recall.
391 */
392 @Test public void testRoutePermissionAssignmentInsufficientForRouterToRecallDoc() throws Exception {
393 assignRoutePermissionToTechAdmin();
394 // recall as 'admin' (Tech Admin) user
395 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC, false);
396 }
397 /**
398 * Tests that simply assigning the recall permission to the Document Router derived role *without* assigning the
399 * Route Document permission to the Technical Admin role, is NOT sufficient to enable recall.
400 */
401 @Test public void testRecallPermissionAssignmentInsufficientForRouterToRecallDoc() throws Exception {
402 assignRecallPermissionToDocumentRouters();
403 // recall as 'admin' (Tech Admin) user
404 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC, false);
405 }
406 /**
407 * Tests that we can use the Route Document derived role to assign Recall permission to document routers.
408 */
409 @Test public void testRecallToActionListAsRouterDerivedRole() throws Exception {
410 // assign both! derived role works its magic
411 assignRoutePermissionToTechAdmin();
412 assignRecallPermissionToDocumentRouters();
413 // recall as 'admin' user (Tech Admin) user
414 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC);
415 }
416
417 protected void testRecallToActionListAsInitiatorAfterApprovals(String doctype) {
418 testRecallToActionListAfterApprovals(EWESTFAL, EWESTFAL, doctype);
419 }
420
421 // Implements various permutations of recalls - with and without doctype policies/notifications of various sorts
422 // and as initiator or a third party recaller
423 protected void testRecallToActionListAfterApprovals(String initiator, String recaller, String doctype) {
424 testRecallToActionListAfterApprovals(initiator, recaller, doctype, true);
425 }
426 protected void testRecallToActionListAfterApprovals(String initiator, String recaller, String doctype, boolean expect_recall_success) {
427 boolean notifyPreviousRecipients = !RECALL_TEST_DOC.equals(doctype);
428 boolean notifyPendingRecipients = !RECALL_NO_PENDING_NOTIFY_TEST_DOC.equals(doctype);
429 String[] thirdPartiesNotified = RECALL_NOTIFY_THIRDPARTY_TEST_DOC.equals(doctype) ? new String[] { "quickstart", "admin" } : new String[] {};
430
431 WorkflowDocument document = WorkflowDocumentFactory.createDocument(initiator, doctype);
432 document.route("");
433
434 WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).approve("");
435 WorkflowDocumentFactory.loadDocument(initiator, document.getDocumentId()).approve("");
436 WorkflowDocumentFactory.loadDocument(RKIRKEND, document.getDocumentId()).approve("");
437
438 document = WorkflowDocumentFactory.loadDocument(recaller, document.getDocumentId());
439 System.err.println(document.getValidActions().getValidActions());
440 if (expect_recall_success) {
441 assertTrue("recaller '" + recaller + "' should be able to RECALL", document.getValidActions().getValidActions().contains(ActionType.RECALL));
442 } else {
443 assertFalse("recaller '" + recaller + "' should NOT be able to RECALL", document.getValidActions().getValidActions().contains(ActionType.RECALL));
444 return;
445 }
446 document.recall("recalling", false);
447
448 assertTrue("Document should be saved", document.isSaved());
449 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.COMPLETE);
450
451 // the recaller has a completion request
452 assertTrue(document.isCompletionRequested());
453
454 // pending approver has FYI
455 assertEquals(notifyPendingRecipients, WorkflowDocumentFactory.loadDocument(NATJOHNS, document.getDocumentId()).isFYIRequested());
456 // third approver has FYI
457 assertEquals(notifyPreviousRecipients, WorkflowDocumentFactory.loadDocument(RKIRKEND, document.getDocumentId()).isFYIRequested());
458 // second approver does not have FYI - approver is initiator, FYI is skipped
459 assertFalse(WorkflowDocumentFactory.loadDocument(initiator, document.getDocumentId()).isFYIRequested());
460 // first approver has FYI
461 assertEquals(notifyPreviousRecipients, WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isFYIRequested());
462
463 if (!ArrayUtils.isEmpty(thirdPartiesNotified)) {
464 for (String recipient: thirdPartiesNotified) {
465 assertTrue("Expected FYI to be sent to: " + recipient, WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(recipient), document.getDocumentId()).isFYIRequested());
466 }
467 }
468
469 // omit JHOPF, and see if FYI is subsumed by approval request
470 for (String user: new String[] { RKIRKEND, NATJOHNS }) {
471 WorkflowDocumentFactory.loadDocument(user, document.getDocumentId()).fyi();
472 }
473
474 document.complete("completing");
475
476 assertTrue("Document should be enroute", document.isEnroute());
477
478 // generation of approval requests nullify FYIs (?)
479 // if JHOPF had an FYI, he doesn't any longer
480 for (String user: new String[] { JHOPF, RKIRKEND, NATJOHNS }) {
481 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
482 assertFalse(getPrincipalNameForId(user) + " should not have an FYI", document.isFYIRequested());
483 }
484
485 // submit all approvals
486 for (String user: new String[] { JHOPF, initiator, RKIRKEND, NATJOHNS, BMCGOUGH }) {
487 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
488 assertTrue(getPrincipalNameForId(user) + " should have approval request", document.isApprovalRequested());
489 document.approve("approving");
490 }
491
492 // 2 acks outstanding, we're PROCESSED
493 assertTrue("Document should be processed", document.isProcessed());
494 assertTrue("Document should be approved", document.isApproved());
495
496 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
497 document.acknowledge("");
498
499 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jthomas"), document.getDocumentId());
500 document.fyi();
501
502 assertTrue("Document should be approved", document.isApproved());
503 assertTrue("Document should be final", document.isFinal());
504 }
505 }