/* * The MIT License (MIT) * * Copyright (c) 2014 Andreas Alanko, Emil Nilsson, Sony Mobile Communications AB. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.sonymobile.jenkins.plugins.gitlab.gitlabauth; import com.google.common.base.Ticker; import com.google.common.cache.CacheBuilder; import com.sonymobile.gitlab.api.GitLabApiClient; import com.sonymobile.gitlab.exceptions.GroupNotFoundException; import com.sonymobile.gitlab.exceptions.UserNotFoundException; import com.sonymobile.gitlab.model.GitLabAccessLevel; import com.sonymobile.gitlab.model.GitLabGroupInfo; import com.sonymobile.gitlab.model.GitLabGroupMemberInfo; import com.sonymobile.gitlab.model.GitLabUserInfo; import com.sonymobile.jenkins.plugins.gitlab.gitlabapi.GitLabConfiguration; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.List; import java.util.concurrent.TimeUnit; import static com.sonymobile.jenkins.plugins.gitlab.gitlabauth.helpers.MockDataCreators.mockGroupInfo; import static com.sonymobile.jenkins.plugins.gitlab.gitlabauth.helpers.MockDataLoaders.loadAdminUser; import static com.sonymobile.jenkins.plugins.gitlab.gitlabauth.helpers.MockDataLoaders.loadGroupMembers; import static com.sonymobile.jenkins.plugins.gitlab.gitlabauth.helpers.MockDataLoaders.loadGroups; import static com.sonymobile.jenkins.plugins.gitlab.gitlabauth.helpers.MockDataLoaders.loadUser; import static org.apache.commons.lang.StringUtils.EMPTY; import static org.easymock.EasyMock.expect; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertThat; import static org.powermock.api.easymock.PowerMock.createMock; import static org.powermock.api.easymock.PowerMock.mockStatic; import static org.powermock.api.easymock.PowerMock.replay; import static org.powermock.api.easymock.PowerMock.reset; import static org.powermock.api.easymock.PowerMock.verify; import static org.powermock.reflect.Whitebox.getInnerClassType; import static org.powermock.reflect.Whitebox.invokeConstructor; import static org.powermock.reflect.Whitebox.invokeMethod; /** * Tests for the {@link GitLab} class. * * @author Emil Nilsson */ @RunWith(PowerMockRunner.class) @PrepareForTest({GitLabApiClient.class, GitLabConfiguration.class}) public class GitLabTest { /** The number of nanoseconds in a second, for the ticker. */ private static final long SECONDS = TimeUnit.SECONDS.toNanos(1); /** The number of nanoseconds in a minute, for the ticker. */ private static final long MINUTES = TimeUnit.MINUTES.toNanos(1); /** A mock for the GitLab API client returned by GitLabConfiguration. */ private GitLabApiClient mockApiClient; /** A mock ticker for cache tests. */ private MockTicker mockTicker; /** * Prepares tests by adding a mock for the GitLab API client. */ @Before public void setUp() throws Exception { // create mock for the GitLab API client mockApiClient = createMock(GitLabApiClient.class); // mock GitLabConfiguration to return the mocked GitLab API Client mockStatic(GitLabConfiguration.class); expect(GitLabConfiguration.getApiClient()).andReturn(mockApiClient).anyTimes(); replay(GitLabConfiguration.class); // create ticker for testing cache mockTicker = new MockTicker(); // create singleton implementation for GitLab using the mock ticker Object implementation = invokeConstructor(getInnerClassType(GitLab.class, "Implementation"), new Class<?>[] { CacheBuilder.class }, new Object[] { CacheBuilder.newBuilder().ticker(mockTicker) }); // replace the singleton implementation instance of GitLab invokeMethod(GitLab.class, "setInstance", implementation); } @Test public void getUser() throws Exception { // user 1 exists, user 1000 does not expect(mockApiClient.getUser(1)).andReturn(loadUser()); expect(mockApiClient.getUser(1000)).andThrow(new UserNotFoundException(EMPTY)); replay(mockApiClient); GitLabUserInfo goodUser = GitLab.getUser(1); GitLabUserInfo badUser = GitLab.getUser(1000); assertThat("user 1 should exist", goodUser, is(notNullValue())); assertThat(1, is(goodUser.getId())); assertThat("user 1000 should not exist", badUser, is(nullValue())); verify(mockApiClient); } /** * Tests caching with {@link GitLab#getUser(int)}. */ @Test public void cachedGetUser() throws Exception { // before cache invalidation { // should only access API once for both method calls expect(mockApiClient.getUser(1)).andReturn(loadUser()).once(); replay(mockApiClient); assertThat("username", is(GitLab.getUser(1).getUsername())); // advance time without forcing cache to invalidate mockTicker.value += 10 * SECONDS; assertThat("username", is(GitLab.getUser(1).getUsername())); verify(mockApiClient); } reset(mockApiClient); // advance time to force the cache to invalidate mockTicker.value += 1 * MINUTES; // after cache invalidation { // return an updated user object expect(mockApiClient.getUser(1)).andReturn(loadUser("newer")).once(); replay(mockApiClient); // should access API again assertThat("newusername", is(GitLab.getUser(1).getUsername())); verify(mockApiClient); } } @Test public void getGroupMember() throws Exception { expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1)).anyTimes(); expect(mockApiClient.getGroupMembers(1000)).andThrow(new GroupNotFoundException(EMPTY)); replay(mockApiClient); GitLabGroupMemberInfo goodMember = GitLab.getGroupMember(/* userId */ 1, /* groupId */ 1); GitLabGroupMemberInfo badMember = GitLab.getGroupMember(/* userId */ 1000, /* groupId */ 1); GitLabGroupMemberInfo memberOfBadGroup = GitLab.getGroupMember(/* userId */ 1, /* groupId */ 1000); assertThat("user 1 should be a member of the group", goodMember, is(notNullValue())); assertThat(1, is(goodMember.getId())); assertThat(1, is(goodMember.getGroupId())); assertThat("user 1000 should not be a member of the group", badMember, is(nullValue())); assertThat("group 1000 should not exist", memberOfBadGroup, is(nullValue())); verify(mockApiClient); } @Test public void getGroupMemberByPath() throws Exception { expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1)).anyTimes(); // will call getGroups to find the groups expect(mockApiClient.getGroups()).andReturn(loadGroups()).anyTimes(); replay(mockApiClient); GitLabGroupMemberInfo goodMember = GitLab.getGroupMember(/* userId */ 1, "groupname"); GitLabGroupMemberInfo badMember = GitLab.getGroupMember(/* userId */ 1000, "groupname"); GitLabGroupMemberInfo memberOfBadGroup = GitLab.getGroupMember(/* userId */ 1, "notreal"); assertThat("user 1 should be a member of the group", goodMember, is(notNullValue())); assertThat(1, is(goodMember.getId())); assertThat(1, is(goodMember.getGroupId())); assertThat("user 1000 should not be a member of the group", badMember, is(nullValue())); assertThat("group 1000 should not exist", memberOfBadGroup, is(nullValue())); verify(mockApiClient); } /** * Tests caching with {@link GitLab#getGroupMember(int, int)}}. */ @Test public void cachedGetGroupMember() throws Exception { // before cache invalidation { // should only access API once for both method calls expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1)).once(); replay(mockApiClient); assertThat("username", is(GitLab.getGroupMember(1, 1).getUsername())); // advance time without forcing cache to invalidate mockTicker.value += 10 * SECONDS; assertThat("username", is(GitLab.getGroupMember(1, 1).getUsername())); verify(mockApiClient); } reset(mockApiClient); // advance time to force the cache to invalidate mockTicker.value += 1 * MINUTES; // after cache invalidation { // return an updated member object expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1, "newer")).once(); replay(mockApiClient); // should access API again assertThat("newusername", is(GitLab.getGroupMember(1, 1).getUsername())); verify(mockApiClient); } } @Test public void getGroups() throws Exception { expect(mockApiClient.getGroups()).andReturn(loadGroups()); replay(mockApiClient); List<GitLabGroupInfo> groups = GitLab.getGroups(); assertThat(groups, hasSize(1)); GitLabGroupInfo group = groups.get(0); assertThat(1, is(group.getId())); verify(mockApiClient); } @Test public void getGroupsAsUser() throws Exception { expect(mockApiClient.asUser(1)).andReturn(mockApiClient); expect(mockApiClient.getGroups()).andReturn(loadGroups()); replay(mockApiClient); List<GitLabGroupInfo> groups = GitLab.getGroupsAsUser(1); GitLabGroupInfo group = groups.get(0); assertThat(1, is(group.getId())); verify(mockApiClient); } @Test public void cachedGetGroups() throws Exception { // before cache invalidation { // should only access API once for both method calls expect(mockApiClient.getGroups()).andReturn(loadGroups()).once(); replay(mockApiClient); assertThat(GitLab.getGroups(), hasSize(1)); // advance time without forcing cache to invalidate mockTicker.value += 10 * SECONDS; assertThat(GitLab.getGroups(), hasSize(1)); verify(mockApiClient); } reset(mockApiClient); // advance time to force the cache to invalidate mockTicker.value += 1 * MINUTES; // after cache invalidation { // return an updated group list expect(mockApiClient.getGroups()).andReturn(loadGroups("newer")).once(); replay(mockApiClient); // should access API again assertThat(GitLab.getGroups(), hasSize(2)); verify(mockApiClient); } } @Test public void getGroup() throws Exception { expect(mockApiClient.getGroups()).andReturn(loadGroups()); replay(mockApiClient); GitLabGroupInfo group = GitLab.getGroup(1); assertThat("Group should exist", group, is(notNullValue())); assertThat(group.getId(), is(1)); assertThat("Group should not exist", GitLab.getGroup(1000), is(nullValue())); verify(mockApiClient); } @Test public void getGroupByPath() throws Exception { expect(mockApiClient.getGroups()).andReturn(loadGroups()); replay(mockApiClient); GitLabGroupInfo group = GitLab.getGroupByPath("groupname"); assertThat("Group should exist", group, is(notNullValue())); assertThat(1, is(group.getId())); assertThat("Group should not exist", GitLab.getGroupByPath("notreal"), is(nullValue())); verify(mockApiClient); } @Test public void getGroupsOwnedByUser() throws Exception { // should impersonate as each of the users expect(mockApiClient.asUser(1)).andReturn(mockApiClient); expect(mockApiClient.asUser(2)).andReturn(mockApiClient); expect(mockApiClient.asUser(3)).andReturn(mockApiClient); expect(mockApiClient.getGroups()).andReturn(loadGroups()).anyTimes(); expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1)).anyTimes(); replay(mockApiClient); // only user 3 is an owner of the group assertThat(GitLab.getGroupsOwnedByUser(1), is(empty())); assertThat(GitLab.getGroupsOwnedByUser(2), is(empty())); assertThat(GitLab.getGroupsOwnedByUser(3), hasSize(1)); verify(mockApiClient); } @Test public void isAdmin() throws Exception { // user 1 is an admin, user 2 is not, user 1000 doesn't exist expect(mockApiClient.getUser(1)).andReturn(loadAdminUser()); expect(mockApiClient.getUser(2)).andReturn(loadUser()); expect(mockApiClient.getUser(1000)).andThrow(new UserNotFoundException(EMPTY)); replay(mockApiClient); assertThat(GitLab.isAdmin(1), is(true)); assertThat(GitLab.isAdmin(2), is(false)); assertThat(GitLab.isAdmin(1000), is(false)); verify(mockApiClient); } @Test public void isGroupOwner() throws Exception { // user 1 is a developer, user 2 is a guest and user 3 is an owner expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1)).anyTimes(); replay(mockApiClient); assertThat(GitLab.isGroupOwner(/* userId */ 1, /* groupId */ 1), is(false)); assertThat(GitLab.isGroupOwner(/* userId */ 2, /* groupId */ 1), is(false)); assertThat(GitLab.isGroupOwner(/* userId */ 3, /* groupId */ 1), is(true)); verify(mockApiClient); } @Test public void getAccessLevelInGroup() throws Exception { expect(mockApiClient.getGroupMembers(1)).andReturn(loadGroupMembers(1)).anyTimes(); replay(mockApiClient); // user 1 is an developer, user 2 is a guest and user 1000 isn't member of the group assertThat(GitLab.getAccessLevelInGroup(/* userId */ 1, /* groupId */ 1), is(GitLabAccessLevel.DEVELOPER)); assertThat(GitLab.getAccessLevelInGroup(/* userId */ 2, /* groupId */ 1), is(GitLabAccessLevel.GUEST)); assertThat(GitLab.getAccessLevelInGroup(/* userId */ 1000, /* groupId */ 1), is(GitLabAccessLevel.NONE)); verify(mockApiClient); } @Test public void getUrlForGroup() throws Exception { expect(mockApiClient.getHost()).andReturn("http://example.com"); replay(mockApiClient); GitLabGroupInfo group = mockGroupInfo(1, "Group Name", "groupname"); assertThat(GitLab.getUrlForGroup(group), is("http://example.com/groups/groupname")); verify(mockApiClient); } /** * A fake Ticker for cache tests. */ private static class MockTicker extends Ticker { /** The ticker value. */ public long value = 0l; @Override public long read() { return value; } } }