package se.bjurr.sbcc.util;
import static com.atlassian.bitbucket.permission.Permission.REPO_ADMIN;
import static com.atlassian.bitbucket.repository.RefChangeType.ADD;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.io.Resources.getResource;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static se.bjurr.sbcc.JqlValidator.setJiraClient;
import static se.bjurr.sbcc.settings.SbccSettings.SETTING_GROUP_ACCEPT;
import static se.bjurr.sbcc.settings.SbccSettings.SETTING_GROUP_MATCH;
import static se.bjurr.sbcc.settings.SbccSettings.SETTING_GROUP_MESSAGE;
import static se.bjurr.sbcc.settings.SbccSettings.SETTING_RULE_MESSAGE;
import static se.bjurr.sbcc.settings.SbccSettings.SETTING_RULE_REGEXP;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import com.atlassian.applinks.api.ApplicationLinkService;
import com.atlassian.applinks.api.CredentialsRequiredException;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.hook.HookResponse;
import com.atlassian.bitbucket.hook.repository.RepositoryHookContext;
import com.atlassian.bitbucket.hook.repository.RepositoryHookService;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestRef;
import com.atlassian.bitbucket.repository.MinimalRef;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.RefChangeType;
import com.atlassian.bitbucket.repository.RefType;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.pull.MergeRequest;
import com.atlassian.bitbucket.setting.Settings;
import com.atlassian.bitbucket.setting.SettingsBuilder;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.EscalatedSecurityContext;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.bitbucket.user.UserType;
import com.atlassian.bitbucket.util.Operation;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.sal.api.net.ResponseException;
import com.atlassian.sal.api.pluginsettings.PluginSettings;
import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import com.google.common.io.Resources;
import se.bjurr.sbcc.JiraClient;
import se.bjurr.sbcc.ResultsCallable;
import se.bjurr.sbcc.SbccPreReceiveRepositoryHook;
import se.bjurr.sbcc.SbccRepositoryMergeRequestCheck;
import se.bjurr.sbcc.SbccUserAdminService;
import se.bjurr.sbcc.SbccUserAdminServiceImpl;
import se.bjurr.sbcc.commits.ChangeSetsService;
import se.bjurr.sbcc.data.SbccChangeSet;
import se.bjurr.sbcc.settings.SbccGroup;
import se.bjurr.sbcc.settings.SbccSettings;
public class RefChangeBuilder {
public static final String JIRA_REGEXP = "((?<!([A-Z]{1,10})-?)[A-Z]+-\\d+)";
public static final String JIRA_RESPONSE_EMPTY = "jiraResponseEmpty.json";
public static final String JIRA_RESPONSE_ONE = "jiraResponseOne.json";
public static final String JIRA_RESPONSE_TWO = "jiraResponseTwo.json";
public static RefChangeBuilder refChangeBuilder() {
try {
return new RefChangeBuilder();
} catch (final Throwable t) {
propagate(t);
return null;
}
}
private ApplicationLinkService applicationLinkService;
private final AuthenticationContext bitbucketAuthenticationContext;
private final ApplicationUser bitbucketUser;
private final ChangeSetsService changeSetService;
private String fromHash = "e2bc4ed00386fafe00100738f739b9f29c9f4beb";
private final SbccPreReceiveRepositoryHook hook;
private final HookResponse hookResponse;
private final Map<String, String> jiraJsonResponses = newHashMap();
private final SbccRepositoryMergeRequestCheck mergeHook;
private final List<SbccChangeSet> newChangesets;
private final OutputStream outputAll = newOutputStream();
private final PrintWriter printWriterReject = new PrintWriter(this.outputAll);
private final PrintWriter printWriterStandard = new PrintWriter(this.outputAll);
private String prMessage;
private String prSummary;
private boolean prWasAccepted;
private RefChange refChange;
private String refId = "refs/heads/master";
private final RepositoryHookContext repositoryHookContext;
private RepositoryHookService repositoryHookService;
private SbccUserAdminService sbccUserAdminService;
private SecurityService securityService;
private final Settings settings;
private String toHash = "af35d5c1a435d4f323b4e01775fa90a3eae652b3";
private RefChangeType type = ADD;
private Boolean wasAccepted = null;
@Captor private ArgumentCaptor<Map<String, ?>> mapCaptor;
@SuppressWarnings("unchecked")
private RefChangeBuilder() throws Throwable {
initMocks(this);
setJiraClient(
new JiraClient() {
@Override
protected String invokeJira(
final ApplicationLinkService applicationLinkService, final String jqlCheckQuery)
throws UnsupportedEncodingException, ResponseException, CredentialsRequiredException {
if (RefChangeBuilder.this.jiraJsonResponses.containsKey(jqlCheckQuery)) {
return RefChangeBuilder.this.jiraJsonResponses.get(jqlCheckQuery);
}
throw new RuntimeException("No faked response for: \"" + jqlCheckQuery + "\"");
}
});
this.newChangesets = newArrayList();
this.settings = mock(Settings.class);
this.repositoryHookContext = mock(RepositoryHookContext.class);
when(this.repositoryHookContext.getSettings()).thenReturn(this.settings);
final Repository repository = mock(Repository.class);
when(this.repositoryHookContext.getRepository()).thenReturn(repository);
this.changeSetService = mock(ChangeSetsService.class);
this.bitbucketAuthenticationContext = mock(AuthenticationContext.class);
this.sbccUserAdminService = mock(SbccUserAdminServiceImpl.class);
this.hook =
new SbccPreReceiveRepositoryHook(
this.changeSetService,
this.bitbucketAuthenticationContext,
this.applicationLinkService,
this.sbccUserAdminService);
this.hook.setHookName("");
final PluginSettingsFactory pluginSettingsFactory = mock(PluginSettingsFactory.class);
this.repositoryHookService = mock(RepositoryHookService.class);
final PluginSettings pluginSettings = mock(PluginSettings.class);
when(pluginSettingsFactory.createGlobalSettings()).thenReturn(pluginSettings);
final HashMap<String, Object> map = new HashMap<>();
when(pluginSettingsFactory.createGlobalSettings().get(ArgumentMatchers.anyString()))
.thenReturn(map);
final SettingsBuilder settingsBuilder = mock(SettingsBuilder.class);
when(this.repositoryHookService.createSettingsBuilder()).thenReturn(settingsBuilder);
when(this.repositoryHookService.createSettingsBuilder().addAll(mapCaptor.capture()))
.thenReturn(settingsBuilder);
when(this.repositoryHookService.createSettingsBuilder().build()).thenReturn(this.settings);
this.securityService = mock(SecurityService.class);
final EscalatedSecurityContext escalatedSecurityContext = mock(EscalatedSecurityContext.class);
final Operation<Object, RuntimeException> operation = ArgumentMatchers.any(Operation.class);
when(escalatedSecurityContext.call(operation)).thenReturn(this.settings);
when(this.securityService.withPermission(REPO_ADMIN, "Retrieving settings"))
.thenReturn(escalatedSecurityContext);
this.mergeHook =
new SbccRepositoryMergeRequestCheck(
this.changeSetService,
this.bitbucketAuthenticationContext,
this.applicationLinkService,
this.sbccUserAdminService,
pluginSettingsFactory,
this.repositoryHookService,
this.securityService);
this.hookResponse = mock(HookResponse.class);
when(this.hookResponse.out()).thenReturn(this.printWriterStandard);
when(this.hookResponse.err()).thenReturn(this.printWriterReject);
this.bitbucketUser = mock(ApplicationUser.class);
when(this.bitbucketAuthenticationContext.getCurrentUser()).thenReturn(this.bitbucketUser);
}
public RefChangeBuilder build() throws IOException {
this.refChange = newRefChange();
when(this.changeSetService.getNewChangeSets(
ArgumentMatchers.any(SbccSettings.class),
ArgumentMatchers.any(Repository.class),
ArgumentMatchers.eq(this.refId),
ArgumentMatchers.eq(this.type),
ArgumentMatchers.eq(this.fromHash),
ArgumentMatchers.eq(this.toHash)))
.thenReturn(this.newChangesets);
when(this.changeSetService.getNewChangeSets(
ArgumentMatchers.any(SbccSettings.class), ArgumentMatchers.any(PullRequest.class)))
.thenReturn(this.newChangesets);
return this;
}
public RefChangeBuilder fakeJiraResponse(final String jqlQuery, final String responseFileName)
throws IOException {
this.jiraJsonResponses.put(jqlQuery, Resources.toString(getResource(responseFileName), UTF_8));
return this;
}
public String getOutputAll() {
return this.outputAll.toString();
}
public RefChangeBuilder hasNoOutput() {
assertTrue(
"Expected output to be empty, but was\"" + getOutputAll() + "\"", getOutputAll().isEmpty());
return this;
}
public RefChangeBuilder hasOutput(final String output) {
checkNotNull(this.wasAccepted, "do 'run' before.");
assertEquals(output, getOutputAll());
return this;
}
public RefChangeBuilder hasOutputFrom(final String filename) throws IOException {
return hasOutput(Resources.toString(getResource(filename), UTF_8));
}
public RefChangeBuilder hasTrimmedFlatOutput(final String output) {
checkNotNull(this.wasAccepted, "do 'run' before.");
assertEquals(output.trim().replaceAll("\n", " "), getOutputAll().trim().replaceAll("\n", " "));
return this;
}
public RefChangeBuilder hasTrimmedPrPrintOut(final String printOut) {
assertEquals(
printOut.trim().replaceAll("\n", " "), this.prMessage.trim().replaceAll("\n", " "));
return this;
}
public RefChangeBuilder hasTrimmedPrSummary(final String summary) {
assertEquals(summary.trim().replaceAll("\n", " "), this.prSummary.trim().replaceAll("\n", " "));
return this;
}
public RefChangeBuilder prWasAccepted() {
assertTrue("Pull request was not accepted", this.prWasAccepted);
return this;
}
public RefChangeBuilder prWasRejected() {
assertFalse("Pull request was not rejected", this.prWasAccepted);
return this;
}
public RefChangeBuilder run() throws IOException {
checkNotNull(this.refChange, "do 'throwing' or 'build' before.");
this.hook.setChangesetsService(this.changeSetService);
this.wasAccepted =
this.hook.onReceive(
this.repositoryHookContext, newArrayList(this.refChange), this.hookResponse);
this.printWriterReject.flush();
this.printWriterStandard.flush();
return this;
}
public RefChangeBuilder runPullRequest() throws IOException {
checkNotNull(this.refChange, "do 'throwing' or 'build' before.");
this.mergeHook.setChangesetsService(this.changeSetService);
this.mergeHook.setResultsCallback(
new ResultsCallable() {
@Override
public void report(
final boolean isAccepted, final String summaryParam, final String messageParam) {
RefChangeBuilder.this.prWasAccepted = isAccepted;
RefChangeBuilder.this.prSummary = summaryParam;
RefChangeBuilder.this.prMessage = messageParam;
}
});
when(this.repositoryHookService.getSettings(
ArgumentMatchers.any(Repository.class), ArgumentMatchers.anyString()))
.thenReturn(this.settings);
final Repository repository = mock(Repository.class);
final MergeRequest mergeRequest = mock(MergeRequest.class);
final PullRequest pullRequest = mock(PullRequest.class);
final PullRequestRef fromRef = mock(PullRequestRef.class);
final PullRequestRef toRef = mock(PullRequestRef.class);
when(mergeRequest.getPullRequest()).thenReturn(pullRequest);
when(mergeRequest.getPullRequest().getFromRef()).thenReturn(fromRef);
when(mergeRequest.getPullRequest().getFromRef().getId()).thenReturn(this.refId);
when(mergeRequest.getPullRequest().getFromRef().getLatestCommit()).thenReturn(this.fromHash);
when(mergeRequest.getPullRequest().getFromRef().getRepository()).thenReturn(repository);
when(mergeRequest.getPullRequest().getToRef()).thenReturn(toRef);
when(mergeRequest.getPullRequest().getToRef().getLatestCommit()).thenReturn(this.toHash);
this.mergeHook.check(mergeRequest);
return this;
}
public RefChangeBuilder throwing(final IOException ioException) throws IOException {
this.refChange = newRefChange();
when(this.changeSetService.getNewChangeSets(
ArgumentMatchers.any(SbccSettings.class),
ArgumentMatchers.any(Repository.class),
ArgumentMatchers.any(String.class),
ArgumentMatchers.any(RefChangeType.class),
ArgumentMatchers.any(String.class),
ArgumentMatchers.any(String.class)))
.thenThrow(ioException);
return this;
}
public RefChangeBuilder wasAccepted() {
assertEquals("Expected accepted", TRUE, this.wasAccepted);
return this;
}
public RefChangeBuilder wasRejected() {
assertEquals("Expected rejection", FALSE, this.wasAccepted);
return this;
}
public RefChangeBuilder withBitbucketDisplayName(final String name) {
when(this.bitbucketUser.getDisplayName()).thenReturn(name);
return this;
}
public RefChangeBuilder withBitbucketEmail(final String email) {
when(this.bitbucketUser.getEmailAddress()).thenReturn(email);
return this;
}
public RefChangeBuilder withBitbucketName(final String name) {
when(this.bitbucketUser.getName()).thenReturn(name);
return this;
}
public RefChangeBuilder withBitbucketUserSlug(final String name) {
when(this.bitbucketUser.getSlug()).thenReturn(name);
return this;
}
public RefChangeBuilder withBitbucketUserType(final UserType type) {
when(this.bitbucketUser.getType()).thenReturn(type);
return this;
}
public RefChangeBuilder withChangeSet(final SbccChangeSet changeSet) {
this.newChangesets.add(changeSet);
return this;
}
public RefChangeBuilder withFromHash(final String fromHash) {
this.fromHash = fromHash;
return this;
}
public RefChangeBuilder withGroupAcceptingAtLeastOneJira() {
return this.withSetting(SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.ONE.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "You need to specity an issue") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA");
}
public RefChangeBuilder withGroupAcceptingJirasAndAnotherRejectingInc() {
return this.withSetting(SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.ALL.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "You need to specity JIRA") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA") //
.withSetting(SETTING_GROUP_ACCEPT + "[1]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[1]", SbccGroup.Match.NONE.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[1]", "Dont include INC") //
.withSetting(SETTING_RULE_REGEXP + "[1][0]", "INC") //
.withSetting(SETTING_RULE_MESSAGE + "[1][0]", "Incident, INC");
}
public RefChangeBuilder withGroupAcceptingOnlyBothJiraAndIncInEachCommit() {
return this.withSetting(SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.ALL.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "You need to specity JIRA and INC") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA") //
.withSetting(SETTING_RULE_REGEXP + "[0][1]", "INC") //
.withSetting(SETTING_RULE_MESSAGE + "[0][1]", "Incident, INC");
}
public RefChangeBuilder withGroupAcceptingOnlyJiraAndAnotherGroupAcceptingOnlyInc() {
return this.withSetting(SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.ALL.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "You need to specity JIRA") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA") //
.withSetting(SETTING_GROUP_ACCEPT + "[1]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[1]", SbccGroup.Match.ALL.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[1]", "You need to specity INC") //
.withSetting(SETTING_RULE_REGEXP + "[1][0]", "INC") //
.withSetting(SETTING_RULE_MESSAGE + "[1][0]", "Incident, INC");
}
public RefChangeBuilder withGroupRejectingAnyCommitContainingJiraOrInc() {
return this.withSetting(SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.ACCEPT.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.NONE.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "Do not specify issues") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA") //
.withSetting(SETTING_RULE_REGEXP + "[0][1]", "INC") //
.withSetting(SETTING_RULE_MESSAGE + "[0][1]", "Incident, INC");
}
public RefChangeBuilder withGroupShowingMessageForAnyCommitContainingAtLeastOneJira() {
return this.withSetting(
SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.SHOW_MESSAGE.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.ONE.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "Thanks for specifying a Jira =)") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA");
}
public RefChangeBuilder withGroupShowingMessageToAllCommitsNotContainingJiraOrInc() {
return this.withSetting(
SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.SHOW_MESSAGE.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.NONE.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "Thanks for not specifying a Jira or INC =)") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA") //
.withSetting(SETTING_RULE_REGEXP + "[0][1]", "INC") //
.withSetting(SETTING_RULE_MESSAGE + "[0][1]", "Incident, INC");
}
public RefChangeBuilder withGroupShowingMessageToEveryCommitContainingJiraOrInc() {
return this.withSetting(
SETTING_GROUP_ACCEPT + "[0]", SbccGroup.Accept.SHOW_MESSAGE.toString()) //
.withSetting(SETTING_GROUP_MATCH + "[0]", SbccGroup.Match.ALL.toString()) //
.withSetting(SETTING_GROUP_MESSAGE + "[0]", "Thanks for specifying a Jira and INC =)") //
.withSetting(SETTING_RULE_REGEXP + "[0][0]", JIRA_REGEXP) //
.withSetting(SETTING_RULE_MESSAGE + "[0][0]", "JIRA") //
.withSetting(SETTING_RULE_REGEXP + "[0][1]", "INC[0-9]*") //
.withSetting(SETTING_RULE_MESSAGE + "[0][1]", "Incident, INC");
}
public RefChangeBuilder withHookNameVersion(final String hookNameVersion) {
this.hook.setHookName(hookNameVersion);
return this;
}
public RefChangeBuilder withRefChange(final RefChange refChange) {
this.refChange = refChange;
return this;
}
public RefChangeBuilder withRefId(final String refId) {
this.refId = refId;
return this;
}
public RefChangeBuilder withSetting(final String field, final Boolean value) {
when(this.settings.getBoolean(field)).thenReturn(value);
return this;
}
public RefChangeBuilder withSetting(final String field, final String value) {
when(this.settings.getString(field)).thenReturn(value);
return this;
}
public RefChangeBuilder withToHash(final String toHash) {
this.toHash = toHash;
return this;
}
public RefChangeBuilder withType(final RefChangeType type) {
this.type = type;
return this;
}
public RefChangeBuilder withUserInBitbucket(
final String displayName, final String email, final String name) {
@SuppressWarnings("unchecked")
final Page<ApplicationUser> page = mock(Page.class);
when(page.getSize()) //
.thenReturn(1);
when(this.sbccUserAdminService.emailExists(email)) //
.thenReturn(true);
when(this.sbccUserAdminService.displayNameExists(displayName)) //
.thenReturn(true);
return this;
}
private OutputStream newOutputStream() {
return new OutputStream() {
private final StringBuilder string = new StringBuilder();
@Override
public String toString() {
return this.string.toString();
}
@Override
public void write(final int b) throws IOException {
this.string.append((char) b);
}
};
}
private RefChange newRefChange() {
final RefChange refChange =
new RefChange() {
@Override
public String getFromHash() {
return RefChangeBuilder.this.fromHash;
}
@Override
public MinimalRef getRef() {
return new MinimalRef() {
@Override
public String getDisplayId() {
return null;
}
@Override
public String getId() {
return RefChangeBuilder.this.refId;
}
@Override
public RefType getType() {
return null;
}
};
}
public String getRefId() {
return RefChangeBuilder.this.refId;
}
@Override
public String getToHash() {
return RefChangeBuilder.this.toHash;
}
@Override
public RefChangeType getType() {
return RefChangeBuilder.this.type;
}
};
return refChange;
}
}