Repository: hub4j/github-api (java)

Issue:
Checks api functionality broken by recent github api change
Since 26/05 previously working calls to `createCheckRun` fail with the following error when trying to parse the github response

```
Cannot deserialize value of type `org.kohsuke.github.GHEvent` from String "discussion": not one of the values accepted for Enum class: [GOLLUM, ALL, INTEGRATION_INSTALLATION_REPOSITORIES, META, DOWNLOAD, FORK_APPLY, CHECK_RUN, MARKETPLACE_PURCHASE, PROJECT_CARD, SCHEDULE, PUSH, PAGE_BUILD, TEAM_ADD, REPOSITORY_VULNERABILITY_ALERT, PACKAGE, UNKNOWN, DELETE, DEPLOYMENT_STATUS, WATCH, FOLLOW, ORG_BLOCK, ORGANIZATION, COMMIT_COMMENT, ISSUE_COMMENT, REPOSITORY_IMPORT, GITHUB_APP_AUTHORIZATION, CODE_SCANNING_ALERT, CHECK_SUITE, FORK, GIST, CONTENT_REFERENCE, REPOSITORY, INSTALLATION_REPOSITORIES, REPOSITORY_DISPATCH, MILESTONE, STAR, LABEL, MEMBERSHIP, SECURITY_ADVISORY, PROJECT_COLUMN, TEAM, PULL_REQUEST_REVIEW, RELEASE, PUBLIC, WORKFLOW_RUN, STATUS, PULL_REQUEST, PROJECT, WORKFLOW_DISPATCH, CREATE, MEMBER, REGISTRY_PACKAGE, INSTALLATION, DEPLOYMENT, PULL_REQUEST_REVIEW_COMMENT, DEPLOY_KEY, PING, ISSUES]
```

It isn't instantly obvious to me how the full github response can be logged, but a little debugging suggests that gh events now include 'DISCUSSION' and 'DISCUSSION_COMMENT'.

Unfortunately I think there have been further changes. Patching the enum to include these values moves things further, but calls to

```
   repo.updateCheckRun(checkRun.getId())
      .add(output)
      .withConclusion(GHCheckRun.Conclusion.SUCCESS)
      .withStatus(GHCheckRun.Status.COMPLETED)
      .create();
```

Now fail with 

`org.kohsuke.github.GHFileNotFoundException: https://api.github.com/repos/ORG/REPO/check-runs/2675563029 {"message":"Not Found","documentation_url":"https://docs.github.com/rest"}`

The check id is presumably correct as it was returned from the successful `createCheckRun` call, and this code previously functioned.

I think authentication is also affected. From memory, I was previously able to run adhoc tests of api calls using a personal access token. I now get a message saying that I must authenticate as a github app in order to use the checks api.



Pull request:
Fix issues related to introduction of new values in GHEvent
@bitwiseman I decided to apply the same strategy we used for other things: use Strings for serialization/deserialization and only switch to enums for APIs. It's a bit less optimal but less risky. Another better option would be to have custom Jackson deserializer but that's probably something we need to keep for 2.0.

I also fixed an inconsistency in `EnumUtils`: one method was user uppercase values and not the other. This is a recipe for disaster.

Better reviewed commit per commit as I kept them semantic.

I'm not exactly sure if it's worth adding tests as we would have to add tests with unknown values for each enum all over the place.

Fixes #1156

Patch:
diff --git a/src/main/java/org/kohsuke/github/GHApp.java b/src/main/java/org/kohsuke/github/GHApp.java
index b2178046d..eb1f8c35a 100644
--- a/src/main/java/org/kohsuke/github/GHApp.java
+++ b/src/main/java/org/kohsuke/github/GHApp.java
@@ -1,9 +1,12 @@
 package org.kohsuke.github;
 
+import org.kohsuke.github.internal.EnumUtils;
+
 import java.io.IOException;
 import java.net.URL;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import static org.kohsuke.github.internal.Previews.MACHINE_MAN;
 
@@ -20,7 +23,7 @@ public class GHApp extends GHObject {
     private String description;
     private String externalUrl;
     private Map<String, String> permissions;
-    private List<GHEvent> events;
+    private List<String> events;
     private long installationsCount;
     private String htmlUrl;
 
@@ -114,7 +117,9 @@ public class GHApp extends GHObject {
      * @return the events
      */
     public List<GHEvent> getEvents() {
-        return events;
+        return events.stream()
+                .map(e -> EnumUtils.getEnumOrDefault(GHEvent.class, e, GHEvent.UNKNOWN))
+                .collect(Collectors.toList());
     }
 
     /**
@@ -126,7 +131,7 @@ public class GHApp extends GHObject {
      */
     @Deprecated
     public void setEvents(List<GHEvent> events) {
-        this.events = events;
+        this.events = events.stream().map(GHEvent::symbol).collect(Collectors.toList());
     }
 
     /**
diff --git a/src/main/java/org/kohsuke/github/GHAppInstallation.java b/src/main/java/org/kohsuke/github/GHAppInstallation.java
index 7b1797fd5..ee5c732c2 100644
--- a/src/main/java/org/kohsuke/github/GHAppInstallation.java
+++ b/src/main/java/org/kohsuke/github/GHAppInstallation.java
@@ -1,12 +1,14 @@
 package org.kohsuke.github;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import org.kohsuke.github.internal.EnumUtils;
 
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import static org.kohsuke.github.internal.Previews.GAMBIT;
 import static org.kohsuke.github.internal.Previews.MACHINE_MAN;
@@ -35,7 +37,7 @@ public class GHAppInstallation extends GHObject {
     @JsonProperty("target_type")
     private GHTargetType targetType;
     private Map<String, GHPermissionType> permissions;
-    private List<GHEvent> events;
+    private List<String> events;
     @JsonProperty("single_file_name")
     private String singleFileName;
     @JsonProperty("repository_selection")
@@ -250,7 +252,9 @@ public class GHAppInstallation extends GHObject {
      * @return the events
      */
     public List<GHEvent> getEvents() {
-        return events;
+        return events.stream()
+                .map(e -> EnumUtils.getEnumOrDefault(GHEvent.class, e, GHEvent.UNKNOWN))
+                .collect(Collectors.toList());
     }
 
     /**
@@ -262,7 +266,7 @@ public class GHAppInstallation extends GHObject {
      */
     @Deprecated
     public void setEvents(List<GHEvent> events) {
-        this.events = events;
+        this.events = events.stream().map(GHEvent::symbol).collect(Collectors.toList());
     }
 
     /**
diff --git a/src/main/java/org/kohsuke/github/GHEvent.java b/src/main/java/org/kohsuke/github/GHEvent.java
index 046d1ddf2..a8c3b245b 100644
--- a/src/main/java/org/kohsuke/github/GHEvent.java
+++ b/src/main/java/org/kohsuke/github/GHEvent.java
@@ -1,11 +1,7 @@
 package org.kohsuke.github;
 
-import org.kohsuke.github.internal.EnumUtils;
-
 import java.util.Locale;
 
-import javax.annotation.Nonnull;
-
 /**
  * Hook event type.
  *
@@ -24,6 +20,8 @@ public enum GHEvent {
     DEPLOY_KEY,
     DEPLOYMENT,
     DEPLOYMENT_STATUS,
+    DISCUSSION,
+    DISCUSSION_COMMENT,
     DOWNLOAD,
     FOLLOW,
     FORK,
@@ -89,46 +87,4 @@ public enum GHEvent {
             return "*";
         return name().toLowerCase(Locale.ENGLISH);
     }
-
-    /**
-     * Representation of GitHub Event Type
-     *
-     * @see <a href="https://docs.github.com/en/developers/webhooks-and-events/github-event-types">GitHub event
-     *      types</a>
-     */
-    enum GitHubEventType {
-        CommitCommentEvent(COMMIT_COMMENT),
-        CreateEvent(CREATE),
-        DeleteEvent(DELETE),
-        ForkEvent(FORK),
-        GollumEvent(GOLLUM),
-        IssueCommentEvent(ISSUE_COMMENT),
-        IssuesEvent(ISSUES),
-        MemberEvent(MEMBER),
-        PublicEvent(PUBLIC),
-        PullRequestEvent(PULL_REQUEST),
-        PullRequestReviewEvent(PULL_REQUEST_REVIEW),
-        PullRequestReviewCommentEvent(PULL_REQUEST_REVIEW_COMMENT),
-        PushEvent(PUSH),
-        ReleaseEvent(RELEASE),
-        WatchEvent(WATCH),
-        UnknownEvent(UNKNOWN);
-
-        private final GHEvent event;
-        GitHubEventType(GHEvent event) {
-            this.event = event;
-        }
-
-        /**
-         * Required due to different naming conventions between different GitHub event names for Webhook events and
-         * GitHub events
-         *
-         * @param event
-         *            the github event as a string to convert to Event enum
-         * @return GHEvent
-         */
-        static GHEvent transformToGHEvent(@Nonnull String event) {
-            return EnumUtils.getEnumOrDefault(GitHubEventType.class, event, UnknownEvent).event;
-        }
-    }
 }
diff --git a/src/main/java/org/kohsuke/github/GHEventInfo.java b/src/main/java/org/kohsuke/github/GHEventInfo.java
index 43e178f9c..f550c9bcc 100644
--- a/src/main/java/org/kohsuke/github/GHEventInfo.java
+++ b/src/main/java/org/kohsuke/github/GHEventInfo.java
@@ -2,10 +2,9 @@ package org.kohsuke.github;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import org.kohsuke.github.GHEvent.GitHubEventType;
 
 import java.io.IOException;
-import java.util.Date;
+import java.util.*;
 
 /**
  * Represents an event.
@@ -19,6 +18,16 @@ public class GHEventInfo extends GitHubInteractiveObject {
 
     private long id;
     private String created_at;
+
+    /**
+     * Representation of GitHub Event API Event Type.
+     *
+     * This is not the same as the values used for hook methods such as
+     * {@link GHRepository#createHook(String, Map, Collection, boolean)}.
+     *
+     * @see <a href="https://docs.github.com/en/developers/webhooks-and-events/github-event-types">GitHub event
+     *      types</a>
+     */
     private String type;
 
     // these are all shallow objects
@@ -41,13 +50,45 @@ public class GHEventInfo extends GitHubInteractiveObject {
         private String name; // owner/repo
     }
 
+    static final Map<String, GHEvent> mapTypeStringToEvent = createEventMap();
+
+    /**
+     * Map for GitHub Event API Event Type to GHEvent.
+     *
+     * @see <a href="https://docs.github.com/en/developers/webhooks-and-events/github-event-types">GitHub event
+     *      types</a>
+     */
+    private static Map<String, GHEvent> createEventMap() {
+        HashMap<String, GHEvent> map = new HashMap<>();
+        map.put("CommitCommentEvent", GHEvent.COMMIT_COMMENT);
+        map.put("CreateEvent", GHEvent.CREATE);
+        map.put("DeleteEvent", GHEvent.DELETE);
+        map.put("ForkEvent", GHEvent.FORK);
+        map.put("GollumEvent", GHEvent.GOLLUM);
+        map.put("IssueCommentEvent", GHEvent.ISSUE_COMMENT);
+        map.put("IssuesEvent", GHEvent.ISSUES);
+        map.put("MemberEvent", GHEvent.MEMBER);
+        map.put("PublicEvent", GHEvent.PUBLIC);
+        map.put("PullRequestEvent", GHEvent.PULL_REQUEST);
+        map.put("PullRequestReviewEvent", GHEvent.PULL_REQUEST_REVIEW);
+        map.put("PullRequestReviewCommentEvent", GHEvent.PULL_REQUEST_REVIEW_COMMENT);
+        map.put("PushEvent", GHEvent.PUSH);
+        map.put("ReleaseEvent", GHEvent.RELEASE);
+        map.put("WatchEvent", GHEvent.WATCH);
+        return Collections.unmodifiableMap(map);
+    }
+
+    static GHEvent transformTypeToGHEvent(String type) {
+        return mapTypeStringToEvent.getOrDefault(type, GHEvent.UNKNOWN);
+    }
+
     /**
      * Gets type.
      *
      * @return the type
      */
     public GHEvent getType() {
-        return GitHubEventType.transformToGHEvent(type);
+        return transformTypeToGHEvent(type);
     }
 
     GHEventInfo wrapUp(GitHub root) {
diff --git a/src/main/java/org/kohsuke/github/GHHook.java b/src/main/java/org/kohsuke/github/GHHook.java
index 35fa7bd1f..0d8193213 100644
--- a/src/main/java/org/kohsuke/github/GHHook.java
+++ b/src/main/java/org/kohsuke/github/GHHook.java
@@ -1,13 +1,13 @@
 package org.kohsuke.github;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.kohsuke.github.internal.EnumUtils;
 
 import java.io.IOException;
 import java.net.URL;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -40,10 +40,7 @@ public abstract class GHHook extends GHObject {
     public EnumSet<GHEvent> getEvents() {
         EnumSet<GHEvent> s = EnumSet.noneOf(GHEvent.class);
         for (String e : events) {
-            if (e.equals("*"))
-                s.add(GHEvent.ALL);
-            else
-                s.add(Enum.valueOf(GHEvent.class, e.toUpperCase(Locale.ENGLISH)));
+            s.add(e.equals("*") ? GHEvent.ALL : EnumUtils.getEnumOrDefault(GHEvent.class, e, GHEvent.UNKNOWN));
         }
         return s;
     }
diff --git a/src/main/java/org/kohsuke/github/internal/EnumUtils.java b/src/main/java/org/kohsuke/github/internal/EnumUtils.java
index 4e81b9f8c..7d247ad80 100644
--- a/src/main/java/org/kohsuke/github/internal/EnumUtils.java
+++ b/src/main/java/org/kohsuke/github/internal/EnumUtils.java
@@ -30,7 +30,7 @@ public final class EnumUtils {
         if (value == null) {
             return null;
         }
-        return getEnumOrDefault(enumClass, value.toUpperCase(Locale.ROOT), defaultEnum);
+        return getEnumOrDefault(enumClass, value, defaultEnum);
     }
 
     /**
@@ -49,8 +49,8 @@ public final class EnumUtils {
      */
     public static <E extends Enum<E>> E getEnumOrDefault(Class<E> enumClass, String value, E defaultEnum) {
         try {
-            return Enum.valueOf(enumClass, value);
-        } catch (IllegalArgumentException e) {
+            return Enum.valueOf(enumClass, value.toUpperCase(Locale.ROOT));
+        } catch (NullPointerException | IllegalArgumentException e) {
             LOGGER.warning("Unknown value " + value + " for enum class " + enumClass.getName() + ", defaulting to "
                     + defaultEnum.name());
             return defaultEnum;


Test patch:
diff --git a/src/test/java/org/kohsuke/github/EnumTest.java b/src/test/java/org/kohsuke/github/EnumTest.java
index a83a6267c..605d1c728 100644
--- a/src/test/java/org/kohsuke/github/EnumTest.java
+++ b/src/test/java/org/kohsuke/github/EnumTest.java
@@ -27,7 +27,7 @@ public class EnumTest extends AbstractGitHubWireMockTest {
 
         assertThat(GHDirection.values().length, equalTo(2));
 
-        assertThat(GHEvent.values().length, equalTo(58));
+        assertThat(GHEvent.values().length, equalTo(60));
         assertThat(GHEvent.ALL.symbol(), equalTo("*"));
         assertThat(GHEvent.PULL_REQUEST.symbol(), equalTo(GHEvent.PULL_REQUEST.toString().toLowerCase()));
 
diff --git a/src/test/java/org/kohsuke/github/GHEventTest.java b/src/test/java/org/kohsuke/github/GHEventTest.java
index 97543a036..aea0fa14b 100644
--- a/src/test/java/org/kohsuke/github/GHEventTest.java
+++ b/src/test/java/org/kohsuke/github/GHEventTest.java
@@ -1,7 +1,6 @@
 package org.kohsuke.github;
 
 import org.junit.Test;
-import org.kohsuke.github.GHEvent.GitHubEventType;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -26,10 +25,9 @@ public class GHEventTest {
 
     @Test
     public void regressionTest() {
-        assertThat(GitHubEventType.transformToGHEvent("NewlyAddedOrBogusEvent"), is(GHEvent.UNKNOWN));
-        for (GitHubEventType gitHubEventType : GitHubEventType.values()) {
-            assertThat(GitHubEventType.transformToGHEvent(gitHubEventType.name()),
-                    is(oldTransformationFunction(gitHubEventType.name())));
+        assertThat(GHEventInfo.transformTypeToGHEvent("NewlyAddedOrBogusEvent"), is(GHEvent.UNKNOWN));
+        for (String eventInfoType : GHEventInfo.mapTypeStringToEvent.keySet()) {
+            assertThat(GHEventInfo.transformTypeToGHEvent(eventInfoType), is(oldTransformationFunction(eventInfoType)));
         }
     }
 }
diff --git a/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java b/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java
index ac5c686b6..0eb47385e 100644
--- a/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java
+++ b/src/test/java/org/kohsuke/github/internal/EnumUtilsTest.java
@@ -9,6 +9,8 @@ public class EnumUtilsTest {
 
     @Test
     public void testGetEnum() {
+        assertThat(EnumUtils.getEnumOrDefault(TestEnum.class, null, TestEnum.UNKNOWN), equalTo(TestEnum.UNKNOWN));
+
         assertThat(EnumUtils.getNullableEnumOrDefault(TestEnum.class, null, TestEnum.UNKNOWN), nullValue());
         assertThat(EnumUtils.getNullableEnumOrDefault(TestEnum.class, "foobar", TestEnum.UNKNOWN),
                 equalTo(TestEnum.UNKNOWN));