Add tests for reviewing and submitting refs/meta/config changes

Change-Id: I51b699e0a4dc32b96e228629319b682f566c553c
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
new file mode 100644
index 0000000..c78231b
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -0,0 +1,153 @@
+// Copyright (C) 2016 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.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.Util;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RefSpec;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ConfigChangeIT extends AbstractDaemonTest {
+  @Before
+  public void setUp() throws Exception {
+    ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+    Util.allow(cfg, Permission.OWNER, REGISTERED_USERS, "refs/*");
+    Util.allow(
+        cfg, Permission.PUSH, REGISTERED_USERS, "refs/for/refs/meta/config");
+    Util.allow(cfg, Permission.SUBMIT, REGISTERED_USERS, "refs/meta/config");
+    saveProjectConfig(project, cfg);
+
+    setApiUser(user);
+    fetchRefsMetaConfig();
+  }
+
+  @Test
+  @TestProjectInput(cloneAs = "user")
+  public void updateProjectConfig() throws Exception {
+    Config cfg = readProjectConfig();
+    assertThat(cfg.getString("project", null, "description")).isNull();
+    String desc = "new project description";
+    cfg.setString("project", null, "description", desc);
+
+    PushOneCommit.Result r = createConfigChange(cfg);
+    String id = r.getChangeId();
+
+    gApi.changes().id(id).current().review(ReviewInput.approve());
+    gApi.changes().id(id).current().submit();
+
+    assertThat(gApi.changes().id(id).info().status)
+        .isEqualTo(ChangeStatus.MERGED);
+    assertThat(gApi.projects().name(project.get()).get().description)
+        .isEqualTo(desc);
+    fetchRefsMetaConfig();
+    assertThat(readProjectConfig().getString("project", null, "description"))
+        .isEqualTo(desc);
+  }
+
+  @Test
+  @TestProjectInput(cloneAs = "user")
+  public void onlyAdminMayUpdateProjectParent() throws Exception {
+    setApiUser(admin);
+    ProjectInput parent = new ProjectInput();
+    parent.name = name("parent");
+    parent.permissionsOnly = true;
+    gApi.projects().create(parent);
+
+    setApiUser(user);
+    Config cfg = readProjectConfig();
+    assertThat(cfg.getString("access", null, "inheritFrom"))
+        .isAnyOf(null, allProjects.get());
+    cfg.setString("access", null, "inheritFrom", parent.name);
+
+    PushOneCommit.Result r = createConfigChange(cfg);
+    String id = r.getChangeId();
+
+    gApi.changes().id(id).current().review(ReviewInput.approve());
+    try {
+      gApi.changes().id(id).current().submit();
+      fail("expected submit to fail");
+    } catch (ResourceConflictException e) {
+      assertThat(e).hasMessage(
+          "Cannot merge " + r.getCommit().name() + "\n"
+          + "Change contains a project configuration that changes the parent"
+          + " project.\n"
+          + "The change must be submitted by a Gerrit administrator.");
+    }
+
+    assertThat(gApi.projects().name(project.get()).get().parent)
+        .isEqualTo(allProjects.get());
+    fetchRefsMetaConfig();
+    assertThat(readProjectConfig().getString("access", null, "inheritFrom"))
+        .isAnyOf(null, allProjects.get());
+
+    setApiUser(admin);
+    gApi.changes().id(id).current().submit();
+    assertThat(gApi.changes().id(id).info().status)
+        .isEqualTo(ChangeStatus.MERGED);
+    assertThat(gApi.projects().name(project.get()).get().parent)
+        .isEqualTo(parent.name);
+    fetchRefsMetaConfig();
+    assertThat(readProjectConfig().getString("access", null, "inheritFrom"))
+        .isEqualTo(parent.name);
+  }
+
+  private void fetchRefsMetaConfig() throws Exception {
+    git().fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config"))
+        .call();
+    testRepo.reset("refs/meta/config");
+  }
+
+  private Config readProjectConfig() throws Exception {
+    RevWalk rw = testRepo.getRevWalk();
+    RevTree tree = rw.parseTree(testRepo.getRepository().resolve("HEAD"));
+    RevObject obj = rw.parseAny(testRepo.get(tree, "project.config"));
+    ObjectLoader loader = rw.getObjectReader().open(obj);
+    String text = new String(loader.getCachedBytes(), UTF_8);
+    Config cfg = new Config();
+    cfg.fromText(text);
+    return cfg;
+  }
+
+  private PushOneCommit.Result createConfigChange(Config cfg) throws Exception {
+    PushOneCommit.Result r = pushFactory.create(
+            db, user.getIdent(), testRepo,
+            "Update project config",
+            "project.config",
+            cfg.toText())
+        .to("refs/for/refs/meta/config");
+    r.assertOkStatus();
+    return r;
+  }
+}