/*
*
* * Copyright (c) 2016. David Sowerby
* *
* * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* * specific language governing permissions and limitations under the License.
*
*/
package uk.q3c.krail.core.navigate;
import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.mycila.testing.junit.MycilaJunitRunner;
import com.mycila.testing.plugin.guice.GuiceContext;
import com.mycila.testing.plugin.guice.ModuleProvider;
import com.vaadin.server.Page;
import fixture.ReferenceUserSitemap;
import fixture.testviews2.ViewB;
import net.engio.mbassy.bus.common.PubSubSupport;
import net.engio.mbassy.listener.Handler;
import net.engio.mbassy.listener.Listener;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import uk.q3c.krail.core.config.ApplicationConfigurationModule;
import uk.q3c.krail.core.eventbus.*;
import uk.q3c.krail.core.guice.vsscope.VaadinSessionScopeModule;
import uk.q3c.krail.core.i18n.MessageKey;
import uk.q3c.krail.core.navigate.sitemap.*;
import uk.q3c.krail.core.navigate.sitemap.set.MasterSitemapQueue;
import uk.q3c.krail.core.services.ServicesModule;
import uk.q3c.krail.core.shiro.PageAccessControl;
import uk.q3c.krail.core.shiro.PageAccessController;
import uk.q3c.krail.core.shiro.PagePermission;
import uk.q3c.krail.core.shiro.SubjectProvider;
import uk.q3c.krail.core.ui.ScopedUI;
import uk.q3c.krail.core.ui.ScopedUIProvider;
import uk.q3c.krail.core.user.notify.UserNotifier;
import uk.q3c.krail.core.user.status.UserStatusBusMessage;
import uk.q3c.krail.core.user.status.UserStatusChangeSource;
import uk.q3c.krail.core.view.*;
import uk.q3c.krail.core.view.component.AfterViewChangeBusMessage;
import uk.q3c.krail.core.view.component.ViewChangeBusMessage;
import uk.q3c.krail.testutil.guice.uiscope.TestUIScopeModule;
import uk.q3c.krail.testutil.i18n.TestI18NModule;
import uk.q3c.krail.testutil.option.MockOption;
import uk.q3c.krail.testutil.option.TestOptionModule;
import uk.q3c.krail.testutil.persist.TestPersistenceModule;
import uk.q3c.krail.util.UtilsModule;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MycilaJunitRunner.class)
@GuiceContext({VaadinSessionScopeModule.class, TestI18NModule.class, TestPersistenceModule.class, TestOptionModule.class, EventBusModule.class,
TestUIScopeModule.class, SitemapModule.class, ServicesModule.class, UtilsModule.class, ApplicationConfigurationModule.class})
public class DefaultNavigatorTest {
@Mock
UserSitemapBuilder builder;
@Inject
TestViewChangeListener changeListener;
@Mock
BeforeViewChangeBusMessage event;
@Mock
UserStatusChangeSource source;
@Inject
MockOption option;
@Inject
DefaultViewChangeRule defaultViewChangeRule;
@Mock
UserNotifier userNotifier;
InvalidURIHandler invalidURIHandler;
@Mock
MasterSitemapQueue masterSitemapQueue;
@Inject
MasterSitemap masterSitemap;
@Mock
private Page browserPage;
@Mock
private ErrorView errorView;
@Mock
private Provider<ErrorView> errorViewProvider;
@Inject
private UIBusProvider eventBusProvider;
@Inject
@UIBus
private PubSubSupport<BusMessage> eventBus2;
@Inject
private FakeListener listener1;
@Inject
private FakeListener listener2;
@Inject
private FakeListener listener3;
@Inject
private MockListener listener4;
@Mock
private LoginNavigationRule loginNavigationRule;
@Mock
private UserStatusChangeSource loginSource;
@Mock
private LogoutNavigationRule logoutNavigationRule;
@Mock
private UserStatusChangeSource logoutSource;
private DefaultNavigator navigator;
@Inject
private PageAccessController pageAccessController;
@Mock
private ScopedUI scopedUI;
@Mock
private SitemapService sitemapService;
@Mock
private Subject subject;
@Mock
private SubjectProvider subjectProvider;
@Mock
private ScopedUIProvider uiProvider;
@Inject
private StrictURIFragmentHandler uriHandler;
@Inject
private ReferenceUserSitemap userSitemap;
@Mock
private Provider<UserSitemap> userSitemapProvider;
@Inject
private DefaultViewFactory viewFactory;
@Before
public void setup() {
userSitemap.populate();
when(builder.getUserSitemap()).thenReturn(userSitemap);
when(uiProvider.get()).thenReturn(scopedUI);
when(scopedUI.getPage()).thenReturn(browserPage);
when(errorViewProvider.get()).thenReturn(errorView);
when(subjectProvider.get()).thenReturn(subject);
when(userSitemapProvider.get()).thenReturn(userSitemap);
when(masterSitemapQueue.getCurrentModel()).thenReturn(masterSitemap);
invalidURIHandler = new DefaultInvalidURIHandler(userNotifier);
}
@After
public void tearDown() {
}
@Test
public void init() throws Exception {
// given
// when
navigator = createNavigator();
// then
verify(sitemapService).start();
verify(builder).build();
}
private DefaultNavigator createNavigator() {
navigator = new DefaultNavigator(uriHandler, sitemapService, subjectProvider, pageAccessController, uiProvider, viewFactory, builder,
loginNavigationRule, logoutNavigationRule, eventBusProvider, defaultViewChangeRule, invalidURIHandler, masterSitemapQueue);
navigator.init();
return navigator;
}
@Test
public void login() {
// given
navigator = createNavigator();
// when
navigator.navigateTo(userSitemap.loginURI);
// then
assertThat(navigator.getCurrentView()).isInstanceOf(userSitemap.loginViewClass);
verify(scopedUI).changeView(any(LoginView.class));
}
@Test
public void logout_rule_invoked() {
// given
navigator = createNavigator();
when(logoutNavigationRule.changedNavigationState(navigator, logoutSource)).thenReturn(Optional.empty());
// when
navigator.userStatusChange(new UserStatusBusMessage(logoutSource, false));
// then
verify(logoutNavigationRule).changedNavigationState(navigator, logoutSource);
}
@Test
public void login_rule_invoked() {
// given
// assertThat(loginNavigationRule.changedNavigationState(navigator,loginSource)).isNotNull();
navigator = createNavigator();
when(loginNavigationRule.changedNavigationState(navigator, loginSource)).thenReturn(Optional.empty());
// when
navigator.userStatusChange(new UserStatusBusMessage(loginSource, true));
// then
verify(loginNavigationRule).changedNavigationState(navigator, loginSource);
}
@Test
public void navigateTo() {
// given
navigator = createNavigator();
// when
navigator.navigateTo(userSitemap.a11URI);
// then
assertThat(navigator.getCurrentView()).isInstanceOf(userSitemap.a11ViewClass);
assertThat(navigator.getCurrentNode()).isEqualTo(userSitemap.a11Node());
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a11URI);
}
@Test
public void navigateToEmptyPageWithParams() {
// given
navigator = createNavigator();
String page1 = "";
String fragment1 = page1 + "/id=2/age=5";
// when
navigator.navigateTo(fragment1);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo("public/home/id=2/age=5");
}
@Test
public void navigateTo_invalidURI() {
// given
navigator = createNavigator();
String page = "public/view3";
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getVirtualPage()).isEqualTo("public/home");
}
@Test
public void navigate_to_invalid_URI() {
//given
navigator = createNavigator();
String page = "public/view3";
// when
navigator.navigateTo(page);
//then
verify(userNotifier).notifyInformation(MessageKey.Invalid_URI, page);
}
@Test
public void getNavigationState() {
// given
navigator = createNavigator();
// when
navigator.navigateTo(userSitemap.a1URI);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a1URI);
}
@Test
public void getNavigationParams() {
// given
navigator = createNavigator();
String page1 = userSitemap.a1URI;
String fragment1 = page1 + "/id=2/age=5";
// when
navigator.navigateTo(fragment1);
// then
assertThat(navigator.getNavigationParams()).containsOnly("id=2", "age=5");
}
@Test
public void navigateToNode() {
// given
navigator = createNavigator();
// when
navigator.navigateTo(userSitemap.a11Node());
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a11URI);
}
@Test
public void currentAndPreviousViews_andClearHistory() {
// given
navigator = createNavigator();
String page1 = userSitemap.a1URI;
String fragment1 = page1 + "/id=1";
String page2 = userSitemap.a11URI;
String fragment2 = page2 + "/id=2";
// when
// then
// start position
assertThat(navigator.getCurrentView()).isNull();
assertThat(navigator.getCurrentNavigationState()).isNull();
assertThat(navigator.getPreviousNavigationState()).isNull();
// when
navigator.navigateTo(fragment1);
// then
assertThat(navigator.getCurrentView()).isInstanceOf(userSitemap.a1ViewClass);
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(fragment1);
assertThat(navigator.getPreviousNavigationState()).isNull();
// when
navigator.navigateTo(fragment2);
// then
assertThat(navigator.getCurrentView()).isInstanceOf(userSitemap.a11ViewClass);
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(fragment2);
assertThat(navigator.getPreviousNavigationState()
.getFragment()).isEqualTo(fragment1);
// when
navigator.clearHistory();
// then
assertThat(navigator.getCurrentView()).isInstanceOf(userSitemap.a11ViewClass);
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(fragment2);
assertThat(navigator.getPreviousNavigationState()).isNull();
}
/**
* Checks add and remove listeners
*/
@Test
public void listeners_allRespond() {
// given
navigator = createNavigator();
String page = userSitemap.a11URI;
// when
NavigationState startState = navigator.getCurrentNavigationState();
navigator.navigateTo(page);
NavigationState endState = navigator.getCurrentNavigationState();
// then
assertThat(endState).isNotEqualTo(startState);
}
@Test
public void listener_blocked() {
// given
navigator = createNavigator();
String page = userSitemap.a11URI;
listener4.cancelBefore = true;
// when
NavigationState startState = navigator.getCurrentNavigationState();
navigator.navigateTo(page);
NavigationState endState = navigator.getCurrentNavigationState();
// then
assertThat(endState).isEqualTo(startState);
}
@Test
public void redirection() {
// given
navigator = createNavigator();
String page = "wiggly";
String page2 = userSitemap.a1URI;
userSitemap.addRedirect(page, page2);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(page2);
}
@Test
public void navigateToNavState() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
NavigationState navigationState = uriHandler.navigationState(page);
// when
navigator.navigateTo(navigationState);
// then
assertThat(navigator.getCurrentNavigationState()).isEqualTo(navigationState);
assertThat(navigator.getCurrentView()).isInstanceOf(userSitemap.a1ViewClass);
}
@Test
public void error() {
// given
navigator = createNavigator();
// when
navigator.error(new NullPointerException("test"));
// then
assertThat(navigator.getCurrentView()).isInstanceOf(ErrorView.class);
}
@Test
public void UAC_Public() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a1URI);
}
/**
* 'user' is required to be either authenticated or remembered
*/
@Test
public void UAC_User() {
// given authenticated
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
userSitemap.a1Node()
.getMasterNode()
.modifyPageAccessControl(PageAccessControl.USER);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a1URI);
// given remembered
page = userSitemap.a11URI;
when(subject.isAuthenticated()).thenReturn(false);
when(subject.isRemembered()).thenReturn(true);
userSitemap.a11Node()
.getMasterNode()
.modifyPageAccessControl(PageAccessControl.USER);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a11URI);
}
@Test(expected = UnauthorizedException.class)
public void UAC_User_fail() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(false);
when(subject.isRemembered()).thenReturn(false);
updatePermissionForA1Node(PageAccessControl.USER);
// when
navigator.navigateTo(page);
// then
// exception thrown
}
/**
* Changes the master node permission
*/
private void updatePermissionForA1Node(PageAccessControl pageAccessControl) {
MasterSitemapNode masterSitemapNode = userSitemap.a1Node()
.getMasterNode()
.modifyPageAccessControl(pageAccessControl);
UserSitemapNode newNode = new UserSitemapNode(masterSitemapNode);
newNode.setCollationKey(userSitemap.a1Node()
.getCollationKey());
newNode.setLabel(userSitemap.a1Node()
.getLabel());
userSitemap.replaceNode(userSitemap.a1Node(), newNode);
userSitemap.setA1Node(newNode);
}
@Test
public void UAC_Guest() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(false);
when(subject.isRemembered()).thenReturn(false);
userSitemap.a1Node()
.getMasterNode()
.modifyPageAccessControl(PageAccessControl.GUEST);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a1URI);
}
@Test(expected = UnauthorizedException.class)
public void UAC_Guest_Fail_remembered() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(false);
when(subject.isRemembered()).thenReturn(true);
updatePermissionForA1Node(PageAccessControl.GUEST);
// when
navigator.navigateTo(page);
// then
}
@Test(expected = UnauthorizedException.class)
public void UAC_Guest_Fail_authenticated() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
updatePermissionForA1Node(PageAccessControl.GUEST);
// when
navigator.navigateTo(page);
// then
}
@Test
public void UAC_Authenticate() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
userSitemap.a1Node()
.getMasterNode()
.modifyPageAccessControl(PageAccessControl.AUTHENTICATION);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a1URI);
}
@Test(expected = UnauthorizedException.class)
public void UAC_Authenticate_Fail() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(false);
when(subject.isRemembered()).thenReturn(false);
updatePermissionForA1Node(PageAccessControl.AUTHENTICATION);
// when
navigator.navigateTo(page);
// then
}
@Test
public void UAC_Permission() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
userSitemap.a1Node()
.getMasterNode()
.modifyPageAccessControl(PageAccessControl.PERMISSION);
when(subject.isPermitted(any(PagePermission.class))).thenReturn(true);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(userSitemap.a1URI);
}
@Test(expected = UnauthorizedException.class)
public void UAC_Permission_Failed() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
updatePermissionForA1Node(PageAccessControl.PERMISSION);
when(subject.isPermitted(any(PagePermission.class))).thenReturn(false);
// when
navigator.navigateTo(page);
// then
}
@Test
public void UAC_roles() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
updatePermissionForA1Node(PageAccessControl.ROLES, Lists.newArrayList("admin", "beast"));
List<String> permissions = userSitemap.a1Node()
.getMasterNode()
.getRoles();
when(subject.hasAllRoles(permissions)).thenReturn(true);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(page);
}
/**
* Changes the master node permission
*/
private void updatePermissionForA1Node(PageAccessControl pageAccessControl, List<String> roles) {
MasterSitemapNode oldMaster = userSitemap.a1Node()
.getMasterNode();
MasterSitemapNode masterSitemapNode = new MasterSitemapNode(oldMaster.getId(), oldMaster.getUriSegment(), oldMaster.getViewClass(), oldMaster
.getLabelKey(), oldMaster.getPositionIndex(), pageAccessControl, roles);
UserSitemapNode newNode = new UserSitemapNode(masterSitemapNode);
newNode.setCollationKey(userSitemap.a1Node()
.getCollationKey());
newNode.setLabel(userSitemap.a1Node()
.getLabel());
userSitemap.replaceNode(userSitemap.a1Node(), newNode);
}
@Test(expected = UnauthorizedException.class)
public void UAC_roles_failed() {
// given
navigator = createNavigator();
String page = userSitemap.a1URI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
updatePermissionForA1Node(PageAccessControl.ROLES, Lists.newArrayList("admin", "beast"));
List<String> permissions = userSitemap.a1Node()
.getMasterNode()
.getRoles();
when(subject.hasAllRoles(permissions)).thenReturn(false);
// when
navigator.navigateTo(page);
// then
assertThat(navigator.getCurrentNavigationState()
.getFragment()).isEqualTo(page);
}
@Test
public void callOrder() {
//given
navigator = createNavigator();
String page = userSitemap.bURI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
when(subject.isPermitted(any(PagePermission.class))).thenReturn(true);
//when
navigator.navigateTo(page);
//then
assertThat(navigator.getCurrentView()).isInstanceOf(ViewB.class);
ViewB view = (ViewB) navigator.getCurrentView();
assertThat(changeListener.getCalls()).containsExactly("beforeViewChange", "readFromEnvironment",
"beforeBuild", "buildView", "afterBuild", "afterViewChange");
}
@Test
public void eventContent() {
navigator = createNavigator();
String page = userSitemap.bURI;
when(subject.isAuthenticated()).thenReturn(true);
when(subject.isRemembered()).thenReturn(false);
when(subject.isPermitted(any(PagePermission.class))).thenReturn(true);
//when
navigator.navigateTo(page);
//then
ViewChangeBusMessage event = getEvent("beforeViewChange");
assertThat(event.getFromState()).isNull();
assertThat(event.getToState()
.getFragment()).isEqualTo(userSitemap.bURI);
event = getEvent("afterViewChange");
assertThat(event.getFromState()).isNull();
assertThat(event.getToState()
.getFragment()).isEqualTo(userSitemap.bURI);
//given
String page2 = userSitemap.b1URI;
changeListener.clear();
//when
navigator.navigateTo(page2);
//then
event = getEvent("beforeViewChange");
assertThat(event.getFromState()
.getFragment()).isEqualTo(userSitemap.bURI);
assertThat(event.getToState()
.getFragment()).isEqualTo(userSitemap.b1URI);
event = getEvent("afterViewChange");
assertThat(event.getFromState()
.getFragment()).isEqualTo(userSitemap.bURI);
assertThat(event.getToState()
.getFragment()).isEqualTo(userSitemap.b1URI);
}
private ViewChangeBusMessage getEvent(String eventKey) {
return changeListener.getMessage(eventKey);
}
/**
* https://github.com/davidsowerby/krail/issues/382
*/
@Test
public void navStateNotUpdated() {
//given
navigator = createNavigator();
NavigationState navState = new NavigationState();
navState.setVirtualPage(userSitemap.aURI);
//when
navigator.navigateTo(navState);
//then
assertThat(navigator.getCurrentNavigationState()).isEqualTo(navState);
}
@ModuleProvider
protected AbstractModule moduleProvider() {
return new AbstractModule() {
@Override
protected void configure() {
bind(ErrorView.class).to(DefaultErrorView.class);
bind(URIFragmentHandler.class).to(StrictURIFragmentHandler.class);
bind(MasterSitemap.class).to(DefaultMasterSitemap.class);
bind(UserSitemap.class).to(DefaultUserSitemap.class);
}
};
}
@Listener
@SubscribeTo(UIBus.class)
static class MockListener {
boolean cancelBefore = false;
boolean cancelAfter = false;
@Handler
public void beforeViewChange(BeforeViewChangeBusMessage event) {
if (cancelBefore) {
event.cancel();
}
}
@Handler
public void afterViewChange(BeforeViewChangeBusMessage event) {
if (cancelAfter) {
event.cancel();
}
}
}
@Singleton
@Listener
@SubscribeTo(UIBus.class)
public static class TestViewChangeListener {
Map<String, ViewChangeBusMessage> calls = new LinkedHashMap<>();
public Set<String> getCalls() {
return calls.keySet();
}
@Handler
public void beforeViewChange(BeforeViewChangeBusMessage busMessage) {
calls.put("beforeViewChange", busMessage);
}
/**
* Invoked after the view is changed. If a <code>beforeViewChange</code>
* method blocked the view change, this method is not called. Be careful of
* unbounded recursion if you decide to change the view again in the
* listener.
*
* @param busMessage view change event
*/
@Handler
public void afterViewChange(AfterViewChangeBusMessage busMessage) {
calls.put("afterViewChange", busMessage);
}
public void addCall(String call, ViewChangeBusMessage busMessage) {
calls.put(call, busMessage);
}
public ViewChangeBusMessage getMessage(String eventKey) {
return calls.get(eventKey);
}
public void clear() {
calls.clear();
}
}
@Listener
@SubscribeTo(UIBus.class)
public static class FakeListener {
int callsBefore;
int callsAfter;
@Handler
public void beforeViewChange(BeforeViewChangeBusMessage event) {
callsBefore++;
}
@Handler
public void afterViewChange(BeforeViewChangeBusMessage event) {
callsAfter++;
}
}
}