| // Copyright (C) 2019 The Android Open Source Project |
| // |
| // 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 com.googlesource.gerrit.plugins.replication; |
| |
| import static com.googlesource.gerrit.plugins.replication.RemoteRefUpdateCollectionMatcher.eqRemoteRef; |
| import static org.easymock.EasyMock.anyObject; |
| import static org.easymock.EasyMock.createNiceMock; |
| import static org.easymock.EasyMock.expect; |
| import static org.easymock.EasyMock.getCurrentArguments; |
| import static org.easymock.EasyMock.replay; |
| import static org.easymock.EasyMock.verify; |
| import static org.eclipse.jgit.lib.Ref.Storage.NEW; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.extensions.registration.DynamicItem; |
| import com.google.gerrit.metrics.Timer1; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.git.PerThreadRequestScope; |
| import com.google.gerrit.server.permissions.PermissionBackend; |
| import com.google.gerrit.server.permissions.PermissionBackend.ForProject; |
| import com.google.gerrit.server.permissions.PermissionBackend.WithUser; |
| import com.google.gerrit.server.project.ProjectCache; |
| import com.google.gerrit.server.project.ProjectState; |
| import com.google.gerrit.server.util.IdGenerator; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import junit.framework.AssertionFailedError; |
| import org.easymock.IAnswer; |
| import org.eclipse.jgit.errors.NotSupportedException; |
| import org.eclipse.jgit.errors.TransportException; |
| import org.eclipse.jgit.lib.Config; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectIdRef; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.storage.file.FileBasedConfig; |
| import org.eclipse.jgit.transport.FetchConnection; |
| import org.eclipse.jgit.transport.PushConnection; |
| import org.eclipse.jgit.transport.PushResult; |
| import org.eclipse.jgit.transport.RefSpec; |
| import org.eclipse.jgit.transport.RemoteConfig; |
| import org.eclipse.jgit.transport.RemoteRefUpdate; |
| import org.eclipse.jgit.transport.Transport; |
| import org.eclipse.jgit.transport.URIish; |
| import org.eclipse.jgit.util.FS; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class PushOneTest { |
| private static final int TEST_PUSH_TIMEOUT_SECS = 10; |
| |
| private GitRepositoryManager gitRepositoryManagerMock; |
| private Repository repositoryMock; |
| private PermissionBackend permissionBackendMock; |
| private PermissionBackend.WithUser withUserMock; |
| private PermissionBackend.ForProject forProjectMock; |
| |
| private Destination destinationMock; |
| private RemoteConfig remoteConfigMock; |
| private RefSpec refSpecMock; |
| private CredentialsFactory credentialsFactory; |
| private PerThreadRequestScope.Scoper threadRequestScoperMock; |
| private ReplicationQueue replicationQueueMock; |
| private IdGenerator idGeneratorMock; |
| private ReplicationStateListeners replicationStateListenersMock; |
| private ReplicationMetrics replicationMetricsMock; |
| private Timer1.Context timerContextMock; |
| private ProjectCache projectCacheMock; |
| private TransportFactory transportFactoryMock; |
| private Transport transportMock; |
| private FetchConnection fetchConnection; |
| private PushConnection pushConnection; |
| private ProjectState projectStateMock; |
| private RefUpdate refUpdateMock; |
| |
| private Project.NameKey projectNameKey; |
| private URIish urish; |
| private Map<String, Ref> localRefs; |
| |
| private Map<String, Ref> remoteRefs; |
| private CountDownLatch isCallFinished; |
| private Ref newLocalRef; |
| |
| @Before |
| public void setup() throws Exception { |
| projectNameKey = new Project.NameKey("fooProject"); |
| urish = new URIish("http://foo.com/fooProject.git"); |
| |
| newLocalRef = |
| new ObjectIdRef.Unpeeled( |
| NEW, "foo", ObjectId.fromString("0000000000000000000000000000000000000001")); |
| |
| localRefs = new HashMap<>(); |
| localRefs.put("fooProject", newLocalRef); |
| |
| Ref remoteRef = new ObjectIdRef.Unpeeled(NEW, "foo", ObjectId.zeroId()); |
| remoteRefs = new HashMap<>(); |
| remoteRefs.put("fooProject", remoteRef); |
| |
| isCallFinished = new CountDownLatch(1); |
| |
| setupMocks(); |
| } |
| |
| private void setupMocks() throws Exception { |
| FileBasedConfig config = new FileBasedConfig(new Config(), new File("/foo"), FS.DETECTED); |
| config.setString("remote", "Replication", "push", "foo"); |
| |
| setupRefUpdateMock(); |
| setupRepositoryMock(config); |
| setupGitRepoManagerMock(); |
| |
| projectStateMock = createNiceMock(ProjectState.class); |
| forProjectMock = createNiceMock(ForProject.class); |
| setupWithUserMock(); |
| setupPermissionBackedMock(); |
| |
| setupDestinationMock(); |
| |
| setupRefSpecMock(); |
| setupRemoteConfigMock(); |
| |
| credentialsFactory = createNiceMock(CredentialsFactory.class); |
| |
| setupFetchConnectionMock(); |
| setupPushConnectionMock(); |
| setupRequestScopeMock(); |
| replicationQueueMock = createNiceMock(ReplicationQueue.class); |
| idGeneratorMock = createNiceMock(IdGenerator.class); |
| replicationStateListenersMock = createNiceMock(ReplicationStateListeners.class); |
| |
| timerContextMock = createNiceMock(Timer1.Context.class); |
| setupReplicationMetricsMock(); |
| |
| setupTransportMock(); |
| |
| setupProjectCacheMock(); |
| |
| replay( |
| gitRepositoryManagerMock, |
| refUpdateMock, |
| repositoryMock, |
| permissionBackendMock, |
| destinationMock, |
| remoteConfigMock, |
| credentialsFactory, |
| threadRequestScoperMock, |
| replicationQueueMock, |
| idGeneratorMock, |
| replicationStateListenersMock, |
| replicationMetricsMock, |
| projectCacheMock, |
| timerContextMock, |
| transportFactoryMock, |
| projectStateMock, |
| withUserMock, |
| forProjectMock, |
| fetchConnection, |
| pushConnection, |
| refSpecMock); |
| } |
| |
| @Test |
| public void shouldPushAllRefsWhenNoFilters() throws InterruptedException, IOException { |
| shouldPushAllRefsWithDynamicItemFilter(DynamicItem.itemOf(ReplicationPushFilter.class, null)); |
| } |
| |
| @Test |
| public void shouldPushAllRefsWhenNoFiltersSetup() throws InterruptedException, IOException { |
| shouldPushAllRefsWithDynamicItemFilter(null); |
| } |
| |
| private void shouldPushAllRefsWithDynamicItemFilter( |
| DynamicItem<ReplicationPushFilter> replicationPushFilter) |
| throws IOException, NotSupportedException, TransportException, InterruptedException { |
| List<RemoteRefUpdate> expectedUpdates = |
| Arrays.asList( |
| new RemoteRefUpdate( |
| repositoryMock, |
| newLocalRef.getName(), |
| newLocalRef.getObjectId(), |
| "fooProject", |
| false, |
| "fooProject", |
| null)); |
| |
| PushResult pushResult = new PushResult(); |
| |
| expect(transportMock.push(anyObject(), eqRemoteRef(expectedUpdates))) |
| .andReturn(pushResult) |
| .once(); |
| replay(transportMock); |
| |
| PushOne pushOne = createPushOne(replicationPushFilter); |
| |
| pushOne.addRef(PushOne.ALL_REFS); |
| pushOne.run(); |
| |
| isCallFinished.await(TEST_PUSH_TIMEOUT_SECS, TimeUnit.SECONDS); |
| |
| verify(transportMock); |
| } |
| |
| @Test |
| public void shouldBlockReplicationUsingPushFilter() throws InterruptedException, IOException { |
| DynamicItem<ReplicationPushFilter> replicationPushFilter = |
| DynamicItem.itemOf( |
| ReplicationPushFilter.class, |
| new ReplicationPushFilter() { |
| |
| @Override |
| public List<RemoteRefUpdate> filter( |
| String projectName, List<RemoteRefUpdate> remoteUpdatesList) { |
| return Collections.emptyList(); |
| } |
| }); |
| |
| // easymock way to check if method was never called |
| expect(transportMock.push(anyObject(), anyObject())) |
| .andThrow(new AssertionFailedError()) |
| .anyTimes(); |
| replay(transportMock); |
| |
| PushOne pushOne = createPushOne(replicationPushFilter); |
| |
| pushOne.addRef(PushOne.ALL_REFS); |
| pushOne.run(); |
| |
| isCallFinished.await(10, TimeUnit.SECONDS); |
| |
| verify(transportMock); |
| } |
| |
| private PushOne createPushOne(DynamicItem<ReplicationPushFilter> replicationPushFilter) { |
| PushOne push = |
| new PushOne( |
| gitRepositoryManagerMock, |
| permissionBackendMock, |
| destinationMock, |
| remoteConfigMock, |
| credentialsFactory, |
| threadRequestScoperMock, |
| replicationQueueMock, |
| idGeneratorMock, |
| replicationStateListenersMock, |
| replicationMetricsMock, |
| projectCacheMock, |
| transportFactoryMock, |
| projectNameKey, |
| urish); |
| |
| push.setReplicationPushFilter(replicationPushFilter); |
| return push; |
| } |
| |
| private void setupProjectCacheMock() throws IOException { |
| projectCacheMock = createNiceMock(ProjectCache.class); |
| expect(projectCacheMock.checkedGet(projectNameKey)).andReturn(projectStateMock); |
| } |
| |
| private void setupTransportMock() throws NotSupportedException, TransportException { |
| transportMock = createNiceMock(Transport.class); |
| expect(transportMock.openFetch()).andReturn(fetchConnection); |
| transportFactoryMock = createNiceMock(TransportFactory.class); |
| expect(transportFactoryMock.open(repositoryMock, urish)).andReturn(transportMock).anyTimes(); |
| } |
| |
| private void setupReplicationMetricsMock() { |
| replicationMetricsMock = createNiceMock(ReplicationMetrics.class); |
| expect(replicationMetricsMock.start(anyObject())).andReturn(timerContextMock); |
| } |
| |
| private void setupRequestScopeMock() { |
| threadRequestScoperMock = createNiceMock(PerThreadRequestScope.Scoper.class); |
| expect(threadRequestScoperMock.scope(anyObject())) |
| .andAnswer( |
| new IAnswer<Callable<Object>>() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public Callable<Object> answer() throws Throwable { |
| Callable<Object> originalCall = (Callable<Object>) getCurrentArguments()[0]; |
| return new Callable<Object>() { |
| |
| @Override |
| public Object call() throws Exception { |
| Object result = originalCall.call(); |
| isCallFinished.countDown(); |
| return result; |
| } |
| }; |
| } |
| }) |
| .anyTimes(); |
| } |
| |
| private void setupPushConnectionMock() { |
| pushConnection = createNiceMock(PushConnection.class); |
| expect(pushConnection.getRefsMap()).andReturn(remoteRefs); |
| } |
| |
| private void setupFetchConnectionMock() { |
| fetchConnection = createNiceMock(FetchConnection.class); |
| expect(fetchConnection.getRefsMap()).andReturn(remoteRefs); |
| } |
| |
| private void setupRemoteConfigMock() { |
| remoteConfigMock = createNiceMock(RemoteConfig.class); |
| expect(remoteConfigMock.getPushRefSpecs()).andReturn(ImmutableList.of(refSpecMock)); |
| } |
| |
| private void setupRefSpecMock() { |
| refSpecMock = createNiceMock(RefSpec.class); |
| expect(refSpecMock.matchSource(anyObject(String.class))).andReturn(true); |
| expect(refSpecMock.expandFromSource(anyObject(String.class))).andReturn(refSpecMock); |
| expect(refSpecMock.getDestination()).andReturn("fooProject").anyTimes(); |
| expect(refSpecMock.isForceUpdate()).andReturn(false).anyTimes(); |
| } |
| |
| private void setupDestinationMock() { |
| destinationMock = createNiceMock(Destination.class); |
| expect(destinationMock.requestRunway(anyObject())).andReturn(RunwayStatus.allowed()); |
| } |
| |
| private void setupPermissionBackedMock() { |
| permissionBackendMock = createNiceMock(PermissionBackend.class); |
| expect(permissionBackendMock.currentUser()).andReturn(withUserMock); |
| } |
| |
| private void setupWithUserMock() { |
| withUserMock = createNiceMock(WithUser.class); |
| expect(withUserMock.project(projectNameKey)).andReturn(forProjectMock); |
| } |
| |
| private void setupGitRepoManagerMock() throws IOException { |
| gitRepositoryManagerMock = createNiceMock(GitRepositoryManager.class); |
| expect(gitRepositoryManagerMock.openRepository(projectNameKey)).andReturn(repositoryMock); |
| } |
| |
| @SuppressWarnings("deprecation") |
| private void setupRepositoryMock(FileBasedConfig config) throws IOException { |
| repositoryMock = createNiceMock(Repository.class); |
| expect(repositoryMock.getConfig()).andReturn(config).anyTimes(); |
| expect(repositoryMock.getAllRefs()).andReturn(localRefs); |
| expect(repositoryMock.updateRef("fooProject")).andReturn(refUpdateMock); |
| } |
| |
| private void setupRefUpdateMock() { |
| refUpdateMock = createNiceMock(RefUpdate.class); |
| expect(refUpdateMock.getOldObjectId()) |
| .andReturn(ObjectId.fromString("0000000000000000000000000000000000000001")) |
| .anyTimes(); |
| } |
| } |