/*
 * Copyright (C) 2017 Google Inc.
 *
 * 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.google.copybara.git.gerritapi;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.copybara.git.GitRepository.newBareRepo;
import static com.google.copybara.git.gerritapi.ChangeStatus.ABANDONED;
import static com.google.copybara.git.gerritapi.ChangeStatus.NEW;
import static com.google.copybara.git.gerritapi.IncludeResult.CURRENT_COMMIT;
import static com.google.copybara.git.gerritapi.IncludeResult.CURRENT_REVISION;
import static com.google.copybara.git.gerritapi.IncludeResult.DETAILED_LABELS;
import static com.google.copybara.git.gerritapi.IncludeResult.SUBMITTABLE;
import static com.google.copybara.testing.git.GitTestUtil.getGitEnv;
import static com.google.copybara.util.CommandRunner.DEFAULT_TIMEOUT;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;

import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.copybara.exception.ValidationException;
import com.google.copybara.git.GerritOptions;
import com.google.copybara.git.GitRepository;
import com.google.copybara.git.gerritapi.GerritApiException.ResponseCode;
import com.google.copybara.testing.OptionsBuilder;
import com.google.copybara.testing.git.GitTestUtil;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class GerritApiTest {

  private static final String CHANGE_ID = "Ie39b6e2c0c6e5ef8839013360bba38238c6ecfcd";
  private static final String REVISION_ID = "674ac754f91e64a0efb8087e59a176484bd534d1";

  protected Map<Predicate<String>, byte[]> requestToResponse = Maps.newHashMap();

  protected GerritApi gerritApi;
  private MockHttpTransport httpTransport;
  private Path credentialsFile;

  @Before
  public void setUp() throws Exception {
    OptionsBuilder options =
        new OptionsBuilder()
            .setWorkdirToRealTempDir()
            .setEnvironment(GitTestUtil.getGitEnv().getEnvironment())
            .setOutputRootToTmpDir();

    credentialsFile = Files.createTempFile("credentials", "test");
    Files.write(credentialsFile, "https://user:[email protected]".getBytes(UTF_8));
    GitRepository repo = newBareRepo(Files.createTempDirectory("test_repo"),
        getGitEnv(), /*verbose=*/true, DEFAULT_TIMEOUT, /*noVerify=*/false)
        .init()
        .withCredentialHelper("store --file=" + credentialsFile);

    httpTransport =
        new MockHttpTransport() {
          @Override
          public LowLevelHttpRequest buildRequest(String method, String url) {
            String requestString = method + " " + url;
            MockLowLevelHttpRequest request = new MockLowLevelHttpRequest();
            MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
            request.setResponse(response);
            for (Entry<Predicate<String>, byte[]> entry : requestToResponse.entrySet()) {
              if (entry.getKey().test(requestString)) {
                byte[] content = entry.getValue();
                assertWithMessage("'" + method + " " + url + "'").that(content).isNotNull();
                if (content.length == 0) {
                  // No content
                  response.setStatusCode(204);
                  return request;
                }
                response.setContent(content);
                return request;
              }
            }
            response.setStatusCode(404);
            response.setContent(("NO BASE_URL MATCHED! (Returning 404) REQUEST: " + requestString));
            return request;
          }
        };

    GerritOptions gerritOptions =
        new GerritOptions(options.general, options.git) {
          @Override
          protected HttpTransport getHttpTransport() {
            return httpTransport;
          }

          @Override
          protected GitRepository getCredentialsRepo() {
            return repo;
          }
        };
    gerritApi = gerritOptions.newGerritApi(getHost() + "/foo/bar/baz");
  }

  protected String getHost() {
    return "https://copybara-not-real.com";
  }

  @Test
  public void testChanges() throws Exception {
    mockResponse(new CheckRequest("GET", "/changes/\\?q=status(:|%3A)open"), ""
        + ")]}'\n" + "[\n" + mockChangeInfo(NEW) + "]");

    List<ChangeInfo> changes = gerritApi.getChanges(new ChangesQuery("status:open"));
    assertThat(changes).hasSize(1);
    assertThat(changes.get(0).getId()).contains(CHANGE_ID);
    assertThat(changes.get(0).getStatus()).isEqualTo(NEW);
    assertThat(changes.get(0).getNumber()).isEqualTo(1082);
  }

  @Test
  public void testChangesNoChanges() throws Exception {
    mockResponse(new CheckRequest("GET", "/changes/\\?q=status(:|%3A)open"), ""
        + ")]}'\n"
        + "[]");

    List<ChangeInfo> changes = gerritApi.getChanges(new ChangesQuery("status:open"));
    assertThat(changes).isEmpty();
  }

  @Test
  public void testChanges404NotFound() throws Exception {
    mockResponse(s -> false, "");
    GerritApiException e =
        assertThrows(
            GerritApiException.class, () -> gerritApi.getChanges(new ChangesQuery("status:open")));
    assertThat(e.getExitCode()).isEqualTo(404);
  }

  @Test
  public void testGetChange() throws Exception {
    mockResponse(new CheckRequest("GET", "/changes/" + CHANGE_ID + "\\?o="), ""
        + ")]}'\n" + mockChangeInfo(NEW));
    ChangeInfo change =
        gerritApi.getChange(
            CHANGE_ID,
            new GetChangeInput(
                ImmutableSet.of(CURRENT_REVISION, CURRENT_COMMIT, DETAILED_LABELS, SUBMITTABLE)));

    validateChangeInfoCommon(change);
  }

  @Test
  public void testGetChangeDetail() throws Exception {
    mockResponse(new CheckRequest("GET", "/changes/" + CHANGE_ID + "/detail\\?o="), ""
        + ")]}'\n" + mockChangeInfo(NEW, /*detail=*/ true));
    ChangeInfo change =
        gerritApi.getChangeDetail(
            CHANGE_ID,
            new GetChangeInput(
                ImmutableSet.of(CURRENT_REVISION, CURRENT_COMMIT, DETAILED_LABELS, SUBMITTABLE)));

    validateChangeInfoCommon(change);

    assertThat(change.getReviewers().keySet()).containsExactly("REVIEWER", "CC");
    assertThat(change.getReviewers().get("REVIEWER").stream()
        .map(AccountInfo::getAccountId).collect(Collectors.toList()))
        .containsExactly(1000096L);
    assertThat(change.getReviewers().get("CC").stream()
        .map(AccountInfo::getAccountId).collect(Collectors.toList()))
        .containsExactly(1000097L);
  }

  @Test
  public void testDeleteReviewer() throws Exception {
    mockResponse(new CheckRequest("POST", "/changes/" + CHANGE_ID + "/reviewers/12345/delete"), "");
    gerritApi.deleteReviewer(CHANGE_ID, 12345, new DeleteReviewerInput(NotifyType.ALL));
  }

  @Test
  public void testDeleteReviewerNotFound() throws Exception {
    try {
      gerritApi.deleteReviewer(CHANGE_ID, 12345, new DeleteReviewerInput(NotifyType.ALL));
    } catch (GerritApiException e) {
      assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NOT_FOUND);
    }
  }

  @Test
  public void testAddReviewer() throws Exception {
    mockResponse(new CheckRequest("POST", "/changes/" + CHANGE_ID + "/reviewers"), ""
        + ")]}'\n" + mockAddReviewerResult());
    gerritApi.addReviewer(CHANGE_ID, new ReviewerInput("[email protected]"));
  }

  @Test
  public void testAddReviewerFail() throws Exception {
    GerritApiException e =
        assertThrows(
            GerritApiException.class,
            () -> gerritApi.addReviewer(CHANGE_ID, new ReviewerInput("[email protected]")));
    assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NOT_FOUND);
  }

  private void validateChangeInfoCommon(ChangeInfo change) {
    assertThat(change.getChangeId()).isEqualTo(CHANGE_ID);
    assertThat(change.getNumber()).isEqualTo(1082);
    assertThat(change.getUpdated().format(DateTimeFormatter.ISO_DATE_TIME))
        .isEqualTo("2017-12-01T17:33:30Z");
    assertThat(change.isSubmittable()).isTrue();
    RevisionInfo revisionInfo = change.getAllRevisions().get(change.getCurrentRevision());
    assertThat(revisionInfo.getCommit().getMessage()).contains("JUST A TEST");
    assertThat(revisionInfo.getCommit().getMessage()).contains("Second line of description");
    assertThat(revisionInfo.getFetch()).containsKey("https");
    FetchInfo fetchInfo = revisionInfo.getFetch().get("https");
    assertThat(fetchInfo.getUrl()).isEqualTo("https://foo.bar/copybara/test");
    assertThat(fetchInfo.getRef()).isEqualTo("refs/changes/11/11111/1");

    List<ChangeMessageInfo> messages = change.getMessages();
    assertThat(messages).hasSize(1);
    ChangeMessageInfo message = Iterables.getOnlyElement(messages);
    assertThat(message.getMessage()).isEqualTo("Uploaded patch set 1.");

    ImmutableMap<String, LabelInfo> labels = change.getLabels();
    assertThat(labels).hasSize(1);
    LabelInfo labelInfo = Iterables.getOnlyElement(labels.values());
    assertThat(labelInfo.getAll().asList().get(0).getDate().format(DateTimeFormatter.ISO_DATE_TIME))
        .isEqualTo("2017-01-01T12:00:00Z");
  }

  @Test
  public void testGetChange404NotFound() throws Exception {
    mockResponse(s -> false, "");
    GerritApiException e =
        assertThrows(
            GerritApiException.class, () -> gerritApi.getChange(CHANGE_ID, new GetChangeInput()));
    assertThat(e.getExitCode()).isEqualTo(404);
  }

  @Test
  public void testAbandonRestore() throws Exception {
    mockResponse(new CheckRequest("POST", ".*/abandon.*"), ""
        + ")]}'\n" + mockChangeInfo(ChangeStatus.ABANDONED));
    mockResponse(new CheckRequest("POST", ".*/restore.*"), ""
        + ")]}'\n" + mockChangeInfo(NEW));

    ChangeInfo change = gerritApi.abandonChange(CHANGE_ID, AbandonInput.createWithoutComment());
    assertThat(change.getId()).contains(CHANGE_ID);
    assertThat(change.getStatus()).isEqualTo(ABANDONED);

    change = gerritApi.restoreChange(CHANGE_ID, RestoreInput.createWithoutComment());
    assertThat(change.getId()).contains(CHANGE_ID);
    assertThat(change.getStatus()).isEqualTo(NEW);
  }

  @Test
  public void testListProjects() throws Exception {
    mockResponse(new CheckRequest("GET", "/projects/"), ""
        + ")]}'\n"
        + "{\n"
        + "    \"external/bison\": {\n"
        + "      \"id\": \"external%2Fbison\",\n"
        + "      \"description\": \"GNU parser generator\"\n"
        + "    },\n"
        + "    \"external/gcc\": {\n"
        + "      \"id\": \"external%2Fgcc\"\n"
        + "    },\n"
        + "    \"external/openssl\": {\n"
        + "      \"id\": \"external%2Fopenssl\",\n"
        + "      \"description\": \"encryption\\\\ncrypto routines\"\n"
        + "    }\n"
        + "  }");

    Map<String, ProjectInfo> projects = gerritApi.listProjects(
        new ListProjectsInput().withLimit(3).withRegex("external.*"));

    assertThat(projects).hasSize(3);
    assertThat(projects.get("external/bison").getId()).isEqualTo("external%2Fbison");
    assertThat(projects.get("external/bison").getDescription()).isEqualTo("GNU parser generator");
    assertThat(projects.get("external/gcc").getId()).isEqualTo("external%2Fgcc");
    assertThat(projects.get("external/openssl").getId()).isEqualTo("external%2Fopenssl");
  }

  @Test
  public void testCreateProject() throws Exception {
    mockResponse(new CheckRequest("PUT", "/projects/external%2Ftest"), ""
        + ")]}'\n"
        + "{\n"
        + "      \"id\": \"external%2Ftest\",\n"
        + "      \"name\": \"external/test\",\n"
        + "      \"description\": \"Some test project\"\n"
        + "  }");

    ProjectInfo projects = gerritApi.createProject("external/test");

    assertThat(projects.getId()).isEqualTo("external%2Ftest");
    assertThat(projects.getName()).isEqualTo("external/test");
    assertThat(projects.getDescription()).isEqualTo("Some test project");
  }

  @Test
  public void testCreateProject_invalid() throws Exception {
    ValidationException e =
        assertThrows(ValidationException.class, () -> gerritApi.createProject("some project"));
    assertThat(e).hasMessageThat().contains("has spaces");
  }

  @Test
  public void testSetReview() throws Exception {
    mockResponse(new CheckRequest("POST", ".*/changes/.*/revisions/.*"), ""
        + ")]}'\n" + mockReviewResult());

    ReviewResult reviewResult =
        gerritApi.setReview(CHANGE_ID, REVISION_ID,
            SetReviewInput.create(null, ImmutableMap.of(), null));
    assertThat(reviewResult.getLabels()).isEqualTo(ImmutableMap.of("Code-Review", -1));
  }

  @Test
  public void testSetReviewWithMessage() throws Exception {
    mockResponse(new CheckRequest("POST", ".*/changes/.*/revisions/.*"), ""
        + ")]}'\n" + mockReviewResult());

    ReviewResult reviewResult =
        gerritApi.setReview(CHANGE_ID, REVISION_ID,
            SetReviewInput.create("foo", ImmutableMap.of(), null ));
    assertThat(reviewResult.getLabels()).isEqualTo(ImmutableMap.of("Code-Review", -1));
  }

  @Test
  public void testGetActions() throws Exception {
    mockResponse(new CheckRequest("GET", ".*/changes/.*/revisions/.*/actions"), ""
        + ")]}'\n"
    + "{\n"
    + "    \"submit\": {\n"
    + "         \"method\": \"POST\", \n"
    + "         \"label\": \"Submit\", \n"
    + "         \"title\": \"Submit patch set 1 into master\", \n"
    + "         \"enabled\": true\n"
    + "      },\n"
    + "    \"cherrypick\": {\n"
    + "          \"method\": \"POST\", \n"
    + "          \"label\": \"Cherry Pick\", \n"
    + "          \"title\": \"Cherry pick change to a different branch\",\n"
    + "          \"enabled\": false\n"
    + "     }\n"
    + " }");

    Map<String, ActionInfo> actions =
        gerritApi.getActions(CHANGE_ID, REVISION_ID);
    assertThat(actions.size()).isEqualTo(2);
    assertThat(actions.get("cherrypick").getEnabled()).isFalse();
    assertThat(actions.get("cherrypick").getLabel()).isEqualTo("Cherry Pick");
    assertThat(actions.get("cherrypick").getMethod()).isEqualTo("POST");
    assertThat(actions.get("cherrypick").getTitle()).isEqualTo(
        "Cherry pick change to a different branch");
    assertThat(actions.get("submit").getEnabled()).isTrue();
    assertThat(actions.get("submit").getLabel()).isEqualTo("Submit");
    assertThat(actions.get("submit").getMethod()).isEqualTo("POST");
    assertThat(actions.get("submit").getTitle()).isEqualTo("Submit patch set 1 into master");
  }

  @Test
  public void deleteViewerVote() throws Exception {
    mockResponse(new CheckRequest("POST",
        ".*/changes/.*/reviewers/123/votes/Code-Review/delete"), "");
    gerritApi.deleteVote(CHANGE_ID,
        "123", "Code-Review", new DeleteVoteInput(NotifyType.NONE));
  }

  @Test
  public void deleteVoteNotFound() throws Exception {
    try {
      gerritApi.deleteVote(CHANGE_ID, "123", "Code-Review", new DeleteVoteInput(NotifyType.NONE));
    } catch (GerritApiException e) {
      assertThat(e.getResponseCode()).isEqualTo(ResponseCode.NOT_FOUND);
    }
  }

  private static String mockReviewResult() throws IOException {
    Map<String, Object> result = new LinkedHashMap<>();
    result.put("labels", ImmutableMap.of("Code-Review", (short) -1));
    return GsonFactory.getDefaultInstance().toPrettyString(result);
  }

  @Test
  public void testSetReviewInputSerialization() {
    SetReviewInput setReviewInput =
        SetReviewInput.create("foo", ImmutableMap.of("Code Review", 1), null);
    Gson gson = new Gson();
    String text = gson.toJson(setReviewInput);
    SetReviewInput deserialized = gson.fromJson(text, SetReviewInput.class);
    assertThat(deserialized.labels).isEqualTo(setReviewInput.labels);
    assertThat(deserialized.message).isEqualTo("foo");
  }

  private static String mockChangeInfo(ChangeStatus status) {
    return mockChangeInfo(status, /*detail=*/false);
  }

  private static String mockChangeInfo(ChangeStatus status, boolean detail) {
    String change = "{\n"
        + "  'id': 'copybara-project~"
        + CHANGE_ID
        + "',\n"
        + "  'project': 'copybara-project',\n"
        + "  'branch': 'master',\n"
        + "  'hashtags': [],\n"
        + "  'change_id': '"
        + CHANGE_ID
        + "',\n"
        + "  'subject': 'JUST A TEST',\n"
        + "    'status': '"
        + status
        + "',\n"
        + "  'created': '2017-12-01 17:33:30.000000000',\n"
        + "  'updated': '2017-12-01 17:33:30.000000000',\n"
        + "  'submit_type': 'MERGE_IF_NECESSARY',\n"
        + "  'submittable': true,\n"
        + "  'insertions': 2,\n"
        + "  'deletions': 10,\n"
        + "  'unresolved_comment_count': 0,\n"
        + "  'has_review_started': true,\n"
        + "  '_number': 1082,\n"
        + "  'owner': {\n"
        + "    '_account_id': 12345\n"
        + "  },\n"
        + "  'labels': {\n"
        + "    'Code-Review': {\n"
        + "      'all': [\n"
        + "        {\n"
        + "          'value': 2,\n"
        + "          'date': '2017-01-01 12:00:00.000000000',\n"
        + "          'permitted_voting_range': {\n"
        + "            'min': 2,\n"
        + "            'max': 2\n"
        + "          },\n"
        + "          '_account_id': 123456\n"
        + "        },\n"
        + "        {\n"
        + "          'value': 0,\n"
        + "          '_account_id': 123456\n"
        + "        },\n"
        + "        {\n"
        + "          'value': 0,\n"
        + "          '_account_id': 123456\n"
        + "        }\n"
        + "      ],\n"
        + "      'values': {\n"
        + "        '-2': 'Do not submit',\n"
        + "        '-1': 'I would prefer that you didn\\u0027t submit this',\n"
        + "        ' 0': 'No score',\n"
        + "        '+1': 'Looks good to me, but someone else must approve',\n"
        + "        '+2': 'Looks good to me, approved'\n"
        + "      },\n"
        + "      'default_value': 0\n"
        + "    }\n"
        + "},\n"
        + "  'current_revision': 'f33bd8687ae27c25254a21012b3c9b4a546db779',\n"
        + "  'revisions': {\n"
        + "    'f33bd8687ae27c25254a21012b3c9b4a546db779': {\n"
        + "      'kind': 'REWORK',\n"
        + "      '_number': 1,\n"
        + "      'created': '2017-12-07 19:11:59.000000000',\n"
        + "      'uploader': {\n"
        + "        '_account_id': 12345\n"
        + "      },\n"
        + "      'ref': 'refs/changes/11/11111/1',\n"
        + "      'fetch': {\n"
        + "        'https': {\n"
        + "          'url': 'https://foo.bar/copybara/test',\n"
        + "          'ref': 'refs/changes/11/11111/1'\n"
        + "        }\n"
        + "      },\n"
        + "      'commit': {\n"
        + "        'parents': [\n"
        + "          {\n"
        + "            'commit': 'e6b7772add9d2137fd5f879192bd249dfc4d0a00',\n"
        + "            'subject': 'Parent commit description.'\n"
        + "          }\n"
        + "        ],\n"
        + "        'author': {\n"
        + "          'name': 'Glorious Copybara',\n"
        + "          'email': '[email protected]',\n"
        + "          'date': '2017-12-01 00:00:00.000000000',\n"
        + "          'tz': -480\n"
        + "        },\n"
        + "        'committer': {\n"
        + "          'name': 'Glorious Copybara',\n"
        + "          'email': '[email protected]',\n"
        + "          'date': '2017-12-01 00:00:00.000000000',\n"
        + "          'tz': -480\n"
        + "        },\n"
        + "        'subject': 'JUST A TEST',\n"
        + "        'message': 'JUST A TEST\\n\\nSecond line of description.\n'\n"
        + "      }\n"
        + "    }\n"
        + "  },\n"
        + "  'messages': [\n"
        + "      {\n"
        + "        'id': 'e6aa8a323fd948cc9986dd4d8b4c253487bab253',\n"
        + "        'tag': 'autogenerated:gerrit:newPatchSet',\n"
        + "        'author': {\n"
        + "          '_account_id': 12345,\n"
        + "          'name': 'Glorious Copybara',\n"
        + "          'email': '[email protected]'\n"
        + "        },\n"
        + "        'real_author': {\n"
        + "          '_account_id': 12345,\n"
        + "          'name': 'Glorious Copybara',\n"
        + "          'email': '[email protected]'\n"
        + "        },\n"
        + "        'date': '2017-12-01 00:00:00.000000000',\n"
        + "        'message': 'Uploaded patch set 1.',\n"
        + "        '_revision_number': 1\n"
        + "      }\n"
        + "  ]";
    if (detail) {
      return change
          + ",\n"
          + "\"reviewers\": {\n"
          + "      \"REVIEWER\": [\n"
          + "        {\n"
          + "          \"_account_id\": 1000096,\n"
          + "          \"name\": \"John Doe\",\n"
          + "          \"email\": \"[email protected]\",\n"
          + "          \"username\": \"jdoe\"\n"
          + "        }"
          + "      ],\n"
          + "      \"CC\": [\n"
          + "        {\n"
          + "          \"_account_id\": 1000097,\n"
          + "          \"name\": \"Jane Roe\",\n"
          + "          \"email\": \"[email protected]\",\n"
          + "          \"username\": \"jroe\"\n"
          + "        }\n"
          + "      ]\n"
          + "    }"
          + "}\n";
    } else {
      return change + "\n}\n";
    }
  }

  private String mockAddReviewerResult(){
    return "{\n"
        + "    \"input\": \"[email protected]\"\n"
        + "  }";
  }
  @Test
  public void testGetAccessInfo() throws Exception {
    JsonObject response = new JsonObject();
    response.addProperty("is_owner", true);
    mockResponse(new CheckRequest("GET", ".*projects/copybara/access.*"), response.toString());
    ProjectAccessInfo info =  gerritApi.getAccessInfo("copybara");
    assertThat(info.isOwner).isTrue();
  }

  @Test
  public void testGetSelfAccount() throws Exception {
    JsonObject response = new JsonObject();
    response.addProperty("_account_id", 42);
    response.addProperty("name", "Copy Bara");
    response.addProperty("email", "[email protected]");

    mockResponse(new CheckRequest("GET", ".*accounts/self"), response.toString());
    AccountInfo info =  gerritApi.getSelfAccount();
    assertThat(info.getAccountId()).isEqualTo(42);
  }

  private void mockResponse(Predicate<String> filter, String response) {
    requestToResponse.put(filter, response.getBytes(StandardCharsets.UTF_8));
  }

  private class CheckRequest implements Predicate<String> {

    private final String method;
    private final String path;

    CheckRequest(String method, String path) {
      this.method = Preconditions.checkNotNull(method);
      this.path = Preconditions.checkNotNull(path);
    }

    @Override
    public boolean test(String s) {
      return s.matches(
          "(\r|\n|.)*" + method + " " + GerritApiTest.this.getHost() + path + "(\r|\n|.)*");
    }
  }
}