Repository: hub4j/github-api
Pull request #1630 (2023-03-19 13:12:28)

Enable fetch plan for current app and account
# Description

Fixes https://github.com/hub4j/github-api/issues/1613

This enables accessing the plan with current GithubApp on current GHInstallation.

# Before submitting a PR:

- [ ] Changes must not break binary backwards compatibility. If you are unclear on how to make the change you think is needed while maintaining backward compatibility, [CONTRIBUTING.md](CONTRIBUTING.md) for details.
- [ ] Add JavaDocs and other comments as appropriate. Consider including links in comments to relevant documentation on https://docs.github.com/en/rest .  
- [ ] Add tests that cover any added or changed code. This generally requires capturing snapshot test data. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
- [ ] Run `mvn -D enable-ci clean install site` locally. If this command doesn't succeed, your change will not pass CI.
- [ ] Push your changes to a branch other than `main`. You will create your PR from that branch.

# When creating a PR: 

- [ ] Fill in the "Description" above with clear summary of the changes. This includes:
  - [ ] If this PR fixes one or more issues, include "Fixes #<issue number>" lines for each issue. 
  - [ ] Provide links to relevant documentation on https://docs.github.com/en/rest where possible.
- [ ] All lines of new code should be covered by tests as reported by code coverage. Any lines that are not covered must have PR comments explaining why they cannot be covered. For example, "Reaching this particular exception is hard and is not a particular common scenario."
- [ ] Enable "Allow edits from maintainers".



Patch:
diff --git a/src/main/java/org/kohsuke/github/GHAppInstallation.java b/src/main/java/org/kohsuke/github/GHAppInstallation.java
index 212a19794..cf5bda32b 100644
--- a/src/main/java/org/kohsuke/github/GHAppInstallation.java
+++ b/src/main/java/org/kohsuke/github/GHAppInstallation.java
@@ -361,4 +361,25 @@ public class GHAppInstallation extends GHObject {
     public GHAppCreateTokenBuilder createToken() {
         return new GHAppCreateTokenBuilder(root(), String.format("/app/installations/%d/access_tokens", getId()));
     }
+
+    /**
+     * Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub
+     * App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will
+     * also see the upcoming pending change.
+     *
+     * <p>
+     * GitHub Apps must use a JWT to access this endpoint.
+     * <p>
+     * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint.
+     *
+     * @return a GHMarketplaceAccountPlan instance
+     * @throws IOException
+     *             it may throw an {@link IOException}
+     * @see <a href=
+     *      "https://docs.github.com/en/rest/apps/marketplace?apiVersion=2022-11-28#get-a-subscription-plan-for-an-account">Get
+     *      a subscription plan for an account</a>
+     */
+    public GHMarketplaceAccountPlan getMarketplaceAccount() throws IOException {
+        return new GHMarketplacePlanForAccountBuilder(root(), account.getId()).createRequest();
+    }
 }
diff --git a/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java b/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java
index ddc28ad1d..19a71b68f 100644
--- a/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java
+++ b/src/main/java/org/kohsuke/github/GHMarketplaceAccount.java
@@ -1,5 +1,6 @@
 package org.kohsuke.github;
 
+import java.io.IOException;
 import java.net.URL;
 
 // TODO: Auto-generated Javadoc
@@ -72,4 +73,25 @@ public class GHMarketplaceAccount extends GitHubInteractiveObject {
         return type;
     }
 
+    /**
+     * Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub
+     * App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will
+     * also see the upcoming pending change.
+     *
+     * <p>
+     * GitHub Apps must use a JWT to access this endpoint.
+     * <p>
+     * OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint.
+     *
+     * @return a GHMarketplaceListAccountBuilder instance
+     * @throws IOException
+     *             in case of {@link IOException}
+     * @see <a href=
+     *      "https://docs.github.com/en/rest/apps/marketplace?apiVersion=2022-11-28#get-a-subscription-plan-for-an-account">Get
+     *      a subscription plan for an account</a>
+     */
+    public GHMarketplaceAccountPlan getPlan() throws IOException {
+        return new GHMarketplacePlanForAccountBuilder(root(), this.id).createRequest();
+    }
+
 }
diff --git a/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java b/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java
new file mode 100644
index 000000000..e5167f31a
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHMarketplacePlanForAccountBuilder.java
@@ -0,0 +1,45 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+
+// TODO: Auto-generated Javadoc
+/**
+ * Returns the plan associated with current account.
+ *
+ * @author Benoit Lacelle
+ * @see GHMarketplacePlan#listAccounts()
+ * @see GitHub#listMarketplacePlans()
+ */
+public class GHMarketplacePlanForAccountBuilder extends GitHubInteractiveObject {
+    private final Requester builder;
+    private final long accountId;
+
+    /**
+     * Instantiates a new GH marketplace list account builder.
+     *
+     * @param root
+     *            the root
+     * @param accountId
+     *            the account id
+     */
+    GHMarketplacePlanForAccountBuilder(GitHub root, long accountId) {
+        super(root);
+        this.builder = root.createRequest();
+        this.accountId = accountId;
+    }
+
+    /**
+     * Fetch the plan associated with the account specified on construction.
+     * <p>
+     * GitHub Apps must use a JWT to access this endpoint.
+     *
+     * @return a GHMarketplaceAccountPlan
+     * @throws IOException
+     *             on error
+     */
+    public GHMarketplaceAccountPlan createRequest() throws IOException {
+        return builder.withUrlPath(String.format("/marketplace_listing/accounts/%d", this.accountId))
+                .fetch(GHMarketplaceAccountPlan.class);
+    }
+
+}
diff --git a/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java
index 11cda833b..85ed1f69e 100644
--- a/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java
+++ b/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java
@@ -3,6 +3,7 @@ package org.kohsuke.github.extras.authorization;
 import io.jsonwebtoken.JwtBuilder;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.jackson.io.JacksonSerializer;
 import org.kohsuke.github.authorization.AuthorizationProvider;
 
 import java.io.File;
@@ -181,7 +182,7 @@ public class JWTTokenProvider implements AuthorizationProvider {
         validUntil = expiration.minus(Duration.ofMinutes(2));
 
         // Builds the JWT and serializes it to a compact, URL-safe string
-        return builder.compact();
+        return builder.serializeToJsonWith(new JacksonSerializer<>()).compact();
     }
 
     Instant getIssuedAt(Instant now) {


Test patch:
diff --git a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java
index 40b9b0bdb..da7d7f2f9 100644
--- a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java
+++ b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java
@@ -1,5 +1,6 @@
 package org.kohsuke.github;
 
+import com.google.common.collect.ImmutableSet;
 import io.jsonwebtoken.Jwts;
 import org.apache.commons.io.IOUtils;
 import org.kohsuke.github.authorization.AuthorizationProvider;
@@ -9,6 +10,7 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
@@ -17,6 +19,7 @@ import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.Base64;
 import java.util.Date;
+import java.util.List;
 
 // TODO: Auto-generated Javadoc
 /**
@@ -24,6 +27,11 @@ import java.util.Date;
  */
 public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
 
+    private static String ENV_GITHUB_APP_ID = "GITHUB_APP_ID";
+    private static String ENV_GITHUB_APP_JWK_PATH = "GITHUB_APP_JWK_PATH";
+    private static String ENV_GITHUB_APP_ORG = "GITHUB_APP_ORG";
+    private static String ENV_GITHUB_APP_REPO = "GITHUB_APP_REPO";
+
     private static String TEST_APP_ID_1 = "82994";
     private static String TEST_APP_ID_2 = "83009";
     private static String TEST_APP_ID_3 = "89368";
@@ -44,16 +52,23 @@ public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
      * Instantiates a new abstract GH app installation test.
      */
     protected AbstractGHAppInstallationTest() {
+        String appId = System.getenv(ENV_GITHUB_APP_ID);
+        String appJwkPath = System.getenv(ENV_GITHUB_APP_JWK_PATH);
         try {
-            jwtProvider1 = new JWTTokenProvider(TEST_APP_ID_1,
-                    new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_1).getFile()));
-            jwtProvider2 = new JWTTokenProvider(TEST_APP_ID_2,
-                    new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_2).getFile()).toPath());
-            jwtProvider3 = new JWTTokenProvider(TEST_APP_ID_3,
-                    new String(
-                            Files.readAllBytes(
-                                    new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_3).getFile()).toPath()),
-                            StandardCharsets.UTF_8));
+            if (appId != null && appJwkPath != null) {
+                jwtProvider1 = new JWTTokenProvider(appId, Paths.get(appJwkPath));
+                jwtProvider2 = jwtProvider1;
+                jwtProvider3 = jwtProvider1;
+            } else {
+                jwtProvider1 = new JWTTokenProvider(TEST_APP_ID_1,
+                        new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_1).getFile()));
+                jwtProvider2 = new JWTTokenProvider(TEST_APP_ID_2,
+                        new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_2).getFile()).toPath());
+                jwtProvider3 = new JWTTokenProvider(TEST_APP_ID_3,
+                        new String(Files.readAllBytes(
+                                new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_3).getFile()).toPath()),
+                                StandardCharsets.UTF_8));
+            }
         } catch (GeneralSecurityException | IOException e) {
             throw new RuntimeException("These should never fail", e);
         }
@@ -89,17 +104,28 @@ public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
      *             Signals that an I/O exception has occurred.
      */
     protected GHAppInstallation getAppInstallationWithToken(String jwtToken) throws IOException {
+        if (jwtToken.startsWith("Bearer ")) {
+            jwtToken = jwtToken.substring("Bearer ".length());
+        }
+
         GitHub gitHub = getGitHubBuilder().withJwtToken(jwtToken)
                 .withEndpoint(mockGitHub.apiServer().baseUrl())
                 .build();
 
-        GHAppInstallation appInstallation = gitHub.getApp()
-                .listInstallations()
-                .toList()
-                .stream()
-                .filter(it -> it.getAccount().login.equals("hub4j-test-org"))
-                .findFirst()
-                .get();
+        GHApp app = gitHub.getApp();
+
+        GHAppInstallation appInstallation;
+        if (ImmutableSet.of(TEST_APP_ID_1, TEST_APP_ID_2, TEST_APP_ID_3).contains(Long.toString(app.getId()))) {
+            List<GHAppInstallation> installations = app.listInstallations().toList();
+            appInstallation = installations.stream()
+                    .filter(it -> it.getAccount().login.equals("hub4j-test-org"))
+                    .findFirst()
+                    .get();
+        } else {
+            // We may be processing a custom JWK, for a custom GHApp: fetch a relevant repository dynamically
+            appInstallation = app.getInstallationByRepository(System.getenv(ENV_GITHUB_APP_ORG),
+                    System.getenv(ENV_GITHUB_APP_REPO));
+        }
 
         // TODO: this is odd
         // appInstallation
diff --git a/src/test/java/org/kohsuke/github/GHAppInstallationTest.java b/src/test/java/org/kohsuke/github/GHAppInstallationTest.java
index a54535eb0..1a380a973 100644
--- a/src/test/java/org/kohsuke/github/GHAppInstallationTest.java
+++ b/src/test/java/org/kohsuke/github/GHAppInstallationTest.java
@@ -44,4 +44,21 @@ public class GHAppInstallationTest extends AbstractGHAppInstallationTest {
                 appInstallation.listRepositories().toList().isEmpty());
     }
 
+    /**
+     * Test list repositories no permissions.
+     *
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    @Test
+    public void testGetMarketplaceAccount() throws IOException {
+        GHAppInstallation appInstallation = getAppInstallationWithToken(jwtProvider3.getEncodedAuthorization());
+
+        GHMarketplaceAccountPlan marketplaceAccount = appInstallation.getMarketplaceAccount();
+        GHMarketplacePlanTest.testMarketplaceAccount(marketplaceAccount);
+
+        GHMarketplaceAccountPlan plan = marketplaceAccount.getPlan();
+        assertThat(plan.getType(), equalTo(GHMarketplaceAccountType.ORGANIZATION));
+    }
+
 }
diff --git a/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java b/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java
index 7ae705a99..90e697ce3 100644
--- a/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java
+++ b/src/test/java/org/kohsuke/github/GHMarketplacePlanTest.java
@@ -1,8 +1,10 @@
 package org.kohsuke.github;
 
+import org.hamcrest.Matchers;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 
 import static org.hamcrest.Matchers.*;
@@ -40,7 +42,7 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
     public void listMarketplacePlans() throws IOException {
         List<GHMarketplacePlan> plans = gitHub.listMarketplacePlans().toList();
         assertThat(plans.size(), equalTo(3));
-        plans.forEach(this::testMarketplacePlan);
+        plans.forEach(GHMarketplacePlanTest::testMarketplacePlan);
     }
 
     /**
@@ -55,7 +57,7 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
         assertThat(plans.size(), equalTo(3));
         List<GHMarketplaceAccountPlan> marketplaceUsers = plans.get(0).listAccounts().createRequest().toList();
         assertThat(marketplaceUsers.size(), equalTo(2));
-        marketplaceUsers.forEach(this::testMarketplaceAccount);
+        marketplaceUsers.forEach(GHMarketplacePlanTest::testMarketplaceAccount);
     }
 
     /**
@@ -75,7 +77,7 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
                     .createRequest()
                     .toList();
             assertThat(marketplaceUsers.size(), equalTo(2));
-            marketplaceUsers.forEach(this::testMarketplaceAccount);
+            marketplaceUsers.forEach(GHMarketplacePlanTest::testMarketplaceAccount);
         }
 
     }
@@ -98,12 +100,12 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
                     .createRequest()
                     .toList();
             assertThat(marketplaceUsers.size(), equalTo(2));
-            marketplaceUsers.forEach(this::testMarketplaceAccount);
+            marketplaceUsers.forEach(GHMarketplacePlanTest::testMarketplaceAccount);
         }
 
     }
 
-    private void testMarketplacePlan(GHMarketplacePlan plan) {
+    static void testMarketplacePlan(GHMarketplacePlan plan) {
         // Non-nullable fields
         assertThat(plan.getUrl(), notNullValue());
         assertThat(plan.getAccountsUrl(), notNullValue());
@@ -118,10 +120,10 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
         assertThat(plan.getMonthlyPriceInCents(), greaterThanOrEqualTo(0L));
 
         // list
-        assertThat(plan.getBullets().size(), equalTo(2));
+        assertThat(plan.getBullets().size(), Matchers.in(Arrays.asList(2, 3)));
     }
 
-    private void testMarketplaceAccount(GHMarketplaceAccountPlan account) {
+    static void testMarketplaceAccount(GHMarketplaceAccountPlan account) {
         // Non-nullable fields
         assertThat(account.getLogin(), notNullValue());
         assertThat(account.getUrl(), notNullValue());
@@ -146,7 +148,7 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
             testMarketplacePendingChange(account.getMarketplacePendingChange());
     }
 
-    private void testMarketplacePurchase(GHMarketplacePurchase marketplacePurchase) {
+    static void testMarketplacePurchase(GHMarketplacePurchase marketplacePurchase) {
         // Non-nullable fields
         assertThat(marketplacePurchase.getBillingCycle(), notNullValue());
         assertThat(marketplacePurchase.getNextBillingDate(), notNullValue());
@@ -165,11 +167,11 @@ public class GHMarketplacePlanTest extends AbstractGitHubWireMockTest {
         if (marketplacePurchase.getPlan().getPriceModel() == GHMarketplacePriceModel.PER_UNIT)
             assertThat(marketplacePurchase.getUnitCount(), notNullValue());
         else
-            assertThat(marketplacePurchase.getUnitCount(), nullValue());
+            assertThat(marketplacePurchase.getUnitCount(), Matchers.anyOf(nullValue(), is(1L)));
 
     }
 
-    private void testMarketplacePendingChange(GHMarketplacePendingChange marketplacePendingChange) {
+    static void testMarketplacePendingChange(GHMarketplacePendingChange marketplacePendingChange) {
         // Non-nullable fields
         assertThat(marketplacePendingChange.getEffectiveDate(), notNullValue());
         testMarketplacePlan(marketplacePendingChange.getPlan());
diff --git a/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/app-1.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/app-1.json
new file mode 100644
index 000000000..89a67cab2
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/app-1.json
@@ -0,0 +1,42 @@
+{
+  "id": 83009,
+  "slug": "cleanthat",
+  "node_id": "MDM6QXBwNjU1NTA=",
+  "owner": {
+    "login": "solven-eu",
+    "id": 34552197,
+    "node_id": "MDEyOk9yZ2FuaXphdGlvbjM0NTUyMTk3",
+    "avatar_url": "https://avatars.githubusercontent.com/u/34552197?v=4",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/solven-eu",
+    "html_url": "https://github.com/solven-eu",
+    "followers_url": "https://api.github.com/users/solven-eu/followers",
+    "following_url": "https://api.github.com/users/solven-eu/following{/other_user}",
+    "gists_url": "https://api.github.com/users/solven-eu/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/solven-eu/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/solven-eu/subscriptions",
+    "organizations_url": "https://api.github.com/users/solven-eu/orgs",
+    "repos_url": "https://api.github.com/users/solven-eu/repos",
+    "events_url": "https://api.github.com/users/solven-eu/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/solven-eu/received_events",
+    "type": "Organization",
+    "site_admin": false
+  },
+  "name": "CleanThat",
+  "description": "Cleanthat cleans branches automatically to fix/improve your code.\r\n\r\nFeatures :\r\n- Fix branches a pull_requests head\r\n- Open pull_request to fix protected branches\r\n- Format `.md`, `.java`, `.scala`, `.json`, `.yaml` with the help of [Spotless](https://github.com/diffplug/spotless)\r\n- Refactor `.java` files to improve code-style, security and stability",
+  "external_url": "https://github.com/solven-eu/cleanthat",
+  "html_url": "https://github.com/apps/cleanthat",
+  "created_at": "2020-05-19T13:45:43Z",
+  "updated_at": "2023-01-27T06:10:21Z",
+  "permissions": {
+    "checks": "write",
+    "contents": "write",
+    "metadata": "read",
+    "pull_requests": "write"
+  },
+  "events": [
+    "pull_request",
+    "push"
+  ],
+  "installations_count": 280
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/app_installations-2.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/app_installations-2.json
new file mode 100644
index 000000000..a0721a8ca
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/app_installations-2.json
@@ -0,0 +1,42 @@
+[
+  {
+    "id": 12131496,
+    "account": {
+      "login": "hub4j-test-org",
+      "id": 7544739,
+      "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/hub4j-test-org",
+      "html_url": "https://github.com/hub4j-test-org",
+      "followers_url": "https://api.github.com/users/hub4j-test-org/followers",
+      "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}",
+      "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions",
+      "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs",
+      "repos_url": "https://api.github.com/users/hub4j-test-org/repos",
+      "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events",
+      "type": "Organization",
+      "site_admin": false
+    },
+    "repository_selection": "selected",
+    "access_tokens_url": "https://api.github.com/app/installations/12131496/access_tokens",
+    "repositories_url": "https://api.github.com/installation/repositories",
+    "html_url": "https://github.com/organizations/hub4j-test-org/settings/installations/12131496",
+    "app_id": 83009,
+    "app_slug": "ghapi-test-app-2",
+    "target_id": 7544739,
+    "target_type": "Organization",
+    "permissions": {},
+    "events": [],
+    "created_at": "2020-09-30T15:05:32.000Z",
+    "updated_at": "2020-09-30T15:05:32.000Z",
+    "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/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/marketplace_listing_accounts_7544739-3.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/marketplace_listing_accounts_7544739-3.json
new file mode 100644
index 000000000..e933eaa81
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/__files/marketplace_listing_accounts_7544739-3.json
@@ -0,0 +1,36 @@
+{
+  "url": "https://api.github.com/orgs/solven-eu",
+  "type": "Organization",
+  "id": 7544739,
+  "login": "solven-eu",
+  "marketplace_pending_change": null,
+  "marketplace_purchase": {
+    "billing_cycle": "monthly",
+    "unit_count": 1,
+    "on_free_trial": false,
+    "free_trial_ends_on": null,
+    "is_installed": true,
+    "updated_at": "2023-03-02T06:05:31Z",
+    "next_billing_date": "2023-03-28T00:00:00Z",
+    "plan": {
+      "url": "https://api.github.com/marketplace_listing/plans/8366",
+      "accounts_url": "https://api.github.com/marketplace_listing/plans/8366/accounts",
+      "id": 8366,
+      "number": 8,
+      "name": "Organization Private Repositories",
+      "description": "Enable CleanThat for any of your organization repositories",
+      "monthly_price_in_cents": 5000,
+      "yearly_price_in_cents": 50000,
+      "price_model": "FLAT_RATE",
+      "has_free_trial": false,
+      "unit_name": null,
+      "state": "published",
+      "bullets": [
+        "Branches are automatically cleaned",
+        "Main branchrs are cleaned through PullRequests",
+        "Java, Kotlin, Json, Yaml, Markdown, etc"
+      ]
+    }
+  },
+  "organization_billing_email": "accounting@toto.com"
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app-1.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app-1.json
new file mode 100644
index 000000000..fbd7bbd39
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app-1.json
@@ -0,0 +1,42 @@
+{
+  "id": "144fdb7f-667e-4cf4-bd37-67ed11bdc421",
+  "name": "app",
+  "request": {
+    "url": "/app",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "application/vnd.github.machine-man-preview+json"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "app-1.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Sun, 19 Mar 2023 13:02:50 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Cache-Control": "public, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept",
+        "Accept-Encoding, Accept, X-Requested-With"
+      ],
+      "ETag": "W/\"00fa67d861eb73a934cd9229b76c2dc7c2c235babf8d281e2dd4a1e31ca3b930\"",
+      "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json",
+      "x-github-api-version-selected": "2022-11-28",
+      "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+      "Access-Control-Allow-Origin": "*",
+      "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": "C09A:5A83:172C70C:179D1FD:641707FA"
+    }
+  },
+  "uuid": "144fdb7f-667e-4cf4-bd37-67ed11bdc421",
+  "persistent": true,
+  "insertionIndex": 1
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app_installations-2.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app_installations-2.json
new file mode 100644
index 000000000..fd83a4890
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app_installations-2.json
@@ -0,0 +1,41 @@
+{
+  "id": "45ac2593-8123-49ae-ad1a-ded446491b14",
+  "name": "app_installations",
+  "request": {
+    "url": "/app/installations",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "application/vnd.github.machine-man-preview+json"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "app_installations-2.json",
+    "headers": {
+      "Date": "Thu, 05 Nov 2020 20:42:31 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Server": "GitHub.com",
+      "Status": "200 OK",
+      "Cache-Control": "public, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept",
+        "Accept-Encoding, Accept, X-Requested-With",
+        "Accept-Encoding"
+      ],
+      "ETag": "W/\"60d3ec5c9014799f5e12b88e16e771a386b905ad8d41cd18aed34e58b11c58d4\"",
+      "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json",
+      "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
+      "X-Frame-Options": "deny",
+      "X-Content-Type-Options": "nosniff",
+      "X-XSS-Protection": "1; mode=block",
+      "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+      "Content-Security-Policy": "default-src 'none'",
+      "X-GitHub-Request-Id": "9294:AE05:BDAC831:DB35870:5FA463B7"
+    }
+  },
+  "uuid": "45ac2593-8123-49ae-ad1a-ded446491b14",
+  "persistent": true,
+  "insertionIndex": 2
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app_installations_12131496_access_tokens-3.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app_installations_12131496_access_tokens-3.json
new file mode 100644
index 000000000..8a2d29436
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/app_installations_12131496_access_tokens-3.json
@@ -0,0 +1,48 @@
+{
+  "id": "b8c586bc-0b07-47e8-806a-76e39d058bf5",
+  "name": "app_installations_12131496_access_tokens",
+  "request": {
+    "url": "/app/installations/12131496/access_tokens",
+    "method": "POST",
+    "headers": {
+      "Accept": {
+        "equalTo": "application/vnd.github.machine-man-preview+json"
+      }
+    },
+    "bodyPatterns": [
+      {
+        "equalToJson": "{}",
+        "ignoreArrayOrder": true,
+        "ignoreExtraElements": false
+      }
+    ]
+  },
+  "response": {
+    "status": 201,
+    "body": "{\"token\":\"v1.d5741167e3837f3adda54581ba91c37183c7dd94\",\"expires_at\":\"2020-11-05T21:42:32Z\",\"permissions\":{},\"repository_selection\":\"selected\"}",
+    "headers": {
+      "Date": "Thu, 05 Nov 2020 20:42:32 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Server": "GitHub.com",
+      "Status": "201 Created",
+      "Cache-Control": "public, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept",
+        "Accept-Encoding, Accept, X-Requested-With",
+        "Accept-Encoding"
+      ],
+      "ETag": "\"1fef2b60c4475562d6b9ad2dbc3631503de22b38aac485c1d3f72aa6c8fed608\"",
+      "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json",
+      "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
+      "X-Frame-Options": "deny",
+      "X-Content-Type-Options": "nosniff",
+      "X-XSS-Protection": "1; mode=block",
+      "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
+      "Content-Security-Policy": "default-src 'none'",
+      "X-GitHub-Request-Id": "9294:AE05:BDAC89E:DB35960:5FA463B7"
+    }
+  },
+  "uuid": "b8c586bc-0b07-47e8-806a-76e39d058bf5",
+  "persistent": true,
+  "insertionIndex": 3
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/marketplace_listing_accounts_7544739-3.json b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/marketplace_listing_accounts_7544739-3.json
new file mode 100644
index 000000000..f0eff5801
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHAppInstallationTest/wiremock/testGetMarketplaceAccount/mappings/marketplace_listing_accounts_7544739-3.json
@@ -0,0 +1,47 @@
+{
+  "id": "eae10c4e-7d76-4db7-a741-f0d2520c396d",
+  "name": "marketplace_listing_accounts_7544739",
+  "request": {
+    "url": "/marketplace_listing/accounts/7544739",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "application/vnd.github.v3+json"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "marketplace_listing_accounts_7544739-3.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Sun, 19 Mar 2023 13:02:52 GMT",
+      "Content-Type": "application/json; charset=utf-8",
+      "Cache-Control": "public, max-age=60, s-maxage=60",
+      "Vary": [
+        "Accept",
+        "Accept-Encoding, Accept, X-Requested-With"
+      ],
+      "ETag": "W/\"643755aa11e09fed050b7067db25efbebfe738f9fff6104c15a75fb3754632d4\"",
+      "X-GitHub-Media-Type": "github.v3; format=json",
+      "x-github-api-version-selected": "2022-11-28",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4932",
+      "X-RateLimit-Reset": "1679232791",
+      "X-RateLimit-Used": "68",
+      "X-RateLimit-Resource": "core",
+      "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset",
+      "Access-Control-Allow-Origin": "*",
+      "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": "C09D:5A83:172CCBB:179D7CE:641707FC"
+    }
+  },
+  "uuid": "eae10c4e-7d76-4db7-a741-f0d2520c396d",
+  "persistent": true,
+  "insertionIndex": 3
+}
\ No newline at end of file