com.google.gerrit.acceptance.edit.ChangeEditIT.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.acceptance.edit.ChangeEditIT.java

Source

// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.acceptance.edit;

import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.http.HttpStatus.SC_CONFLICT;
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_NO_CONTENT;
import static org.apache.http.HttpStatus.SC_OK;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ApprovalInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.change.ChangeEdits.EditMessage;
import com.google.gerrit.server.change.ChangeEdits.Post;
import com.google.gerrit.server.change.ChangeEdits.Put;
import com.google.gerrit.server.change.FileContentUtil;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditModifier;
import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gerrit.server.edit.UnchangedCommitMessageException;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.Util;
import com.google.gson.stream.JsonReader;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;

import org.apache.commons.codec.binary.StringUtils;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeUtils.MillisProvider;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

public class ChangeEditIT extends AbstractDaemonTest {

    private static final String FILE_NAME = "foo";
    private static final String FILE_NAME2 = "foo2";
    private static final String FILE_NAME3 = "foo3";
    private static final byte[] CONTENT_OLD = "bar".getBytes(UTF_8);
    private static final byte[] CONTENT_NEW = "baz".getBytes(UTF_8);
    private static final String CONTENT_NEW2_STR = "qux";
    private static final byte[] CONTENT_NEW2 = CONTENT_NEW2_STR.getBytes(UTF_8);

    @Inject
    private SchemaFactory<ReviewDb> reviewDbProvider;

    @Inject
    ChangeEditUtil editUtil;

    @Inject
    private ChangeEditModifier modifier;

    @Inject
    private FileContentUtil fileUtil;

    private Change change;
    private String changeId;
    private Change change2;
    private String changeId2;
    private PatchSet ps;
    private PatchSet ps2;

    @BeforeClass
    public static void setTimeForTesting() {
        final long clockStepMs = MILLISECONDS.convert(1, SECONDS);
        final AtomicLong clockMs = new AtomicLong(new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
        DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
            @Override
            public long getMillis() {
                return clockMs.getAndAdd(clockStepMs);
            }
        });
    }

    @AfterClass
    public static void restoreTime() {
        DateTimeUtils.setCurrentMillisSystem();
    }

    @Before
    public void setUp() throws Exception {
        db = reviewDbProvider.open();
        changeId = newChange(admin.getIdent());
        ps = getCurrentPatchSet(changeId);
        amendChange(admin.getIdent(), changeId);
        change = getChange(changeId);
        assertThat(ps).isNotNull();
        changeId2 = newChange2(admin.getIdent());
        change2 = getChange(changeId2);
        assertThat(change2).isNotNull();
        ps2 = getCurrentPatchSet(changeId2);
        assertThat(ps2).isNotNull();
    }

    @After
    public void cleanup() {
        db.close();
    }

    @Test
    public void deleteEdit() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        editUtil.delete(editUtil.byChange(change).get());
        assertThat(editUtil.byChange(change).isPresent()).isFalse();
    }

    @Test
    public void publishEdit() throws Exception {
        assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId))).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW2))).isEqualTo(RefUpdate.Result.FORCED);
        editUtil.publish(editUtil.byChange(change).get());
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(edit.isPresent()).isFalse();
        assertChangeMessages(change, ImmutableList.of("Uploaded patch set 1.", "Uploaded patch set 2.",
                "Patch set 3: Published edit on patch set 2."));
    }

    @Test
    public void publishEditRest() throws Exception {
        PatchSet oldCurrentPatchSet = getCurrentPatchSet(changeId);
        assertThat(modifier.createEdit(change, oldCurrentPatchSet)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        RestResponse r = adminSession.post(urlPublish());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
        edit = editUtil.byChange(change);
        assertThat(edit.isPresent()).isFalse();
        PatchSet newCurrentPatchSet = getCurrentPatchSet(changeId);
        assertThat(newCurrentPatchSet.getId()).isNotEqualTo(oldCurrentPatchSet.getId());
        assertChangeMessages(change, ImmutableList.of("Uploaded patch set 1.", "Uploaded patch set 2.",
                "Patch set 3: Published edit on patch set 2."));
    }

    @Test
    public void deleteEditRest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        RestResponse r = adminSession.delete(urlEdit());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
        edit = editUtil.byChange(change);
        assertThat(edit.isPresent()).isFalse();
    }

    @Test
    public void publishEditRestWithoutCLA() throws Exception {
        setUseContributorAgreements(InheritableBoolean.TRUE);
        PatchSet oldCurrentPatchSet = getCurrentPatchSet(changeId);
        assertThat(modifier.createEdit(change, oldCurrentPatchSet)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        RestResponse r = adminSession.post(urlPublish());
        assertThat(r.getStatusCode()).isEqualTo(SC_FORBIDDEN);
        setUseContributorAgreements(InheritableBoolean.FALSE);
        r = adminSession.post(urlPublish());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
    }

    @Test
    public void rebaseEdit() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        ChangeEdit edit = editUtil.byChange(change).get();
        PatchSet current = getCurrentPatchSet(changeId);
        assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(current.getPatchSetId() - 1);
        Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
        modifier.rebaseEdit(edit, current);
        edit = editUtil.byChange(change).get();
        assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
                ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
                ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
        assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(current.getPatchSetId());
        Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
        assertThat(beforeRebase.equals(afterRebase)).isFalse();
    }

    @Test
    public void rebaseEditRest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        ChangeEdit edit = editUtil.byChange(change).get();
        PatchSet current = getCurrentPatchSet(changeId);
        assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(current.getPatchSetId() - 1);
        Date beforeRebase = edit.getEditCommit().getCommitterIdent().getWhen();
        RestResponse r = adminSession.post(urlRebase());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
        edit = editUtil.byChange(change).get();
        assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
                ObjectId.fromString(edit.getRevision().get()), FILE_NAME), CONTENT_NEW);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.getChange().getProject()),
                ObjectId.fromString(edit.getRevision().get()), FILE_NAME2), CONTENT_NEW2);
        assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(current.getPatchSetId());
        Date afterRebase = edit.getEditCommit().getCommitterIdent().getWhen();
        assertThat(afterRebase).isNotEqualTo(beforeRebase);
    }

    @Test
    public void rebaseEditWithConflictsRest_Conflict() throws Exception {
        PatchSet current = getCurrentPatchSet(changeId2);
        assertThat(modifier.createEdit(change2, current)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change2).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        ChangeEdit edit = editUtil.byChange(change2).get();
        assertThat(edit.getBasePatchSet().getPatchSetId()).isEqualTo(current.getPatchSetId());
        PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT, FILE_NAME,
                new String(CONTENT_NEW2), changeId2);
        push.to("refs/for/master").assertOkStatus();
        RestResponse r = adminSession.post(urlRebase());
        assertThat(r.getStatusCode()).isEqualTo(SC_CONFLICT);
    }

    @Test
    public void updateExistingFile() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
        editUtil.delete(edit.get());
        edit = editUtil.byChange(change);
        assertThat(edit.isPresent()).isFalse();
    }

    @Test
    @TestProjectInput(createEmptyCommit = false)
    public void updateRootCommitMessage() throws Exception {
        // Re-clone empty repo; TestRepository doesn't let us reset to unborn head.
        testRepo = cloneProject(project);
        changeId = newChange(admin.getIdent());
        change = getChange(changeId);

        assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId))).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(edit.get().getEditCommit().getParentCount()).isEqualTo(0);

        String msg = String.format("New commit message\n\nChange-Id: %s", change.getKey());
        assertThat(modifier.modifyMessage(edit.get(), msg)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(msg);
    }

    @Test
    public void updateMessageNoChange() throws Exception {
        assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId))).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);

        exception.expect(UnchangedCommitMessageException.class);
        exception.expectMessage("New commit message cannot be same as existing commit message");
        modifier.modifyMessage(edit.get(), edit.get().getEditCommit().getFullMessage());
    }

    @Test
    public void updateMessage() throws Exception {
        assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId))).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);

        String msg = String.format("New commit message\n\nChange-Id: %s", change.getKey());
        assertThat(modifier.modifyMessage(edit.get(), msg)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(msg);

        editUtil.publish(edit.get());
        assertThat(editUtil.byChange(change).isPresent()).isFalse();

        ChangeInfo info = get(changeId, ListChangesOption.CURRENT_COMMIT, ListChangesOption.CURRENT_REVISION);
        assertThat(info.revisions.get(info.currentRevision).commit.message).isEqualTo(msg);

        assertChangeMessages(change, ImmutableList.of("Uploaded patch set 1.", "Uploaded patch set 2.",
                "Patch set 3: Commit message was updated."));
    }

    @Test
    public void updateMessageRest() throws Exception {
        assertThat(adminSession.get(urlEditMessage()).getStatusCode()).isEqualTo(SC_NOT_FOUND);
        EditMessage.Input in = new EditMessage.Input();
        in.message = String.format("New commit message\n\n" + CONTENT_NEW2_STR + "\n\nChange-Id: %s",
                change.getKey());
        assertThat(adminSession.put(urlEditMessage(), in).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        RestResponse r = adminSession.getJsonAccept(urlEditMessage());
        assertThat(r.getStatusCode()).isEqualTo(SC_OK);
        assertThat(readContentFromJson(r)).isEqualTo(in.message);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(in.message);
        in.message = String.format("New commit message2\n\nChange-Id: %s", change.getKey());
        assertThat(adminSession.put(urlEditMessage(), in).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        edit = editUtil.byChange(change);
        assertThat(edit.get().getEditCommit().getFullMessage()).isEqualTo(in.message);
        editUtil.publish(edit.get());
        assertChangeMessages(change, ImmutableList.of("Uploaded patch set 1.", "Uploaded patch set 2.",
                "Patch set 3: Commit message was updated."));
    }

    @Test
    public void retrieveEdit() throws Exception {
        RestResponse r = adminSession.get(urlEdit());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        EditInfo info = toEditInfo(false);
        assertThat(info.commit.commit).isEqualTo(edit.get().getRevision().get());
        assertThat(info.commit.parents).hasSize(1);

        edit = editUtil.byChange(change);
        editUtil.delete(edit.get());

        r = adminSession.get(urlEdit());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
    }

    @Test
    public void retrieveFilesInEdit() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
                .isEqualTo(RefUpdate.Result.FORCED);

        EditInfo info = toEditInfo(true);
        assertThat(info.files).hasSize(2);
        List<String> l = Lists.newArrayList(info.files.keySet());
        assertThat(l.get(0)).isEqualTo("/COMMIT_MSG");
        assertThat(l.get(1)).isEqualTo("foo");
    }

    @Test
    public void deleteExistingFile() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.deleteFile(edit.get(), FILE_NAME)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        exception.expect(ResourceNotFoundException.class);
        fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
    }

    @Test
    public void renameExistingFile() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.renameFile(edit.get(), FILE_NAME, FILE_NAME3)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME3), CONTENT_OLD);
        exception.expect(ResourceNotFoundException.class);
        fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
    }

    @Test
    public void createEditByDeletingExistingFileRest() throws Exception {
        RestResponse r = adminSession.delete(urlEditFile());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        exception.expect(ResourceNotFoundException.class);
        fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
    }

    @Test
    public void deletingNonExistingEditRest() throws Exception {
        RestResponse r = adminSession.delete(urlEdit());
        assertThat(r.getStatusCode()).isEqualTo(SC_NOT_FOUND);
    }

    @Test
    public void deleteExistingFileRest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        exception.expect(ResourceNotFoundException.class);
        fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
    }

    @Test
    public void restoreDeletedFileInPatchSet() throws Exception {
        assertThat(modifier.createEdit(change2, ps2)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change2);
        assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change2);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
    }

    @Test
    public void revertChanges() throws Exception {
        assertThat(modifier.createEdit(change2, ps2)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change2);
        assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change2);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
        assertThat(modifier.modifyFile(editUtil.byChange(change2).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change2);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
        assertThat(modifier.restoreFile(edit.get(), FILE_NAME)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change2);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
        editUtil.delete(edit.get());
    }

    @Test
    public void renameFileRest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Post.Input in = new Post.Input();
        in.oldPath = FILE_NAME;
        in.newPath = FILE_NAME3;
        assertThat(adminSession.post(urlEdit(), in).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME3), CONTENT_OLD);
        exception.expect(ResourceNotFoundException.class);
        fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
    }

    @Test
    public void restoreDeletedFileInPatchSetRest() throws Exception {
        Post.Input in = new Post.Input();
        in.restorePath = FILE_NAME;
        assertThat(adminSession.post(urlEdit2(), in).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change2);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
    }

    @Test
    public void amendExistingFile() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
    }

    @Test
    public void createAndChangeEditInOneRequestRest() throws Exception {
        Put.Input in = new Put.Input();
        in.content = RestSession.newRawInput(CONTENT_NEW);
        assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
        in.content = RestSession.newRawInput(CONTENT_NEW2);
        assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW2);
    }

    @Test
    public void changeEditRest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Put.Input in = new Put.Input();
        in.content = RestSession.newRawInput(CONTENT_NEW);
        assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_NEW);
    }

    @Test
    public void emptyPutRequest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(adminSession.put(urlEditFile()).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), "".getBytes());
    }

    @Test
    public void createEmptyEditRest() throws Exception {
        assertThat(adminSession.post(urlEdit()).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME), CONTENT_OLD);
    }

    @Test
    public void getFileContentRest() throws Exception {
        Put.Input in = new Put.Input();
        in.content = RestSession.newRawInput(CONTENT_NEW);
        assertThat(adminSession.putRaw(urlEditFile(), in.content).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME, RestSession.newRawInput(CONTENT_NEW2)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        RestResponse r = adminSession.getJsonAccept(urlEditFile());
        assertThat(r.getStatusCode()).isEqualTo(SC_OK);
        assertThat(readContentFromJson(r)).isEqualTo(StringUtils.newStringUtf8(CONTENT_NEW2));
    }

    @Test
    public void getFileNotFoundRest() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(adminSession.delete(urlEditFile()).getStatusCode()).isEqualTo(SC_NO_CONTENT);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        RestResponse r = adminSession.get(urlEditFile());
        assertThat(r.getStatusCode()).isEqualTo(SC_NO_CONTENT);
        exception.expect(ResourceNotFoundException.class);
        fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME);
    }

    @Test
    public void addNewFile() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
    }

    @Test
    public void addNewFileAndAmend() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW);
        assertThat(modifier.modifyFile(edit.get(), FILE_NAME2, RestSession.newRawInput(CONTENT_NEW2)))
                .isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        assertByteArray(fileUtil.getContent(projectCache.get(edit.get().getChange().getProject()),
                ObjectId.fromString(edit.get().getRevision().get()), FILE_NAME2), CONTENT_NEW2);
    }

    @Test
    public void writeNoChanges() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        exception.expect(InvalidChangeOperationException.class);
        exception.expectMessage("no changes were made");
        modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME, RestSession.newRawInput(CONTENT_OLD));
    }

    @Test
    public void editCommitMessageCopiesLabelScores() throws Exception {
        String cr = "Code-Review";
        ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
        LabelType codeReview = Util.codeReview();
        codeReview.setCopyAllScoresIfNoCodeChange(true);
        cfg.getLabelSections().put(cr, codeReview);
        saveProjectConfig(project, cfg);

        String changeId = change.getKey().get();
        ReviewInput r = new ReviewInput();
        r.labels = ImmutableMap.<String, Short>of(cr, (short) 1);
        gApi.changes().id(changeId).revision(change.currentPatchSetId().get()).review(r);

        assertThat(modifier.createEdit(change, getCurrentPatchSet(changeId))).isEqualTo(RefUpdate.Result.NEW);
        Optional<ChangeEdit> edit = editUtil.byChange(change);
        String newSubj = "New commit message";
        String newMsg = newSubj + "\n\nChange-Id: " + changeId + "\n";
        assertThat(modifier.modifyMessage(edit.get(), newMsg)).isEqualTo(RefUpdate.Result.FORCED);
        edit = editUtil.byChange(change);
        editUtil.publish(edit.get());

        ChangeInfo info = get(changeId);
        assertThat(info.subject).isEqualTo(newSubj);
        List<ApprovalInfo> approvals = info.labels.get(cr).all;
        assertThat(approvals).hasSize(1);
        assertThat(approvals.get(0).value).isEqualTo(1);
    }

    @Test
    public void testHasEditPredicate() throws Exception {
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(queryEdits()).hasSize(1);

        PatchSet current = getCurrentPatchSet(changeId2);
        assertThat(modifier.createEdit(change2, current)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(modifier.modifyFile(editUtil.byChange(change2).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        assertThat(queryEdits()).hasSize(2);

        assertThat(modifier.modifyFile(editUtil.byChange(change).get(), FILE_NAME,
                RestSession.newRawInput(CONTENT_NEW))).isEqualTo(RefUpdate.Result.FORCED);
        editUtil.delete(editUtil.byChange(change).get());
        assertThat(queryEdits()).hasSize(1);

        editUtil.publish(editUtil.byChange(change2).get());
        assertThat(queryEdits()).hasSize(0);

        setApiUser(user);
        assertThat(modifier.createEdit(change, ps)).isEqualTo(RefUpdate.Result.NEW);
        assertThat(queryEdits()).hasSize(1);

        setApiUser(admin);
        assertThat(queryEdits()).hasSize(0);
    }

    private List<ChangeInfo> queryEdits() throws Exception {
        return query("project:{" + project.get() + "} has:edit");
    }

    private String newChange(PersonIdent ident) throws Exception {
        PushOneCommit push = pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
                new String(CONTENT_OLD, StandardCharsets.UTF_8));
        return push.to("refs/for/master").getChangeId();
    }

    private String amendChange(PersonIdent ident, String changeId) throws Exception {
        PushOneCommit push = pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME2,
                new String(CONTENT_NEW2, StandardCharsets.UTF_8), changeId);
        return push.to("refs/for/master").getChangeId();
    }

    private String newChange2(PersonIdent ident) throws Exception {
        PushOneCommit push = pushFactory.create(db, ident, testRepo, PushOneCommit.SUBJECT, FILE_NAME,
                new String(CONTENT_OLD, StandardCharsets.UTF_8));
        return push.rm("refs/for/master").getChangeId();
    }

    private Change getChange(String changeId) throws Exception {
        return getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
    }

    private PatchSet getCurrentPatchSet(String changeId) throws Exception {
        return db.patchSets().get(getChange(changeId).currentPatchSetId());
    }

    private static void assertByteArray(BinaryResult result, byte[] expected) throws Exception {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        result.writeTo(os);
        assertThat(os.toByteArray()).isEqualTo(expected);
    }

    private String urlEdit() {
        return "/changes/" + change.getChangeId() + "/edit";
    }

    private String urlEdit2() {
        return "/changes/" + change2.getChangeId() + "/edit/";
    }

    private String urlEditMessage() {
        return "/changes/" + change.getChangeId() + "/edit:message";
    }

    private String urlEditFile() {
        return urlEdit() + "/" + FILE_NAME;
    }

    private String urlGetFiles() {
        return urlEdit() + "?list";
    }

    private String urlPublish() {
        return "/changes/" + change.getChangeId() + "/edit:publish";
    }

    private String urlRebase() {
        return "/changes/" + change.getChangeId() + "/edit:rebase";
    }

    private EditInfo toEditInfo(boolean files) throws IOException {
        RestResponse r = adminSession.get(files ? urlGetFiles() : urlEdit());
        assertThat(r.getStatusCode()).isEqualTo(SC_OK);
        return newGson().fromJson(r.getReader(), EditInfo.class);
    }

    private String readContentFromJson(RestResponse r) throws IOException {
        JsonReader jsonReader = new JsonReader(r.getReader());
        jsonReader.setLenient(true);
        return newGson().fromJson(jsonReader, String.class);
    }

    private void assertChangeMessages(Change c, List<String> expectedMessages) throws Exception {
        ChangeInfo ci = get(c.getId().toString());
        assertThat(ci.messages).isNotNull();
        assertThat(ci.messages).hasSize(expectedMessages.size());
        List<String> actualMessages = new ArrayList<>();
        Iterator<ChangeMessageInfo> it = ci.messages.iterator();
        while (it.hasNext()) {
            actualMessages.add(it.next().message);
        }
        assertThat(actualMessages).containsExactlyElementsIn(expectedMessages).inOrder();
    }
}