Add tests for plugin-provided fields controlled by options
Change-Id: Iec2008ad7fb9c6e63135e20a4b91586300e88499
diff --git a/javatests/com/google/gerrit/acceptance/api/change/PluginFieldsIT.java b/javatests/com/google/gerrit/acceptance/api/change/PluginFieldsIT.java
index 86b55bc..e495c99 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/PluginFieldsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/PluginFieldsIT.java
@@ -17,8 +17,11 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.io.CharStreams;
import com.google.common.reflect.TypeToken;
import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -30,16 +33,23 @@
import com.google.gerrit.extensions.common.PluginDefinedInfo;
import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.DynamicOptions.DynamicBean;
import com.google.gerrit.server.query.change.ChangeQueryProcessor.ChangeAttributeFactory;
import com.google.gerrit.server.query.change.OutputStreamQuery;
+import com.google.gerrit.server.restapi.change.QueryChanges;
+import com.google.gerrit.sshd.PluginCommandModule;
+import com.google.gerrit.sshd.commands.Query;
import com.google.gson.Gson;
import com.google.inject.AbstractModule;
+import com.google.inject.servlet.ServletModule;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.stream.Stream;
import org.junit.Test;
+import org.kohsuke.args4j.Option;
@UseSsh
public class PluginFieldsIT extends AbstractDaemonTest {
@@ -96,6 +106,38 @@
}
}
+ static class MyOptions implements DynamicBean {
+ @Option(name = "--opt")
+ private String opt;
+ }
+
+ static class OptionAttributeSysModule extends AbstractModule {
+ @Override
+ public void configure() {
+ bind(ChangeAttributeFactory.class)
+ .annotatedWith(Exports.named("simple"))
+ .toInstance(
+ (cd, qp, p) -> {
+ MyOptions opts = (MyOptions) qp.getDynamicBean(p);
+ return opts != null ? new MyInfo("opt " + opts.opt) : null;
+ });
+ }
+ }
+
+ static class OptionAttributeSshModule extends PluginCommandModule {
+ @Override
+ protected void configureCommands() {
+ bind(DynamicBean.class).annotatedWith(Exports.named(Query.class)).to(MyOptions.class);
+ }
+ }
+
+ static class OptionAttributeHttpModule extends ServletModule {
+ @Override
+ protected void configureServlets() {
+ bind(DynamicBean.class).annotatedWith(Exports.named(QueryChanges.class)).to(MyOptions.class);
+ }
+ }
+
@Test
public void queryChangeApiWithNullAttribute() throws Exception {
queryChangeWithNullAttribute(
@@ -155,12 +197,73 @@
assertThat(getter.call(id)).isNull();
}
+ @Test
+ public void queryChangeSshWithOption() throws Exception {
+ queryChangeWithOption(
+ id -> pluginInfoFromSingletonListSsh(adminSshSession.exec(changeQueryCmd(id))),
+ (id, opts) ->
+ pluginInfoFromSingletonListSsh(adminSshSession.exec(changeQueryCmd(id, opts))));
+ }
+
+ @Test
+ public void queryChangeRestWithOption() throws Exception {
+ queryChangeWithOption(
+ id -> pluginInfoFromSingletonListRest(adminRestSession.get(changeQueryUrl(id))),
+ (id, opts) ->
+ pluginInfoFromSingletonListRest(adminRestSession.get(changeQueryUrl(id, opts))));
+ }
+
+ // No test for plugin-provided options over the extension API. There are currently two separate
+ // DynamicMap<DynamicBean> maps initialized in the SSH and HTTP injectors, and plugins have to
+ // define separate SSH/HTTP modules and bind their DynamicBeans in each one. To use the extension
+ // API, we would have to move everything into the sys injector.
+ // TODO(dborowitz): Determine whether this is possible without breaking existing plugins.
+
+ private void queryChangeWithOption(
+ PluginInfoGetter getterWithoutOptions, PluginInfoGetterWithOptions getterWithOptions)
+ throws Exception {
+ Change.Id id = createChange().getChange().getId();
+ assertThat(getterWithoutOptions.call(id)).isNull();
+
+ try (AutoCloseable ignored =
+ installPlugin(
+ "my-plugin",
+ OptionAttributeSysModule.class,
+ OptionAttributeHttpModule.class,
+ OptionAttributeSshModule.class)) {
+ assertThat(getterWithoutOptions.call(id))
+ .containsExactly(new MyInfo("my-plugin", "opt null"));
+ assertThat(getterWithOptions.call(id, ImmutableListMultimap.of("my-plugin--opt", "foo")))
+ .containsExactly(new MyInfo("my-plugin", "opt foo"));
+ }
+
+ assertThat(getterWithoutOptions.call(id)).isNull();
+ }
+
private String changeQueryUrl(Change.Id id) {
- return "/changes/?q=" + id;
+ return changeQueryUrl(id, ImmutableListMultimap.of());
+ }
+
+ private String changeQueryUrl(Change.Id id, ImmutableListMultimap<String, String> opts) {
+ String url = "/changes/?q=" + id;
+ String queryString = Joiner.on('&').withKeyValueSeparator('=').join(opts.entries());
+ if (!queryString.isEmpty()) {
+ url += "&" + queryString;
+ }
+ return url;
}
private String changeQueryCmd(Change.Id id) {
- return "gerrit query --format json " + id;
+ return changeQueryCmd(id, ImmutableListMultimap.of());
+ }
+
+ private String changeQueryCmd(Change.Id id, ImmutableListMultimap<String, String> pluginOptions) {
+ return "gerrit query --format json "
+ + pluginOptions.entries().stream()
+ .flatMap(e -> Stream.of("--" + e.getKey(), e.getValue()))
+ .collect(joining(" "))
+ + " "
+ + id;
}
private static List<MyInfo> pluginInfoFromSingletonList(List<ChangeInfo> changeInfos) {
@@ -216,4 +319,10 @@
private interface PluginInfoGetter {
List<MyInfo> call(Change.Id id) throws Exception;
}
+
+ @FunctionalInterface
+ private interface PluginInfoGetterWithOptions {
+ List<MyInfo> call(Change.Id id, ImmutableListMultimap<String, String> pluginOptions)
+ throws Exception;
+ }
}