it.AsynchronousIssueRestClientTest.java Source code

Java tutorial

Introduction

Here is the source code for it.AsynchronousIssueRestClientTest.java

Source

/*
 * Copyright (C) 2010 Atlassian
 *
 * 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 it;

import com.atlassian.jira.functest.framework.UserProfile;
import com.atlassian.jira.nimblefunctests.annotation.JiraBuildNumberDependent;
import com.atlassian.jira.nimblefunctests.annotation.LongCondition;
import com.atlassian.jira.rest.client.IntegrationTestUtil;
import com.atlassian.jira.rest.client.TestUtil;
import com.atlassian.jira.rest.client.api.GetCreateIssueMetadataOptionsBuilder;
import com.atlassian.jira.rest.client.api.IssueRestClient;
import com.atlassian.jira.rest.client.api.RestClientException;
import com.atlassian.jira.rest.client.api.domain.Attachment;
import com.atlassian.jira.rest.client.api.domain.BasicIssue;
import com.atlassian.jira.rest.client.api.domain.CimIssueType;
import com.atlassian.jira.rest.client.api.domain.CimProject;
import com.atlassian.jira.rest.client.api.domain.Comment;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.IssueLink;
import com.atlassian.jira.rest.client.api.domain.IssueLinkType;
import com.atlassian.jira.rest.client.api.domain.Transition;
import com.atlassian.jira.rest.client.api.domain.input.AttachmentInput;
import com.atlassian.jira.rest.client.api.domain.input.ComplexIssueInputFieldValue;
import com.atlassian.jira.rest.client.api.domain.input.FieldInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;
import com.atlassian.jira.rest.client.api.domain.input.LinkIssuesInput;
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput;
import com.atlassian.jira.rest.client.api.domain.util.ErrorCollection;
import com.atlassian.jira.rest.client.internal.json.TestConstants;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import javax.annotation.Nullable;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.atlassian.jira.rest.client.IntegrationTestUtil.NUMERIC_CUSTOMFIELD_ID;
import static com.atlassian.jira.rest.client.IntegrationTestUtil.NUMERIC_CUSTOMFIELD_TYPE;
import static com.atlassian.jira.rest.client.IntegrationTestUtil.NUMERIC_CUSTOMFIELD_TYPE_V5;
import static com.atlassian.jira.rest.client.IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER;
import static com.atlassian.jira.rest.client.IntegrationTestUtil.TEXT_CUSTOMFIELD_ID;
import static com.atlassian.jira.rest.client.IntegrationTestUtil.USER2;
import static com.atlassian.jira.rest.client.TestUtil.assertErrorCode;
import static com.atlassian.jira.rest.client.TestUtil.assertExpectedErrorCollection;
import static com.atlassian.jira.rest.client.api.domain.EntityHelper.findEntityByName;
import static com.atlassian.jira.rest.client.internal.ServerVersionConstants.BN_JIRA_4_3;
import static com.atlassian.jira.rest.client.internal.ServerVersionConstants.BN_JIRA_5;
import static com.atlassian.jira.rest.client.internal.json.TestConstants.ADMIN_PASSWORD;
import static com.atlassian.jira.rest.client.internal.json.TestConstants.ADMIN_USERNAME;
import static com.atlassian.jira.rest.client.internal.json.TestConstants.USER1_USERNAME;
import static com.atlassian.jira.rest.client.internal.json.TestConstants.USER2_USERNAME;
import static com.atlassian.jira.rest.client.test.matchers.RestClientExceptionMatchers.rceWithSingleError;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.junit.Assert.*;

// Ignore "May produce NPE" warnings, as we know what we are doing in tests
@SuppressWarnings("ConstantConditions")
public class AsynchronousIssueRestClientTest extends AbstractAsynchronousRestClientTest {

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    public static final String UTF8_FILE_BODY = "File body encoded in utf8: Ka\u017a\u0144 i \u017c\u00f3\u0142to\u015b\u0107 b\u0119d\u0105! | \u1f55\u03b1\u03bb\u03bf\u03bd \u03d5\u03b1\u03b3\u03b5\u1fd6\u03bd \u03b4\u1f7b\u03bd\u03b1\u03bc\u03b1\u03b9\u0387 \u03c4\u03bf\u1fe6\u03c4\u03bf \u03bf\u1f54 \u03bc\u03b5 \u03b2\u03bb\u1f71\u03c0\u03c4\u03b5\u03b9 \u0411\u0438 \u0448\u0438\u043b \u0438\u0434\u044d\u0439 \u0447\u0430\u0434\u043d\u0430, \u043d\u0430\u0434\u0430\u0434 \u0445\u043e\u0440\u0442\u043e\u0439 \u0431\u0438\u0448., or 2\u03c0R";
    public static final String UTF8_FILE_NAME = "utf8 file name Ka\u017a\u0144 i \u017c\u00f3\u0142to\u015b\u0107 b\u0119d\u0105! | \u1f55\u03b1\u03bb\u03bf\u03bd \u03d5\u03b1\u03b3\u03b5\u1fd6\u03bd \u03b4\u1f7b\u03bd\u03b1\u03bc\u03b1\u03b9\u0387 \u03c4\u03bf\u1fe6\u03c4\u03bf \u03bf\u1f54 \u03bc\u03b5 \u03b2\u03bb\u1f71\u03c0\u03c4\u03b5\u03b9 \u0411\u0438 \u0448\u0438\u043b \u0438\u0434\u044d\u0439 \u0447\u0430\u0434\u043d\u0430, \u043d\u0430\u0434\u0430\u0434 \u0445\u043e\u0440\u0442\u043e\u0439 \u0431\u0438\u0448., or 2\u03c0R";

    @Before
    public void setup() {
        IntegrationTestUtil.restoreAppropriateJiraData(TestConstants.DEFAULT_JIRA_DUMP_FILE, administration);
    }

    @Test
    public void testTransitionWithNumericCustomFieldPolishLocale() throws Exception {
        final double newValue = 123.45;
        final FieldInput fieldInput;
        if (IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER) {
            fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
        } else {
            fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID,
                    NumberFormat.getNumberInstance(new Locale("pl")).format(newValue));
        }
        assertTransitionWithNumericCustomField(fieldInput, newValue);
    }

    @Test
    public void testTransitionWithNumericCustomFieldEnglishLocale() throws Exception {
        setUser1();
        final double newValue = 123.45;
        final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID,
                NumberFormat.getNumberInstance(new Locale("pl")).format(newValue));

        assertErrorCode(Response.Status.BAD_REQUEST,
                IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? "Operation value must be a number"
                        : ("'" + fieldInput.getValue() + "' is an invalid number"),
                new Runnable() {
                    @Override
                    public void run() {
                        assertTransitionWithNumericCustomField(fieldInput, newValue);
                    }
                });

        final FieldInput fieldInput2 = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue); // this will be serialized always with "." according to JSL
        assertTransitionWithNumericCustomField(fieldInput2, newValue);
    }

    private void assertTransitionWithNumericCustomField(FieldInput fieldInput, Double expectedValue) {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
        final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();

        final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
        assertNotNull(transitionFound);
        assertTrue(Iterables.contains(transitionFound.getFields(),
                new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false,
                        IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5
                                : NUMERIC_CUSTOMFIELD_TYPE)));
        client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(),
                Arrays.asList(fieldInput), Comment.valueOf("My test comment"))).claim();
        final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
        assertTrue(changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue().equals(expectedValue));
    }

    @Test
    public void testDeleteIssue() {
        final IssueRestClient issueClient = client.getIssueClient();

        // verify that issue exist
        final String issueKey = "TST-1";
        final Issue issue = issueClient.getIssue(issueKey).claim();
        assertEquals(issueKey, issue.getKey());

        // delete issue
        issueClient.deleteIssue(issueKey, false).claim();

        // verify
        assertThatIssueNotExists(issueKey);
    }

    @Test
    public void testDeleteIssueWithSubtasks() {
        final IssueRestClient issueClient = client.getIssueClient();

        // verify that issue exist and create subtask
        final String issueKey = "TST-1";
        final Issue issue = issueClient.getIssue(issueKey).claim();
        assertEquals(issueKey, issue.getKey());
        final BasicIssue subtask = addSubtaskToIssue(issue);
        System.out.println(subtask);

        // delete issue
        issueClient.deleteIssue(issueKey, true).claim();

        // verify
        assertThatIssueNotExists(issueKey);
        assertThatIssueNotExists(subtask.getKey());
    }

    @Test
    public void testDeleteIssueWithSubtasksWhenDeleteSubtasksIsFalse() {
        final IssueRestClient issueClient = client.getIssueClient();

        // verify that issue exist and create subtask
        final String issueKey = "TST-1";
        final Issue issue = issueClient.getIssue(issueKey).claim();
        assertEquals(issueKey, issue.getKey());
        BasicIssue subtask = addSubtaskToIssue(issue);
        System.out.println(subtask);

        // delete issue
        expectedException.expect(rceWithSingleError(400, String.format("The issue '%s' has subtasks.  "
                + "You must specify the 'deleteSubtasks' parameter to delete this issue and all its subtasks.",
                issueKey)));
        issueClient.deleteIssue(issueKey, false).claim();
    }

    @Test
    public void testDeleteIssueWhenNoSuchIssue() {
        final IssueRestClient issueClient = client.getIssueClient();

        // verify that issue exist
        final String issueKey = "TST-999";
        assertThatIssueNotExists(issueKey);

        // delete issue should thrown 404
        expectedException.expect(rceWithSingleError(404, "Issue Does Not Exist"));
        issueClient.deleteIssue(issueKey, false).claim();
    }

    @Test
    public void testDeleteIssueWithoutDeletePermission() {
        setAnonymousMode();
        final IssueRestClient issueClient = client.getIssueClient();

        // verify that issue doesn't exist
        final String issueKey = "ANONEDIT-2";
        final Issue issue = issueClient.getIssue(issueKey).claim();
        assertEquals(issueKey, issue.getKey());

        // delete issue should thrown 401
        expectedException
                .expect(rceWithSingleError(401, "You do not have permission to delete issues in this project."));
        issueClient.deleteIssue(issueKey, false).claim();
    }

    @JiraBuildNumberDependent(BN_JIRA_5)
    @Test
    public void testUpdateField() {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
        final double newValue = 123;
        final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
        client.getIssueClient().updateIssue(issue.getKey(), IssueInput.createWithFields(fieldInput)).claim();
        final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
        assertEquals(newValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
    }

    @JiraBuildNumberDependent(BN_JIRA_5)
    @Test
    public void testUpdateMultipleFields() {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
        final double newNumericValue = 123;
        final String newTextValue = "my new text";

        final IssueInputBuilder issueInputBuilder = new IssueInputBuilder()
                .setFieldValue(NUMERIC_CUSTOMFIELD_ID, newNumericValue)
                .setFieldValue(TEXT_CUSTOMFIELD_ID, newTextValue);

        client.getIssueClient().updateIssue(issue.getKey(), issueInputBuilder.build()).claim();
        final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
        assertNotNull(changedIssue);
        assertEquals(newNumericValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
        assertEquals(newTextValue, changedIssue.getField(TEXT_CUSTOMFIELD_ID).getValue());
    }

    @JiraBuildNumberDependent(BN_JIRA_5)
    @Test
    public void testUpdateIssueWithInvalidAdditionalField() {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        final String fieldId = "invalidField";

        expectedException.expect(RestClientException.class);
        expectedException.expectMessage(String
                .format("Field '%s' cannot be set. It is not on the appropriate screen, or unknown.", fieldId));
        final FieldInput fieldInput = new FieldInput(fieldId, "who cares?");
        client.getIssueClient().updateIssue(issue.getKey(), IssueInput.createWithFields(fieldInput)).claim();
    }

    @JiraBuildNumberDependent(BN_JIRA_5)
    @Test
    public void testUpdateIssueWithoutPermissions() {
        setUser2();

        expectedException.expect(RestClientException.class);
        expectedException.expectMessage(
                String.format("Field '%s' cannot be set. It is not on the appropriate screen, or unknown.",
                        NUMERIC_CUSTOMFIELD_ID));
        final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, 1.23d);
        client.getIssueClient().updateIssue("TST-1", IssueInput.createWithFields(fieldInput)).claim();
    }

    @Test
    public void testTransitionWithNumericCustomFieldAndInteger() throws Exception {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
        final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
        final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");

        assertNotNull(transitionFound);
        assertTrue(Iterables.contains(transitionFound.getFields(),
                new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false,
                        IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5
                                : NUMERIC_CUSTOMFIELD_TYPE)));
        final double newValue = 123;
        final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
        client.getIssueClient().transition(issue.getTransitionsUri(), new TransitionInput(transitionFound.getId(),
                Arrays.asList(fieldInput), Comment.valueOf("My test comment"))).claim();
        final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
        assertEquals(newValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
    }

    @Test
    public void testTransitionWithInvalidNumericField() throws Exception {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
        final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
        final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");

        assertNotNull(transitionFound);
        assertTrue(Iterables.contains(transitionFound.getFields(), new Transition.Field(NUMERIC_CUSTOMFIELD_ID,
                false, TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
        final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, "]432jl");
        // warning: Polish language here - I am asserting if the messages are indeed localized
        // since 5.0 messages are changed and not localized
        assertErrorCode(Response.Status.BAD_REQUEST, TESTING_JIRA_5_OR_NEWER ? "Operation value must be a number"
                : "']432jl' nie jest prawid\u0142ow\u0105 liczb\u0105", new Runnable() {
                    @Override
                    public void run() {
                        client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(),
                                Arrays.asList(fieldInput), Comment.valueOf("My test comment"))).claim();
                    }
                });
    }

    @Test
    public void testTransitionWithNoRoleOrGroup() {
        Comment comment = Comment.valueOf("My text which I am just adding " + new DateTime());
        testTransitionImpl(comment);
    }

    @Test
    public void testTransitionWithRoleLevel() {
        Comment comment = Comment.createWithRoleLevel("My text which I am just adding " + new DateTime(), "Users");
        testTransitionImpl(comment);
    }

    @Test
    public void testTransitionWithGroupLevel() {
        Comment comment = Comment.createWithGroupLevel("My text which I am just adding " + new DateTime(),
                "jira-users");
        testTransitionImpl(comment);
    }

    @Test
    public void testTransitionWithInvalidRole() {
        final Comment comment = Comment.createWithRoleLevel("My text which I am just adding " + new DateTime(),
                "some-fake-role");
        if (IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER) {
            assertInvalidCommentInput(comment, "Invalid role level specified.");
        } else {
            assertInvalidCommentInput(comment, "Invalid role [some-fake-role]");
        }
    }

    @Test
    public void testTransitionWithInvalidGroup() {
        final Comment comment = Comment.createWithGroupLevel("My text which I am just adding " + new DateTime(),
                "some-fake-group");
        assertInvalidCommentInput(comment, "Group: some-fake-group does not exist.");
    }

    private void assertInvalidCommentInput(final Comment comment, String expectedErrorMsg) {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
        final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
        final String errorMsg = doesJiraServeCorrectlyErrorMessagesForBadRequestWhileTransitioningIssue()
                ? expectedErrorMsg
                : null;
        assertErrorCode(Response.Status.BAD_REQUEST, errorMsg, new Runnable() {
            @Override
            public void run() {
                client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), comment))
                        .claim();
            }
        });
    }

    private void testTransitionImpl(Comment comment) {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
        Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
        DateTime now = new DateTime();
        client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), comment)).claim();

        final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
        final Comment lastComment = Iterables.getLast(changedIssue.getComments());
        assertEquals(comment.getBody(), lastComment.getBody());
        assertEquals(IntegrationTestUtil.USER_ADMIN, lastComment.getAuthor());
        assertEquals(IntegrationTestUtil.USER_ADMIN, lastComment.getUpdateAuthor());
        assertEquals(lastComment.getCreationDate(), lastComment.getUpdateDate());
        assertTrue(lastComment.getCreationDate().isAfter(now) || lastComment.getCreationDate().isEqual(now));
        assertEquals(comment.getVisibility(), lastComment.getVisibility());
    }

    @Test
    public void testVoteUnvote() {
        final Issue issue1 = client.getIssueClient().getIssue("TST-1").claim();
        assertFalse(issue1.getVotes().hasVoted());
        assertEquals(1, issue1.getVotes().getVotes()); // the other user has voted
        final String expectedMessage = isJira5xOrNewer() // JIRA 5.0 comes without Polish translation OOB
                ? "You cannot vote for an issue you have reported."
                : "Nie mo\u017cesz g\u0142osowa\u0107 na zadanie kt\u00f3re utworzy\u0142e\u015b.";

        // I hope that such Polish special characters (for better testing local specific behaviour of REST
        assertErrorCode(Response.Status.NOT_FOUND, expectedMessage, new Runnable() {
            @Override
            public void run() {
                client.getIssueClient().vote(issue1.getVotesUri()).claim();
            }
        });

        final String issueKey = "TST-7";
        Issue issue = client.getIssueClient().getIssue(issueKey).claim();
        assertFalse(issue.getVotes().hasVoted());
        assertEquals(0, issue.getVotes().getVotes());

        client.getIssueClient().vote(issue.getVotesUri()).claim();
        issue = client.getIssueClient().getIssue(issueKey).claim();
        assertTrue(issue.getVotes().hasVoted());
        assertEquals(1, issue.getVotes().getVotes());

        client.getIssueClient().unvote(issue.getVotesUri()).claim();
        issue = client.getIssueClient().getIssue(issueKey).claim();
        assertFalse(issue.getVotes().hasVoted());
        assertEquals(0, issue.getVotes().getVotes());

        setUser2();
        issue = client.getIssueClient().getIssue(issueKey).claim();
        assertFalse(issue.getVotes().hasVoted());
        assertEquals(0, issue.getVotes().getVotes());
        final Issue finalIssue = issue;
        if (isJira6_3_7_OrNewer()) {
            client.getIssueClient().unvote(finalIssue.getVotesUri()).claim();
            issue = client.getIssueClient().getIssue(issueKey).claim();
            assertEquals(0, issue.getVotes().getVotes());
        } else {
            assertErrorCode(Response.Status.NOT_FOUND,
                    "Cannot remove a vote for an issue that the user has not already voted for.", new Runnable() {
                        @Override
                        public void run() {
                            client.getIssueClient().unvote(finalIssue.getVotesUri()).claim();
                        }
                    });
        }

        issue = client.getIssueClient().getIssue(issueKey).claim();
        assertFalse(issue.getVotes().hasVoted());
        assertEquals(0, issue.getVotes().getVotes());
        client.getIssueClient().vote(issue.getVotesUri()).claim();
        issue = client.getIssueClient().getIssue(issueKey).claim();
        assertTrue(issue.getVotes().hasVoted());
        assertEquals(1, issue.getVotes().getVotes());

        setClient(ADMIN_USERNAME, ADMIN_PASSWORD);
        client.getIssueClient().vote(issue.getVotesUri()).claim();
        issue = client.getIssueClient().getIssue(issueKey).claim();
        assertTrue(issue.getVotes().hasVoted());
        assertEquals(2, issue.getVotes().getVotes());
    }

    @Test
    public void testWatchUnwatch() {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue1 = issueClient.getIssue("TST-1").claim();

        Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                not(hasItem(IntegrationTestUtil.USER_ADMIN)));

        issueClient.watch(issue1.getWatchers().getSelf()).claim();
        Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                hasItem(IntegrationTestUtil.USER_ADMIN));

        issueClient.unwatch(issue1.getWatchers().getSelf()).claim();
        Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                not(hasItem(IntegrationTestUtil.USER_ADMIN)));

        Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                hasItem(IntegrationTestUtil.USER1));
        issueClient.removeWatcher(issue1.getWatchers().getSelf(), IntegrationTestUtil.USER1.getName()).claim();
        Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                not(hasItem(IntegrationTestUtil.USER1)));
        issueClient.addWatcher(issue1.getWatchers().getSelf(), IntegrationTestUtil.USER1.getName()).claim();
        Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                hasItem(IntegrationTestUtil.USER1));
    }

    @Test
    public void testRemoveWatcherUnauthorized() {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue1 = issueClient.getIssue("TST-1").claim();
        issueClient.watch(issue1.getWatchers().getSelf()).claim();

        setUser1();
        final IssueRestClient issueClient2 = client.getIssueClient();
        assertErrorCode(Response.Status.UNAUTHORIZED,
                "User 'wseliga' is not allowed to remove watchers from issue 'TST-1'", new Runnable() {
                    @Override
                    public void run() {
                        issueClient2.removeWatcher(issue1.getWatchers().getSelf(), ADMIN_USERNAME).claim();
                    }
                });
    }

    @Test
    public void testWatchAlreadyWatched() {
        setUser1();
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-1").claim();
        Assert.assertThat(client.getIssueClient().getWatchers(issue.getWatchers().getSelf()).claim().getUsers(),
                hasItem(IntegrationTestUtil.USER1));
        // JIRA allows to watch already watched issue by you - such action effectively has no effect
        issueClient.watch(issue.getWatchers().getSelf()).claim();
        Assert.assertThat(client.getIssueClient().getWatchers(issue.getWatchers().getSelf()).claim().getUsers(),
                hasItem(IntegrationTestUtil.USER1));
    }

    @Test
    public void testAddWatcherUnauthorized() {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue1 = issueClient.getIssue("TST-1").claim();
        issueClient.addWatcher(issue1.getWatchers().getSelf(), USER1_USERNAME).claim();
        assertThat(client.getIssueClient().getWatchers(issue1.getWatchers().getSelf()).claim().getUsers(),
                hasItem(IntegrationTestUtil.USER1));

        setUser1();
        assertTrue(client.getIssueClient().getIssue("TST-1").claim().getWatchers().isWatching());
        String expectedErrorMsg = isJraDev3516Fixed()
                ? ("User '" + USER1_USERNAME + "' is not allowed to add watchers to issue 'TST-1'")
                : null;
        assertErrorCode(Response.Status.UNAUTHORIZED, expectedErrorMsg, new Runnable() {
            @Override
            public void run() {
                client.getIssueClient().addWatcher(issue1.getWatchers().getSelf(), ADMIN_USERNAME).claim();
            }
        });
    }

    private boolean isJraDev3516Fixed() {
        return client.getMetadataClient().getServerInfo().claim().getBuildNumber() >= BN_JIRA_4_3;
    }

    @Test
    public void testAddWatcherWhoDoesNotHaveViewIssuePermissions() {
        final IssueRestClient issueClient = client.getIssueClient();
        final String issueKey = "RST-1";
        final Issue issue1 = issueClient.getIssue(issueKey).claim();
        final String expectedErrorMessage;

        if (isJira5xOrNewer()) {
            expectedErrorMessage = "The user \"" + USER2_USERNAME
                    + "\" does not have permission to view this issue."
                    + " This user will not be added to the watch list.";
        } else if (isJira43xOrNewer()) {
            expectedErrorMessage = "User '" + ADMIN_USERNAME + "' is not allowed to add watchers to issue '"
                    + issueKey + "'";
        } else {
            expectedErrorMessage = "com.sun.jersey.api.client.UniformInterfaceException: Client response status: 401";
        }

        assertErrorCode(Response.Status.UNAUTHORIZED, expectedErrorMessage, new Runnable() {
            @Override
            public void run() {
                issueClient.addWatcher(issue1.getWatchers().getSelf(), USER2_USERNAME).claim();
            }
        });
    }

    @JiraBuildNumberDependent(BN_JIRA_4_3)
    @Test
    public void testLinkIssuesWithRoleLevel() {
        testLinkIssuesImpl(Comment.createWithRoleLevel("A comment about linking", "Administrators"));
    }

    @JiraBuildNumberDependent(BN_JIRA_4_3)
    @Test
    public void testLinkIssuesWithGroupLevel() {
        testLinkIssuesImpl(Comment.createWithGroupLevel("A comment about linking", "jira-administrators"));
    }

    @JiraBuildNumberDependent(BN_JIRA_4_3)
    @Test
    public void testLinkIssuesWithSimpleComment() {
        testLinkIssuesImpl(Comment.valueOf("A comment about linking"));
    }

    @JiraBuildNumberDependent(BN_JIRA_4_3)
    @Test
    public void testLinkIssuesWithoutComment() {
        testLinkIssuesImpl(null);
    }

    @JiraBuildNumberDependent(BN_JIRA_4_3)
    @Test
    public void testLinkIssuesWithInvalidParams() {
        assertErrorCode(Response.Status.NOT_FOUND,
                IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? "Issue Does Not Exist"
                        : "The issue no longer exists.",
                new Runnable() {
                    @Override
                    public void run() {
                        client.getIssueClient()
                                .linkIssue(new LinkIssuesInput("TST-7", "FAKEKEY-1", "Duplicate", null)).claim();
                    }
                });

        assertErrorCode(Response.Status.NOT_FOUND, "No issue link type with name 'NonExistingLinkType' found.",
                new Runnable() {
                    @Override
                    public void run() {
                        client.getIssueClient()
                                .linkIssue(new LinkIssuesInput("TST-7", "TST-6", "NonExistingLinkType", null))
                                .claim();
                    }
                });

        setUser1();
        final String optionalDot = isJira5xOrNewer() ? "." : "";
        assertErrorCode(Response.Status.NOT_FOUND,
                "You do not have the permission to see the specified issue" + optionalDot, new Runnable() {
                    @Override
                    public void run() {
                        client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "RST-1", "Duplicate", null))
                                .claim();
                    }
                });
        final ErrorCollection.Builder ecb = ErrorCollection.builder();
        ecb.status(Response.Status.BAD_REQUEST.getStatusCode())
                .errorMessage("Failed to create comment for issue 'TST-6'")
                .error("commentLevel", "You are currently not a member of the project role: Administrators.");
        final ImmutableList<ErrorCollection> errorCollections = ImmutableList.of(ecb.build());

        assertExpectedErrorCollection(errorCollections, new Runnable() {
            @Override
            public void run() {
                client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
                        Comment.createWithRoleLevel("my body", "Administrators"))).claim();
            }
        });
        assertErrorCode(Response.Status.BAD_REQUEST,
                "You are currently not a member of the group: jira-administrators.", new Runnable() {
                    @Override
                    public void run() {
                        client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
                                Comment.createWithGroupLevel("my body", "jira-administrators"))).claim();
                    }
                });
        assertErrorCode(Response.Status.BAD_REQUEST, "Group: somefakegroup does not exist.", new Runnable() {
            @Override
            public void run() {
                client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
                        Comment.createWithGroupLevel("my body", "somefakegroup"))).claim();
            }
        });
    }

    @JiraBuildNumberDependent(condition = LongCondition.LESS_THAN, value = 6211)
    @Test
    public void testLinkIssuesWithInvalidParamsBeforeUpgradeTask6211() {
        setUser2();
        assertErrorCode(Response.Status.UNAUTHORIZED, "No Link Issue Permission for issue 'TST-7'", new Runnable() {
            @Override
            public void run() {
                client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate", null)).claim();
            }
        });
    }

    @JiraBuildNumberDependent(6211)
    @Test
    public void testLinkIssuesForUserRoleLevelAfterUpgradeTask6211() {
        testLinkIssuesImpl(Comment.createWithRoleLevel("A comment about linking", "Users"));
    }

    private void testLinkIssuesImpl(@Nullable Comment commentInput) {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue originalIssue = issueClient.getIssue("TST-7").claim();
        int origNumComments = Iterables.size(originalIssue.getComments());
        assertFalse(originalIssue.getIssueLinks().iterator().hasNext());

        issueClient.linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate", commentInput)).claim();

        final Issue linkedIssue = issueClient.getIssue("TST-7").claim();
        assertEquals(1, Iterables.size(linkedIssue.getIssueLinks()));
        final IssueLink addedLink = linkedIssue.getIssueLinks().iterator().next();
        assertEquals("Duplicate", addedLink.getIssueLinkType().getName());
        assertEquals("TST-6", addedLink.getTargetIssueKey());
        assertEquals(IssueLinkType.Direction.OUTBOUND, addedLink.getIssueLinkType().getDirection());

        final int expectedNumComments = commentInput != null ? origNumComments + 1 : origNumComments;
        assertEquals(expectedNumComments, Iterables.size(linkedIssue.getComments()));
        if (commentInput != null) {
            final Comment comment = linkedIssue.getComments().iterator().next();
            assertEquals(commentInput.getBody(), comment.getBody());
            assertEquals(IntegrationTestUtil.USER_ADMIN, comment.getAuthor());
            assertEquals(commentInput.getVisibility(), comment.getVisibility());
        } else {
            assertFalse(linkedIssue.getComments().iterator().hasNext());
        }

        final Issue targetIssue = issueClient.getIssue("TST-6").claim();
        final IssueLink targetLink = targetIssue.getIssueLinks().iterator().next();
        assertEquals(IssueLinkType.Direction.INBOUND, targetLink.getIssueLinkType().getDirection());
        assertEquals("Duplicate", targetLink.getIssueLinkType().getName());
    }

    private boolean doesJiraSupportAddingAttachment() {
        return client.getMetadataClient().getServerInfo().claim().getBuildNumber() >= BN_JIRA_4_3;
    }

    private boolean doesJiraServeCorrectlyErrorMessagesForBadRequestWhileTransitioningIssue() {
        return client.getMetadataClient().getServerInfo().claim().getBuildNumber() >= BN_JIRA_4_3;
    }

    @Test
    // TODO: implement
    public void testAddAttachment() throws IOException {

        if (!doesJiraSupportAddingAttachment()) {
            return;
        }
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-3").claim();
        assertFalse(issue.getAttachments().iterator().hasNext());

        String str = "Wojtek";
        final String filename1 = "my-test-file";
        issueClient.addAttachment(issue.getAttachmentsUri(), new ByteArrayInputStream(str.getBytes("UTF-8")),
                filename1).claim();
        final String filename2 = "my-picture.png";
        issueClient.addAttachment(issue.getAttachmentsUri(),
                AsynchronousIssueRestClientTest.class.getResourceAsStream("/attachment-test/transparent-png.png"),
                filename2).claim();

        final Issue issueWithAttachments = issueClient.getIssue("TST-3").claim();
        final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
        assertEquals(2, Iterables.size(attachments));
        final Iterable<String> attachmentsNames = Iterables.transform(attachments,
                new Function<Attachment, String>() {
                    @Override
                    public String apply(@Nullable Attachment from) {
                        return from.getFilename();
                    }
                });
        assertThat(attachmentsNames, containsInAnyOrder(filename1, filename2));
        final Attachment pictureAttachment = Iterables.find(attachments, new Predicate<Attachment>() {
            @Override
            public boolean apply(@Nullable Attachment input) {
                return filename2.equals(input.getFilename());
            }
        });

        // let's download it now and compare it's binary content

        assertTrue(IOUtils.contentEquals(
                AsynchronousIssueRestClientTest.class.getResourceAsStream("/attachment-test/transparent-png.png"),
                issueClient.getAttachment(pictureAttachment.getContentUri()).claim()));
    }

    @Test
    public void testAddAttachmentWithUtf8InNameAndBody() throws IOException {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-3").claim();
        assertFalse(issue.getAttachments().iterator().hasNext());

        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
                UTF8_FILE_BODY.getBytes("UTF-8"));
        issueClient.addAttachment(issue.getAttachmentsUri(), byteArrayInputStream, UTF8_FILE_NAME).claim();

        final Issue issueWithAttachments = issueClient.getIssue("TST-3").claim();
        final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
        assertEquals(1, Iterables.size(attachments));
        final Attachment attachment = attachments.iterator().next();
        assertThat(attachment.getFilename(), equalTo(UTF8_FILE_NAME));

        assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(UTF8_FILE_BODY.getBytes("UTF-8")),
                issueClient.getAttachment(attachment.getContentUri()).claim()));
    }

    @Test
    // TODO: implement
    public void testAddAttachments() throws IOException {
        if (!doesJiraSupportAddingAttachment()) {
            return;
        }
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-4").claim();
        assertFalse(issue.getAttachments().iterator().hasNext());

        final AttachmentInput[] attachmentInputs = new AttachmentInput[3];
        for (int i = 1; i <= 3; i++) {
            attachmentInputs[i - 1] = new AttachmentInput("my-test-file-" + i + ".txt",
                    new ByteArrayInputStream(("content-of-the-file-" + i).getBytes("UTF-8")));
        }
        issueClient.addAttachments(issue.getAttachmentsUri(), attachmentInputs).claim();

        final Issue issueWithAttachments = issueClient.getIssue("TST-4").claim();
        final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
        assertEquals(3, Iterables.size(attachments));
        Pattern pattern = Pattern.compile("my-test-file-(\\d)\\.txt");
        for (Attachment attachment : attachments) {
            assertTrue(pattern.matcher(attachment.getFilename()).matches());
            final Matcher matcher = pattern.matcher(attachment.getFilename());
            matcher.find();
            final String interfix = matcher.group(1);
            assertTrue(IOUtils.contentEquals(
                    new ByteArrayInputStream(("content-of-the-file-" + interfix).getBytes("UTF-8")),
                    issueClient.getAttachment(attachment.getContentUri()).claim()));

        }
    }

    @Test
    public void testAddAttachmentsWithUtf8InNameAndBody() throws IOException {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-4").claim();
        assertFalse(issue.getAttachments().iterator().hasNext());

        final AttachmentInput[] attachmentInputs = new AttachmentInput[3];
        final String[] names = new String[3];
        final String[] contents = new String[3];
        for (int i = 0; i < 3; i++) {
            names[i] = UTF8_FILE_NAME + "-" + i + ".txt";
            contents[i] = "content-of-the-file-" + i + " with some utf8: " + UTF8_FILE_BODY;
            attachmentInputs[i] = new AttachmentInput(names[i],
                    new ByteArrayInputStream(contents[i].getBytes("UTF-8")));
        }
        issueClient.addAttachments(issue.getAttachmentsUri(), attachmentInputs).claim();

        final Issue issueWithAttachments = issueClient.getIssue("TST-4").claim();
        final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
        assertEquals(3, Iterables.size(attachments));
        Pattern pattern = Pattern.compile(".*-(\\d)\\.txt");
        for (Attachment attachment : attachments) {
            assertTrue(pattern.matcher(attachment.getFilename()).matches());
            final Matcher matcher = pattern.matcher(attachment.getFilename());
            matcher.find();
            final int attachmentNum = Integer.parseInt(matcher.group(1));
            assertThat(attachment.getFilename(), equalTo(names[attachmentNum]));
            assertTrue(IOUtils.contentEquals(new ByteArrayInputStream((contents[attachmentNum]).getBytes("UTF-8")),
                    issueClient.getAttachment(attachment.getContentUri()).claim()));

        }
    }

    @Test
    // TODO: implement
    public void testAddFileAttachments() throws IOException {
        if (!doesJiraSupportAddingAttachment()) {
            return;
        }
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-5").claim();
        assertFalse(issue.getAttachments().iterator().hasNext());

        final File tempFile = File.createTempFile("jim-integration-test", ".txt");
        tempFile.deleteOnExit();
        FileWriter writer = new FileWriter(tempFile);
        writer.write("This is the content of my file which I am going to upload to JIRA for testing.");
        writer.close();
        issueClient.addAttachments(issue.getAttachmentsUri(), tempFile).claim();

        final Issue issueWithAttachments = issueClient.getIssue("TST-5").claim();
        final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
        assertEquals(1, Iterables.size(attachments));
        assertTrue(IOUtils.contentEquals(new FileInputStream(tempFile),
                issueClient.getAttachment(attachments.iterator().next().getContentUri()).claim()));
    }

    @Test
    public void testAddFileAttachmentWithUtf8InNameAndBody() throws IOException {
        final IssueRestClient issueClient = client.getIssueClient();
        final Issue issue = issueClient.getIssue("TST-5").claim();
        assertFalse(issue.getAttachments().iterator().hasNext());

        final File tempFile = File.createTempFile(UTF8_FILE_NAME, ".txt");
        tempFile.deleteOnExit();
        FileWriter writer = new FileWriter(tempFile);
        writer.write(UTF8_FILE_BODY);
        writer.close();
        issueClient.addAttachments(issue.getAttachmentsUri(), tempFile).claim();

        final Issue issueWithAttachments = issueClient.getIssue("TST-5").claim();
        final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
        assertEquals(1, Iterables.size(attachments));
        final Attachment firstAttachment = attachments.iterator().next();
        assertTrue(IOUtils.contentEquals(new FileInputStream(tempFile),
                issueClient.getAttachment(firstAttachment.getContentUri()).claim()));
        assertThat(firstAttachment.getFilename(), equalTo(tempFile.getName()));
    }

    private void setUserLanguageToEnUk() {
        changeUserLanguageByValueOrName("en_UK", "angielski (UK)");
    }

    private void changeUserLanguageByValueOrName(String value, String name) {
        final UserProfile userProfile = navigation.userProfile();
        boolean fallbackToChangeByValue = false;
        try {
            Method changeUserLanguageByValue = userProfile.getClass().getMethod("changeUserLanguageByValue",
                    String.class);
            changeUserLanguageByValue.invoke(userProfile, value);
        } catch (NoSuchMethodException e) {
            // fallbackToChangeByValue to value - for JIRA < 5.1
            fallbackToChangeByValue = true;
        } catch (InvocationTargetException e) {
            fallbackToChangeByValue = true;
        } catch (IllegalAccessException e) {
            fallbackToChangeByValue = true;
        }

        if (fallbackToChangeByValue) {
            userProfile.changeUserLanguage(name);
        }
    }

    @Test
    public void testFetchingUnassignedIssue() {
        administration.generalConfiguration().setAllowUnassignedIssues(true);
        assertEquals(IntegrationTestUtil.USER_ADMIN,
                client.getIssueClient().getIssue("TST-5").claim().getAssignee());

        setUserLanguageToEnUk();
        navigation.issue().unassignIssue("TST-5", "unassigning issue");
        // this single line does instead of 2 above - func test suck with non-English locale
        // but it does not work yet with JIRA 5.0-resthack...
        //navigation.issue().assignIssue("TST-5", "unassigning issue", "Nieprzydzielone");

        assertNull(client.getIssueClient().getIssue("TST-5").claim().getAssignee());
    }

    @Test
    public void testFetchingIssueWithAnonymousComment() {
        setUserLanguageToEnUk();
        final String commentText = "my nice comment";
        final String issueKey = "ANONEDIT-1";

        navigation.logout();
        navigation.issue().addComment(issueKey, commentText);

        final Issue issue = client.getIssueClient().getIssue(issueKey).claim();
        assertEquals(1, Iterables.size(issue.getComments()));
        final Comment comment = issue.getComments().iterator().next();
        assertEquals(commentText, comment.getBody());
        if (isJira5xOrNewer()) {
            assertNotNull(comment.getId());
        } else {
            assertNull(comment.getId());
        }
        assertNull(comment.getAuthor());
        assertNull(comment.getUpdateAuthor());
    }

    @Test
    public void testGetIssueWithNoViewWatchersPermission() {
        setUser1();
        assertTrue(client.getIssueClient().getIssue("TST-1").claim().getWatchers().isWatching());

        setUser2();
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        assertFalse(issue.getWatchers().isWatching());
        client.getIssueClient().watch(issue.getWatchers().getSelf()).claim();
        final Issue watchedIssue = client.getIssueClient().getIssue("TST-1").claim();
        assertTrue(watchedIssue.getWatchers().isWatching());
        assertEquals(2, watchedIssue.getWatchers().getNumWatchers());

        // although there are 2 watchers, only one is listed with details - the caller itself, as the caller does not
        // have view watchers and voters permission
        assertThat(client.getIssueClient().getWatchers(watchedIssue.getWatchers().getSelf()).claim().getUsers(),
                containsInAnyOrder(USER2));
    }

    @Test
    public void testTransition() throws Exception {
        final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
        final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
        assertEquals(4, Iterables.size(transitions));
        final Transition startProgressTransition = new Transition("Start Progress",
                IntegrationTestUtil.START_PROGRESS_TRANSITION_ID, Collections.<Transition.Field>emptyList());
        assertTrue(Iterables.contains(transitions, startProgressTransition));

        client.getIssueClient()
                .transition(issue, new TransitionInput(IntegrationTestUtil.START_PROGRESS_TRANSITION_ID,
                        Collections.<FieldInput>emptyList(), Comment.valueOf("My test comment")))
                .claim();
        final Issue transitionedIssue = client.getIssueClient().getIssue("TST-1").claim();
        assertEquals("In Progress", transitionedIssue.getStatus().getName());
        final Iterable<Transition> transitionsAfterTransition = client.getIssueClient().getTransitions(issue)
                .claim();
        assertFalse(Iterables.contains(transitionsAfterTransition, startProgressTransition));
        final Transition stopProgressTransition = new Transition("Stop Progress",
                IntegrationTestUtil.STOP_PROGRESS_TRANSITION_ID, Collections.<Transition.Field>emptyList());
        assertTrue(Iterables.contains(transitionsAfterTransition, stopProgressTransition));
    }

    private void assertThatIssueNotExists(String issueKey) {
        try {
            final Issue issue = client.getIssueClient().getIssue(issueKey).claim();
            fail("It looks that issue exists, and it should not be here! issue = " + issue);
        } catch (RestClientException ex) {
            assertThat(ex, rceWithSingleError(404, "Issue Does Not Exist"));
        }
    }

    private BasicIssue addSubtaskToIssue(final Issue issue) {
        // collect CreateIssueMetadata for project with key TST
        final IssueRestClient issueClient = client.getIssueClient();
        final Iterable<CimProject> metadataProjects = issueClient
                .getCreateIssueMetadata(new GetCreateIssueMetadataOptionsBuilder()
                        .withProjectKeys(issue.getProject().getKey()).withExpandedIssueTypesFields().build())
                .claim();

        // select project and issue
        assertEquals(1, Iterables.size(metadataProjects));
        final CimProject project = metadataProjects.iterator().next();
        final CimIssueType issueType = findEntityByName(project.getIssueTypes(), "Sub-task");

        // build issue input
        final String summary = "Some subtask";
        final String description = "Some description for substask";

        // prepare IssueInput
        final IssueInputBuilder issueInputBuilder = new IssueInputBuilder(project, issueType, summary)
                .setDescription(description)
                .setFieldValue("parent", ComplexIssueInputFieldValue.with("key", issue.getKey()));

        // create
        final BasicIssue basicCreatedIssue = issueClient.createIssue(issueInputBuilder.build()).claim();
        assertNotNull(basicCreatedIssue.getKey());

        return basicCreatedIssue;
    }
}