Repository: hub4j/github-api
Pull request #1187 (2021-06-21 21:26:37)

Add getAppInstallations(/user/installations) method on GHMyself
# Description

Add  getAppInstallations (under the hood call on /user/installations) method in GHMyself

For more info pls take a look at https://docs.github.com/en/rest/reference/apps#list-app-installations-accessible-to-the-user-access-token



Patch:
diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java
new file mode 100644
index 000000000..0c9ec2202
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java
@@ -0,0 +1,48 @@
+package org.kohsuke.github;
+
+import java.net.MalformedURLException;
+import java.util.Iterator;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Iterable for GHAppInstallation listing.
+ */
+class GHAppInstallationsIterable extends PagedIterable<GHAppInstallation> {
+    public static final String APP_INSTALLATIONS_URL = "/user/installations";
+    private final transient GitHub root;
+    private GHAppInstallationsPage result;
+
+    public GHAppInstallationsIterable(GitHub root) {
+        this.root = root;
+    }
+
+    @Nonnull
+    @Override
+    public PagedIterator<GHAppInstallation> _iterator(int pageSize) {
+        try {
+            final GitHubRequest request = root.createRequest().withUrlPath(APP_INSTALLATIONS_URL).build();
+            return new PagedIterator<>(
+                    adapt(GitHubPageIterator.create(root.getClient(), GHAppInstallationsPage.class, request, pageSize)),
+                    null);
+        } catch (MalformedURLException e) {
+            throw new GHException("Malformed URL", e);
+        }
+    }
+
+    protected Iterator<GHAppInstallation[]> adapt(final Iterator<GHAppInstallationsPage> base) {
+        return new Iterator<GHAppInstallation[]>() {
+            public boolean hasNext() {
+                return base.hasNext();
+            }
+
+            public GHAppInstallation[] next() {
+                GHAppInstallationsPage v = base.next();
+                if (result == null) {
+                    result = v;
+                }
+                return v.getInstallations();
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java
new file mode 100644
index 000000000..14a978d52
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java
@@ -0,0 +1,17 @@
+package org.kohsuke.github;
+
+/**
+ * Represents the one page of GHAppInstallations.
+ */
+class GHAppInstallationsPage {
+    private int total_count;
+    private GHAppInstallation[] installations;
+
+    public int getTotalCount() {
+        return total_count;
+    }
+
+    GHAppInstallation[] getInstallations() {
+        return installations;
+    }
+}
diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java
index d827efb30..0cc749df3 100644
--- a/src/main/java/org/kohsuke/github/GHMyself.java
+++ b/src/main/java/org/kohsuke/github/GHMyself.java
@@ -239,4 +239,18 @@ public class GHMyself extends GHUser {
     //// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
     // root.retrieveWithAuth3()
     // }
+
+    /**
+     * Lists installations of your GitHub App that the authenticated user has explicit permission to access. You must
+     * use a user-to-server OAuth access token, created for a user who has authorized your GitHub App, to access this
+     * endpoint.
+     *
+     * @return the paged iterable
+     * @see <a href=
+     *      "https://docs.github.com/en/rest/reference/apps#list-app-installations-accessible-to-the-user-access-token">List
+     *      app installations accessible to the user access token</a>
+     */
+    public PagedIterable<GHAppInstallation> getAppInstallations() {
+        return new GHAppInstallationsIterable(root);
+    }
 }


Test patch:
diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java
index 331f12eaa..68f5379ca 100755
--- a/src/test/java/org/kohsuke/github/AppTest.java
+++ b/src/test/java/org/kohsuke/github/AppTest.java
@@ -384,6 +384,20 @@ public class AppTest extends AbstractGitHubWireMockTest {
         assertThat(prs, is(not(empty())));
     }
 
+    @Test
+    public void testGetAppInstallations() throws Exception {
+        // To generate test data user-to-server OAuth access token was used
+        // For more details pls read
+        // https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#identifying-users-on-your-site
+        final PagedIterable<GHAppInstallation> appInstallation = gitHub.getMyself().getAppInstallations();
+
+        assertThat(appInstallation.toList(), is(not(empty())));
+        assertThat(appInstallation.toList().size(), is(1));
+        final GHAppInstallation ghAppInstallation = appInstallation.toList().get(0);
+        assertThat(ghAppInstallation.getAppId(), is(122478L));
+        assertThat(ghAppInstallation.getAccount().getLogin(), is("t0m4uk1991"));
+    }
+
     @Ignore("Needs mocking check")
     @Test
     public void testRepoPermissions() throws Exception {
diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-1.json
new file mode 100644
index 000000000..6929091fa
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-1.json
@@ -0,0 +1,34 @@
+{
+  "login": "t0m4uk1991",
+  "id": 6698785,
+  "node_id": "MDQ6VXNlcjY2OTg3ODU=",
+  "avatar_url": "https://avatars.githubusercontent.com/u/6698785?v=4",
+  "gravatar_id": "",
+  "url": "https://api.github.com/users/t0m4uk1991",
+  "html_url": "https://github.com/t0m4uk1991",
+  "followers_url": "https://api.github.com/users/t0m4uk1991/followers",
+  "following_url": "https://api.github.com/users/t0m4uk1991/following{/other_user}",
+  "gists_url": "https://api.github.com/users/t0m4uk1991/gists{/gist_id}",
+  "starred_url": "https://api.github.com/users/t0m4uk1991/starred{/owner}{/repo}",
+  "subscriptions_url": "https://api.github.com/users/t0m4uk1991/subscriptions",
+  "organizations_url": "https://api.github.com/users/t0m4uk1991/orgs",
+  "repos_url": "https://api.github.com/users/t0m4uk1991/repos",
+  "events_url": "https://api.github.com/users/t0m4uk1991/events{/privacy}",
+  "received_events_url": "https://api.github.com/users/t0m4uk1991/received_events",
+  "type": "User",
+  "site_admin": false,
+  "name": null,
+  "company": null,
+  "blog": "https://t0m4uk1991.github.io",
+  "location": "Ukraine",
+  "email": "t0m4uk1991@gmail.com",
+  "hireable": true,
+  "bio": null,
+  "twitter_username": null,
+  "public_repos": 14,
+  "public_gists": 10,
+  "followers": 2,
+  "following": 2,
+  "created_at": "2014-02-16T20:43:03Z",
+  "updated_at": "2021-06-21T14:53:32Z"
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-2.json
new file mode 100644
index 000000000..6929091fa
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-2.json
@@ -0,0 +1,34 @@
+{
+  "login": "t0m4uk1991",
+  "id": 6698785,
+  "node_id": "MDQ6VXNlcjY2OTg3ODU=",
+  "avatar_url": "https://avatars.githubusercontent.com/u/6698785?v=4",
+  "gravatar_id": "",
+  "url": "https://api.github.com/users/t0m4uk1991",
+  "html_url": "https://github.com/t0m4uk1991",
+  "followers_url": "https://api.github.com/users/t0m4uk1991/followers",
+  "following_url": "https://api.github.com/users/t0m4uk1991/following{/other_user}",
+  "gists_url": "https://api.github.com/users/t0m4uk1991/gists{/gist_id}",
+  "starred_url": "https://api.github.com/users/t0m4uk1991/starred{/owner}{/repo}",
+  "subscriptions_url": "https://api.github.com/users/t0m4uk1991/subscriptions",
+  "organizations_url": "https://api.github.com/users/t0m4uk1991/orgs",
+  "repos_url": "https://api.github.com/users/t0m4uk1991/repos",
+  "events_url": "https://api.github.com/users/t0m4uk1991/events{/privacy}",
+  "received_events_url": "https://api.github.com/users/t0m4uk1991/received_events",
+  "type": "User",
+  "site_admin": false,
+  "name": null,
+  "company": null,
+  "blog": "https://t0m4uk1991.github.io",
+  "location": "Ukraine",
+  "email": "t0m4uk1991@gmail.com",
+  "hireable": true,
+  "bio": null,
+  "twitter_username": null,
+  "public_repos": 14,
+  "public_gists": 10,
+  "followers": 2,
+  "following": 2,
+  "created_at": "2014-02-16T20:43:03Z",
+  "updated_at": "2021-06-21T14:53:32Z"
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user_installations-3.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user_installations-3.json
new file mode 100644
index 000000000..908e9ca83
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user_installations-3.json
@@ -0,0 +1,48 @@
+{
+  "total_count": 1,
+  "installations": [
+    {
+      "id": 17761138,
+      "account": {
+        "login": "t0m4uk1991",
+        "id": 6698785,
+        "node_id": "MDQ6VXNlcjY2OTg3ODU=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6698785?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/t0m4uk1991",
+        "html_url": "https://github.com/t0m4uk1991",
+        "followers_url": "https://api.github.com/users/t0m4uk1991/followers",
+        "following_url": "https://api.github.com/users/t0m4uk1991/following{/other_user}",
+        "gists_url": "https://api.github.com/users/t0m4uk1991/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/t0m4uk1991/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/t0m4uk1991/subscriptions",
+        "organizations_url": "https://api.github.com/users/t0m4uk1991/orgs",
+        "repos_url": "https://api.github.com/users/t0m4uk1991/repos",
+        "events_url": "https://api.github.com/users/t0m4uk1991/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/t0m4uk1991/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "repository_selection": "selected",
+      "access_tokens_url": "https://api.github.com/app/installations/17761138/access_tokens",
+      "repositories_url": "https://api.github.com/installation/repositories",
+      "html_url": "https://github.com/settings/installations/17761138",
+      "app_id": 122478,
+      "app_slug": "hub4j-github-api-test",
+      "target_id": 6698785,
+      "target_type": "User",
+      "permissions": {
+        "metadata": "read",
+        "administration": "read"
+      },
+      "events": [],
+      "created_at": "2021-06-21T17:39:33.000+03:00",
+      "updated_at": "2021-06-21T17:39:33.000+03:00",
+      "single_file_name": null,
+      "has_multiple_single_files": false,
+      "single_file_paths": [],
+      "suspended_by": null,
+      "suspended_at": null
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-1.json
new file mode 100644
index 000000000..aef8226f9
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-1.json
@@ -0,0 +1,51 @@
+{
+  "id": "04c78e0b-2ca2-4f06-b281-956eaba032bd",
+  "name": "user",
+  "request": {
+    "url": "/user",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "user-1.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Mon, 21 Jun 2021 20:51:32 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Cache-Control": "private, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept, Authorization, Cookie, X-GitHub-OTP",
+        "Accept-Encoding, Accept, X-Requested-With"
+      ],
+      "ETag": "W/\"e9e4780f1b19e9323e73a0f5690bf7b59ee956f536ee5a303eb8657423123051\"",
+      "Last-Modified": "Mon, 21 Jun 2021 14:53:32 GMT",
+      "X-OAuth-Scopes": "",
+      "X-Accepted-OAuth-Scopes": "",
+      "x-oauth-client-id": "Iv1.d1d44ef7894694c0",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4999",
+      "X-RateLimit-Reset": "1624312292",
+      "X-RateLimit-Used": "1",
+      "X-RateLimit-Resource": "core",
+      "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
+      "X-Frame-Options": "deny",
+      "X-Content-Type-Options": "nosniff",
+      "X-XSS-Protection": "0",
+      "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+      "Content-Security-Policy": "default-src 'none'",
+      "X-GitHub-Request-Id": "2B69:2FF5:2E0782:302E02:60D0FBD4"
+    }
+  },
+  "uuid": "04c78e0b-2ca2-4f06-b281-956eaba032bd",
+  "persistent": true,
+  "scenarioName": "scenario-1-user",
+  "requiredScenarioState": "Started",
+  "newScenarioState": "scenario-1-user-2",
+  "insertionIndex": 1
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-2.json
new file mode 100644
index 000000000..a98a10990
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-2.json
@@ -0,0 +1,50 @@
+{
+  "id": "c209281c-5bd1-405a-9dc7-34227ec4d5ae",
+  "name": "user",
+  "request": {
+    "url": "/user",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "user-2.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Mon, 21 Jun 2021 20:51:33 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Cache-Control": "private, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept, Authorization, Cookie, X-GitHub-OTP",
+        "Accept-Encoding, Accept, X-Requested-With"
+      ],
+      "ETag": "W/\"e9e4780f1b19e9323e73a0f5690bf7b59ee956f536ee5a303eb8657423123051\"",
+      "Last-Modified": "Mon, 21 Jun 2021 14:53:32 GMT",
+      "X-OAuth-Scopes": "",
+      "X-Accepted-OAuth-Scopes": "",
+      "x-oauth-client-id": "Iv1.d1d44ef7894694c0",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4997",
+      "X-RateLimit-Reset": "1624312292",
+      "X-RateLimit-Used": "3",
+      "X-RateLimit-Resource": "core",
+      "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
+      "X-Frame-Options": "deny",
+      "X-Content-Type-Options": "nosniff",
+      "X-XSS-Protection": "0",
+      "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+      "Content-Security-Policy": "default-src 'none'",
+      "X-GitHub-Request-Id": "04D3:4B1B:1F3F8F:200584:60D0FBD4"
+    }
+  },
+  "uuid": "c209281c-5bd1-405a-9dc7-34227ec4d5ae",
+  "persistent": true,
+  "scenarioName": "scenario-1-user",
+  "requiredScenarioState": "scenario-1-user-2",
+  "insertionIndex": 2
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user_installations-3.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user_installations-3.json
new file mode 100644
index 000000000..29af79a9d
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user_installations-3.json
@@ -0,0 +1,47 @@
+{
+  "id": "179e0b84-8037-4d0b-9833-c8ef1b226592",
+  "name": "user_installations",
+  "request": {
+    "url": "/user/installations",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "user_installations-3.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Mon, 21 Jun 2021 20:51:33 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Cache-Control": "private, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept, Authorization, Cookie, X-GitHub-OTP",
+        "Accept-Encoding, Accept, X-Requested-With"
+      ],
+      "ETag": "W/\"05762b67c1a69e46ee710171fdb32872b44904cc7c68a7617c134a6c5eee2b13\"",
+      "X-OAuth-Scopes": "",
+      "X-Accepted-OAuth-Scopes": "",
+      "x-oauth-client-id": "Iv1.d1d44ef7894694c0",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4996",
+      "X-RateLimit-Reset": "1624312292",
+      "X-RateLimit-Used": "4",
+      "X-RateLimit-Resource": "core",
+      "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
+      "X-Frame-Options": "deny",
+      "X-Content-Type-Options": "nosniff",
+      "X-XSS-Protection": "0",
+      "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+      "Content-Security-Policy": "default-src 'none'",
+      "X-GitHub-Request-Id": "0A50:20B4:B38D2B:B6B016:60D0FBD5"
+    }
+  },
+  "uuid": "179e0b84-8037-4d0b-9833-c8ef1b226592",
+  "persistent": true,
+  "insertionIndex": 3
+}
\ No newline at end of file