Skip plugins with malformed JSON metadata

When a plugin metadata artifact does not contain a valid JSON object,
JsonSyntaxException escapes from tryGetJson and aborts loading the
available plugin list.

Catch the exception, log the affected artifact URL, and treat the
metadata as unavailable so that the remaining plugins can still be
loaded.

Bug: Issue 564188311
Change-Id: I594f6736a45ef9868db3fec2dd9ea344c8a60e83
diff --git a/BUILD b/BUILD
index 9b85fed..51b324d 100644
--- a/BUILD
+++ b/BUILD
@@ -22,5 +22,6 @@
     visibility = ["//visibility:public"],
     deps = PLUGIN_TEST_DEPS + [
         ":plugin-manager__plugin",
+        "//lib/mockito",
     ],
 )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java
index d84eda5..48dac31 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/manager/repository/JenkinsCiPluginsRepository.java
@@ -20,6 +20,7 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
+import com.google.gson.JsonSyntaxException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -253,13 +254,16 @@
                     jsonArtifact.get().getString("relativePath"))));
   }
 
-  private Optional<SmartJson> tryGetJson(String url) {
+  @VisibleForTesting
+  Optional<SmartJson> tryGetJson(String url) {
     try {
       return Optional.of(gsonProvider.get().get(url));
+    } catch (JsonSyntaxException jse) {
+      logger.atWarning().withCause(jse).log("Invalid JSON from %s", url);
     } catch (IOException e) {
       logger.atSevere().withCause(e).log("Cannot get JSON from %s", url);
-      return Optional.empty();
     }
+    return Optional.empty();
   }
 
   @VisibleForTesting
diff --git a/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java b/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java
index 2437d90..0e14aa1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/manager/repository/PluginsRepositoryTest.java
@@ -17,10 +17,14 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static java.util.stream.Collectors.toList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gson.JsonSyntaxException;
+import com.googlesource.gerrit.plugins.manager.gson.SmartGson;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -70,6 +74,16 @@
     assertThat(pluginRepo.list(Version.getVersion())).isNotEmpty();
   }
 
+  @Test
+  public void malformedPluginMetadataShouldBeIgnored() throws IOException {
+    String metadataUrl = "https://example.com/plugin.json";
+    SmartGson gson = mock(SmartGson.class);
+    when(gson.get(metadataUrl)).thenThrow(new JsonSyntaxException("Expected a JSON object"));
+    JenkinsCiPluginsRepository pluginRepo = new JenkinsCiPluginsRepository(() -> gson, null);
+
+    assertThat(pluginRepo.tryGetJson(metadataUrl)).isEmpty();
+  }
+
   private SitePaths prepareSiteDirWithReleaseWar() throws IOException {
     SitePaths site = new SitePaths(random());
     Path pathToReleaseWar = Path.of(getenv("TEST_SRCDIR"), getenv("TEST_WORKSPACE"), "release.war");