package com.atlassian.stash.internal.project;

import com.atlassian.bitbucket.AbstractServiceTest;
import com.atlassian.bitbucket.AuthorisationException;
import com.atlassian.bitbucket.IntegrityException;
import com.atlassian.bitbucket.NoSuchEntityException;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.avatar.AvatarSupplier;
import com.atlassian.bitbucket.event.project.*;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionPredicateFactory;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.project.*;
import com.atlassian.bitbucket.server.ApplicationMode;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.DummySecurityService;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.bitbucket.validation.groups.Create;
import com.atlassian.bitbucket.validation.groups.TrustedCreate;
import com.atlassian.bitbucket.validation.groups.TrustedUpdate;
import com.atlassian.bitbucket.validation.groups.Update;
import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.crowd.event.user.UserCreatedEvent;
import com.atlassian.crowd.model.user.User;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.stash.internal.avatar.InternalAvatarService;
import com.atlassian.stash.internal.user.*;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import javax.validation.Validator;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;

import static com.atlassian.bitbucket.mockito.MockitoUtils.returnFirst;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings({"ConstantConditions", "unchecked"})
public class DefaultProjectServiceTest extends AbstractServiceTest {

    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private InternalAvatarService avatarService;
    @Mock
    public EventPublisher eventPublisher;
    @Spy
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private ProjectPermissionDao permissionDao;
    @Mock
    private PermissionService permissionService;
    @Mock
    private PermissionPredicateFactory predicateFactory;
    @Mock
    private ProjectDao projectDao;
    private DefaultProjectService projectService;
    @Spy
    private SecurityService securityService = new DummySecurityService();
    @Mock
    private Validator validator;

    public DefaultProjectServiceTest() {
        super(DefaultProjectService.class, InternalProjectService.class);
    }

    @Before
    public void setUp() {
        projectService = newService(ApplicationMode.DEFAULT);
        projectService.setMaxProjects(100);
    }

    @Test
    public void testCount() {
        when(projectDao.countAll()).thenReturn(5L);

        assertEquals(5L, projectService.getCount());

        verify(projectDao).countAll();
    }

    @Test
    public void testCreate() throws Throwable {
        testCreate(ApplicationMode.DEFAULT, Create.class);
    }

    @Test(expected = ProjectCreationCanceledException.class)
    public void testCreateThrowsWhenCanceled() {
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                ProjectCreationRequestedEvent event = (ProjectCreationRequestedEvent) invocationOnMock.getArguments()[0];
                assertFalse(event.isCanceled());
                event.cancel(new KeyedMessage("key", "localisedMessage", "rootMessage"));
                assertTrue(event.isCanceled());
                return null;
            }
        }).when(eventPublisher).publish(isA(ProjectCreationRequestedEvent.class));

        try {
            projectService.create(new ProjectCreateRequest.Builder().key("key").name("name").build());
        } finally {
            verify(eventPublisher, never()).publish(isA(ProjectCreatedEvent.class));
            verify(projectDao, never()).create(any(InternalProject.class));
        }
    }

    @Test
    public void testDelete() throws Exception {
        InternalProject project = mock(InternalProject.class);
        when(project.getId()).thenReturn(1);

        when(projectDao.getById(eq(1))).thenReturn(project);

        assertTrue(projectService.delete(project));

        //Verify the requested event is raised first, then permissions are deleted, then the project is deleted and,
        //finally, that the deleted event is raised.
        InOrder ordered = inOrder(eventPublisher, projectDao);
        ordered.verify(eventPublisher).publish(isA(ProjectDeletionRequestedEvent.class));
        ordered.verify(projectDao).delete(same(project));
        ordered.verify(eventPublisher).publish(isA(ProjectDeletedEvent.class));
    }

    @Test
    public void testDeleteNonExisting() throws Exception {
        Project project = mock(Project.class);
        when(project.getId()).thenReturn(7);

        //Should not throw an exception, nor raise any deletion events
        assertFalse(projectService.delete(project));

        verify(projectDao).getById(eq(7));
        verifyZeroInteractions(eventPublisher);
    }

    @Test(expected = ProjectDeletionCanceledException.class)
    public void testDeleteThrowsWhenCanceled() {
        final InternalProject project = mock(InternalProject.class);
        when(project.getId()).thenReturn(1);

        doAnswer(new Answer<Object>() {

            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                ProjectDeletionRequestedEvent event = (ProjectDeletionRequestedEvent) invocationOnMock.getArguments()[0];
                assertSame(project, event.getProject());
                assertFalse(event.isCanceled());

                event.cancel(new KeyedMessage("key", "localisedMessage", "rootMessage"));
                assertTrue(event.isCanceled());

                return null;
            }
        }).when(eventPublisher).publish(isA(ProjectDeletionRequestedEvent.class));
        when(projectDao.getById(eq(1))).thenReturn(project);

        try {
            projectService.delete(project);
        } finally {
            verify(eventPublisher, never()).publish(isA(ProjectCreatedEvent.class));
            verify(projectDao, never()).delete(any(InternalProject.class));
        }
    }

    @Test(expected = IntegrityException.class)
    public void testDeleteThrowsWhenProjectHasRepositories() throws Exception {
        InternalProject project = mock(InternalProject.class);
        when(project.getId()).thenReturn(1);

        when(projectDao.getById(eq(1))).thenReturn(project);
        when(projectDao.hasRepositories(eq(1))).thenReturn(true);

        try {
            projectService.delete(project);
        } finally {
            verify(projectDao).hasRepositories(eq(1));
            verify(projectDao, never()).delete(any(InternalProject.class));
        }
    }

    @Test
    public void testFindAll() throws Exception {
        Predicate<Project> predicate = mock(Predicate.class);
        Page<InternalProject> page = mock(Page.class);

        PageRequest pageRequest = mock(PageRequest.class);
        when(pageRequest.buildRestrictedPageRequest(eq(100))).thenReturn(pageRequest);

        when(predicateFactory.createProjectAccessiblePredicate()).thenReturn(predicate);
        when(projectDao.findAll(same(pageRequest), same(predicate))).thenReturn(page);

        assertSame(page, projectService.findAll(pageRequest));

        verify(pageRequest).buildRestrictedPageRequest(eq(100));
        verify(predicateFactory).createProjectAccessiblePredicate();
        verify(projectDao).findAll(same(pageRequest), same(predicate));
    }

    @Test
    public void testFindAllKeys() throws Exception {
        List<String> keys = Arrays.asList("A", "B", "C");

        Predicate<Project> predicate = mock(Predicate.class);

        when(predicateFactory.createProjectAccessiblePredicate()).thenReturn(predicate);
        when(projectDao.findAllKeys(same(predicate))).thenReturn(keys);

        List<String> found = projectService.findAllKeys();
        assertNotSame("The key collection should have been copied", keys, found);
        assertEquals(keys.size(), found.size());
        assertEquals(keys.get(0), found.get(0)); //Verify order is preserved
        assertEquals(keys.get(1), found.get(1)); //Verify order is preserved
        assertEquals(keys.get(2), found.get(2)); //Verify order is preserved
    }

    //For simplicity, this method uses partial mocking to verify the delegation to getPersonalProject(true)
    @Test
    public void testGetPersonalProject() {
        InternalPersonalProject project = mock(InternalPersonalProject.class);

        DefaultProjectService service = spy(projectService);
        doReturn(project).when(service).getPersonalProject(eq(true));

        assertSame(project, service.getPersonalProject());
    }

    @Test
    public void testGetPersonalProjectCreatesIfAsked() {
        InternalNormalUser user = createMockNormalUser();
        when(user.getDisplayName()).thenReturn("Administrator");
        when(user.getSlug()).thenReturn("admin");

        when(authenticationContext.getCurrentUser()).thenReturn(user);
        when(projectDao.createPersonal(isA(InternalPersonalProject.class))).thenAnswer(returnFirst());

        InternalPersonalProject project = projectService.getPersonalProject(true);
        assertNotNull(project);
        assertEquals("~ADMIN", project.getKey());
        assertEquals("Administrator", project.getName());
        assertNull(project.getDescription());
        assertSame(user, project.getOwner());
        assertSame(ProjectType.PERSONAL, project.getType());

        InOrder ordered = inOrder(eventPublisher, permissionDao, projectDao);
        ordered.verify(projectDao).createPersonal(same(project));
        ordered.verify(permissionDao).create(isA(InternalProjectPermission.class));
        ordered.verify(eventPublisher).publish(isA(ProjectCreatedEvent.class));

        //Personal projects should not publish requested events
        verify(eventPublisher, never()).publish(isA(ProjectCreationRequestedEvent.class));
    }

    @Test
    public void testGetPersonalProjectDoesNotCreateIfNotAsked() {
        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getSlug()).thenReturn("admin");
        when(user.getId()).thenReturn(42);

        when(authenticationContext.getCurrentUser()).thenReturn(user);

        assertNull(projectService.getPersonalProject(false));
        verify(authenticationContext).getCurrentUser();
        verify(projectDao).getByOwner(eq(42));
    }

    @Test
    public void testGetPersonalProjectForUser() {
        InternalNormalUser user = createMockNormalUser();
        when(user.getDisplayName()).thenReturn("Administrator");
        when(user.getId()).thenReturn(42);
        when(user.getSlug()).thenReturn("admin");

        when(projectDao.createPersonal(isA(InternalPersonalProject.class))).thenAnswer(returnFirst());

        InternalPersonalProject project = projectService.getPersonalProject(user);
        assertNotNull(project);
        assertEquals("~ADMIN", project.getKey());
        assertEquals("Administrator", project.getName());
        assertNull(project.getDescription());
        assertSame(user, project.getOwner());
        assertSame(ProjectType.PERSONAL, project.getType());

        InOrder ordered = inOrder(eventPublisher, permissionDao, projectDao);
        ordered.verify(projectDao).createPersonal(same(project));
        ordered.verify(permissionDao).create(isA(InternalProjectPermission.class));
        ordered.verify(eventPublisher).publish(isA(ProjectCreatedEvent.class));

        //Personal projects should not publish requested events
        verify(eventPublisher, never()).publish(isA(ProjectCreationRequestedEvent.class));
        verifyZeroInteractions(authenticationContext); //Should _never_ use the context user
    }

    @Test
    public void testGetPersonalProjectForUserReturnsExisting() {
        InternalPersonalProject project = mock(InternalPersonalProject.class);

        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getId()).thenReturn(42);

        when(projectDao.getByOwner(eq(42))).thenReturn(project);

        assertSame(project, projectService.getPersonalProject(user));
        verify(projectDao).getByOwner(eq(42));
        verify(projectDao, never()).createPersonal(any(InternalPersonalProject.class));
        verifyZeroInteractions(eventPublisher);
        verifyZeroInteractions(permissionDao);
    }

    @Test
    public void testGetPersonalProjectReturnsExisting() {
        InternalPersonalProject project = mock(InternalPersonalProject.class);

        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getSlug()).thenReturn("admin");
        when(user.getId()).thenReturn(42);

        when(authenticationContext.getCurrentUser()).thenReturn(user);
        when(projectDao.getByOwner(eq(42))).thenReturn(project);

        assertSame(project, projectService.getPersonalProject(false));
        assertSame(project, projectService.getPersonalProject(true));
        verify(projectDao, times(2)).getByOwner(eq(42));
        verify(projectDao, never()).createPersonal(any(InternalPersonalProject.class));
        verifyZeroInteractions(eventPublisher);
        verifyZeroInteractions(permissionDao);
    }

    @Test(expected = AuthorisationException.class)
    public void testGetPersonalProjectThrowsWhenNoCurrentUser() {
        reset(authenticationContext);

        projectService.getPersonalProject(false);
    }

    @Test(expected = NullPointerException.class)
    public void testGetPersonalProjectWithNullUser() {
        projectService.getPersonalProject(null);
    }

    @Test
    public void testOnUserCreated() {
        InternalNormalUser owner = createMockNormalUser();

        User user = mock(User.class);
        when(user.getName()).thenReturn("jdoe");

        InternalPersonalProject project = mock(InternalPersonalProject.class);
        when(project.getKey()).thenReturn("~JDOE");
        when(project.getOwner()).thenReturn(owner);

        when(projectDao.getByUsername(eq("jdoe"))).thenReturn(project);

        projectService.onUserCreated(new UserCreatedEvent(this, mock(Directory.class), user));

        //This verifies that the service is checking the _project owner's_ permissions, _not_ the current user
        verify(permissionService).hasProjectPermission(same(owner), same(project), same(Permission.PROJECT_ADMIN));
        verify(projectDao).getByUsername(eq("jdoe"));
        verifyZeroInteractions(authenticationContext); //Should not be looking at the context user

        ArgumentCaptor<InternalProjectPermission> permissionCaptor = ArgumentCaptor.forClass(InternalProjectPermission.class);
        verify(permissionDao).create(permissionCaptor.capture());

        InternalProjectPermission permission = permissionCaptor.getValue();
        assertNotNull(permission);
        assertSame(Permission.PROJECT_ADMIN, permission.getPermission());
        assertSame(project, permission.getProject());
        assertSame(owner, permission.getUser());
    }

    @Test
    public void testOnUserCreatedWithExistingPermission() {
        InternalNormalUser owner = createMockNormalUser();

        InternalPersonalProject project = mock(InternalPersonalProject.class);
        when(project.getOwner()).thenReturn(owner);

        User user = mock(User.class);
        when(user.getName()).thenReturn("jdoe");

        when(permissionService.hasProjectPermission(same(owner), same(project), same(Permission.PROJECT_ADMIN)))
                .thenReturn(true);
        when(projectDao.getByUsername(eq("jdoe"))).thenReturn(project);

        projectService.onUserCreated(new UserCreatedEvent(this, mock(Directory.class), user));

        verify(permissionService).hasProjectPermission(same(owner), same(project), same(Permission.PROJECT_ADMIN));
        verify(projectDao).getByUsername(eq("jdoe"));
        verifyZeroInteractions(authenticationContext);
        verifyZeroInteractions(permissionDao);
    }

    @Test
    public void testOnUserCreatedWithNoPersonalProject() {
        User user = mock(User.class);
        when(user.getName()).thenReturn("jdoe");

        projectService.onUserCreated(new UserCreatedEvent(this, mock(Directory.class), user));

        verify(projectDao).getByUsername(eq("jdoe"));
        verifyZeroInteractions(authenticationContext);
        verifyZeroInteractions(permissionDao);
        verifyZeroInteractions(permissionService);
    }

    @Test
    public void testSearch() {
        ProjectSearchRequest request = new ProjectSearchRequest.Builder().build();

        Page<InternalProject> page = mock(Page.class);
        Predicate<Project> predicate = mock(Predicate.class);

        PageRequest pageRequest = mock(PageRequest.class);
        when(pageRequest.buildRestrictedPageRequest(eq(100))).thenReturn(pageRequest);

        when(predicateFactory.createProjectAccessiblePredicate()).thenReturn(predicate);
        when(projectDao.search(same(request), same(pageRequest), same(predicate))).thenReturn(page);

        assertSame(page, projectService.search(request, pageRequest));

        verify(predicateFactory).createProjectAccessiblePredicate();
    }

    @Test
    public void testSearchAppliesPermission() {
        ProjectSearchRequest request = new ProjectSearchRequest.Builder()
                .permission(Permission.PROJECT_ADMIN)
                .build();

        Page<InternalProject> page = mock(Page.class);
        Predicate<Project> predicate = mock(Predicate.class);

        PageRequest pageRequest = mock(PageRequest.class);
        when(pageRequest.buildRestrictedPageRequest(eq(100))).thenReturn(pageRequest);

        when(predicateFactory.createProjectPermissionPredicate(same(Permission.PROJECT_ADMIN))).thenReturn(predicate);
        when(projectDao.search(same(request), same(pageRequest), same(predicate))).thenReturn(page);

        assertSame(page, projectService.search(request, pageRequest));

        verify(predicateFactory).createProjectPermissionPredicate(same(Permission.PROJECT_ADMIN));
    }

    @Test(expected = NullPointerException.class)
    public void testSearchWithNullCriteria() {
        projectService.search(null, PageUtils.newRequest(0, 100));
    }

    @Test(expected = NullPointerException.class)
    public void testSearchWithNullPageRequest() {
        projectService.search(new ProjectSearchRequest.Builder().build(), null);
    }

    @Test
    public void testTrustedCreate() throws Throwable {
        testCreate(ApplicationMode.MIRROR, TrustedCreate.class);
    }

    @Test
    public void testTrustedUpdate() throws Exception {
        testUpdate(ApplicationMode.MIRROR, TrustedUpdate.class);
    }

    @Test
    public void testUpdate() throws Exception {
        testUpdate(ApplicationMode.DEFAULT, Update.class);
    }

    @Test(expected = NoSuchEntityException.class)
    public void testUpdateNotFound() throws Exception {
        projectService.update(new ProjectUpdateRequest.Builder(7)
                .key("newkey")
                .name("New Name")
                .description("New description")
                .build());
    }

    @Test(expected = IntegrityException.class)
    public void testUpdatePersonalProject() {
        InternalPersonalProject project = mock(InternalPersonalProject.class);

        when(projectDao.getById(eq(1))).thenReturn(project);

        try {
            projectService.update(new ProjectUpdateRequest.Builder(1)
                    .key("newkey")
                    .name("New Name")
                    .description("New description")
                    .build());
        } finally {
            verify(projectDao).getById(eq(1));
            verify(projectDao, never()).update(any(InternalProject.class));
        }
    }

    @Test(expected = IntegrityException.class)
    public void testUpdateAvatarForPersonalProjectUsingSupplier() {
        AvatarSupplier supplier = mock(AvatarSupplier.class);

        InternalPersonalProject project = mock(InternalPersonalProject.class);
        when(project.getType()).thenReturn(ProjectType.PERSONAL);

        when(projectDao.getById(eq(1))).thenReturn(project);

        try {
            projectService.updateAvatar(1, supplier);
        } finally {
            verify(project).getType();
            verify(projectDao).getById(eq(1));

            verifyZeroInteractions(supplier);
        }
    }

    @Test(expected = IntegrityException.class)
    public void testUpdateAvatarForPersonalProjectUsingUrl() {
        InternalPersonalProject project = mock(InternalPersonalProject.class);
        when(project.getType()).thenReturn(ProjectType.PERSONAL);

        when(projectDao.getById(eq(1))).thenReturn(project);

        try {
            projectService.updateAvatar(1, "This URL intentionally invalid");
        } finally {
            verify(project).getType();
            verify(projectDao).getById(eq(1));
        }
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUpdateAvatarWithBlankUrl() {
        projectService.updateAvatar(1, "\t\n     ");
    }

    @Test(expected = NullPointerException.class)
    public void testUpdateAvatarWithNullSupplier() {
        projectService.updateAvatar(1, (AvatarSupplier) null);
    }

    @Test(expected = NullPointerException.class)
    public void testUpdateAvatarWithNullUrl() {
        projectService.updateAvatar(1, (String) null);
    }

    private DefaultProjectService newService(ApplicationMode mode) {
        return new DefaultProjectService(authenticationContext, avatarService, eventPublisher, i18nService,
                mode, permissionService, permissionDao, predicateFactory, projectDao, securityService, validator);
    }

    private void testCreate(ApplicationMode mode, Class<?> expectedValidationClass) {
        projectService = newService(mode);
        InternalApplicationUser user = mock(InternalApplicationUser.class);
        when(user.getName()).thenReturn("user");

        when(authenticationContext.getCurrentUser()).thenReturn(user);
        when(projectDao.create(isA(InternalNormalProject.class))).thenAnswer(returnFirst());

        String maxKey = StringUtils.repeat("Kk", 32);
        String maxName = StringUtils.repeat("Nn", 32);
        String maxDescription = StringUtils.repeat("DdDdD", 51);

        InternalProject newProject = projectService.create(new ProjectCreateRequest.Builder().key(maxKey).name(maxName)
                .description(maxDescription).publiclyAccessible(true).build());
        assertNotNull("Created project should not be null", newProject);
        assertEquals("Key should be upper case", maxKey.toUpperCase(Locale.US), newProject.getKey());
        assertEquals("Name should be unchanged", maxName, newProject.getName());
        assertEquals("Description should be unchanged", maxDescription, newProject.getDescription());
        assertEquals("Project should be public", true, newProject.isPublic());

        ArgumentCaptor<InternalProjectPermission> permissionCaptor =
                ArgumentCaptor.forClass(InternalProjectPermission.class);

        InOrder ordered = inOrder(eventPublisher, permissionDao, projectDao, securityService, validator);
        ordered.verify(validator).validate(same(newProject), same(expectedValidationClass));
        ordered.verify(eventPublisher).publish(isA(ProjectCreationRequestedEvent.class));
        ordered.verify(projectDao).create(same(newProject));
        ordered.verify(securityService).withPermission(eq(Permission.PROJECT_ADMIN), eq("Creating project with avatar")
        );
        ordered.verify(permissionDao).create(permissionCaptor.capture());
        ordered.verify(eventPublisher).publish(isA(ProjectCreatedEvent.class));

        InternalProjectPermission permission = permissionCaptor.getValue();
        assertNotNull(permission);
        assertSame(Permission.PROJECT_ADMIN, permission.getPermission());
        assertSame(newProject, permission.getProject());
        assertSame(user, permission.getUser());
    }

    private void testUpdate(ApplicationMode mode, Class<?> expectedValidationClass) {
        projectService = newService(mode);
        InternalNormalProject project = new InternalNormalProject.Builder()
                .description("description")
                .id(1)
                .key("KEY")
                .name("name")
                .build();

        when(projectDao.getById(1)).thenReturn(project);
        when(projectDao.update(isA(InternalNormalProject.class))).thenAnswer(returnFirst());

        InternalProject updated = projectService.update(new ProjectUpdateRequest.Builder(1)
                .key("ExIsTiNg").name("new name").description("new description").publiclyAccessible(true).build());
        assertNotNull("Updated project should not be null", updated);
        assertNotSame("The loaded project should have been copied", project, updated);
        assertEquals("Key should have been set in uppercase", "EXISTING", updated.getKey());
        assertEquals("Name should have been set", "new name", updated.getName());
        assertEquals("Description should have been set", "new description", updated.getDescription());
        assertTrue("Repository should now be public", updated.isPublic());

        InOrder ordered = inOrder(eventPublisher, projectDao, validator);
        ordered.verify(validator).validate(isA(InternalNormalProject.class), same(expectedValidationClass));
        ordered.verify(eventPublisher).publish(isA(ProjectModificationRequestedEvent.class));
        ordered.verify(projectDao).update(same(updated));
        ordered.verify(eventPublisher).publish(isA(ProjectModifiedEvent.class));
    }

    private static InternalNormalUser createMockNormalUser() {
        final InternalNormalUser mock = mock(InternalNormalUser.class);
        when(mock.accept((InternalStashUserVisitor) notNull())).thenAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return ((InternalStashUserVisitor) invocation.getArguments()[0]).visit(mock);
            }
        });
        return mock;
    }
}
