Repository: hub4j/github-api
Pull request #1139 (2021-05-09 02:27:07)

(feat) Add method to check if Organization has projects enabled
[#1080]

# Description
Adding new method `hasProjects` to `GHOrganization` to allow for checking if a GitHub Org has projects enabled


akashRindhe: I'd like to get permission to the `hub4j-test-org` in order to write a test for this new method.
codecov[bot]: # [Codecov](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#1139](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (277da96) into [main](https://codecov.io/gh/hub4j/github-api/commit/f5310965dc2ff709bb3474573ea880a4402def1c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (f531096) will **decrease** coverage by `0.01%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/hub4j/github-api/pull/1139/graphs/tree.svg?width=650&height=150&src=pr&token=j1jQqydZLJ&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@             Coverage Diff              @@
##               main    #1139      +/-   ##
============================================
- Coverage     73.26%   73.25%   -0.02%     
  Complexity     1824     1824              
============================================
  Files           185      185              
  Lines          6116     6117       +1     
  Branches        365      365              
============================================
  Hits           4481     4481              
- Misses         1416     1417       +1     
  Partials        219      219              
```


| [Impacted Files](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [...c/main/java/org/kohsuke/github/GHOrganization.java](https://codecov.io/gh/hub4j/github-api/pull/1139/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL21haW4vamF2YS9vcmcva29oc3VrZS9naXRodWIvR0hPcmdhbml6YXRpb24uamF2YQ==) | `62.79% <0.00%> (-0.50%)` | `32.00 <0.00> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [f531096...277da96](https://codecov.io/gh/hub4j/github-api/pull/1139?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java
index 15f13839c..629145417 100644
--- a/src/main/java/org/kohsuke/github/GHOrganization.java
+++ b/src/main/java/org/kohsuke/github/GHOrganization.java
@@ -18,6 +18,9 @@ import static org.kohsuke.github.internal.Previews.INERTIA;
  * @author Kohsuke Kawaguchi
  */
 public class GHOrganization extends GHPerson {
+
+    private boolean has_organization_projects;
+
     GHOrganization wrapUp(GitHub root) {
         return (GHOrganization) super.wrapUp(root);
     }
@@ -367,6 +370,35 @@ public class GHOrganization extends GHPerson {
         root.createRequest().method("DELETE").withUrlPath("/orgs/" + login + "/public_members/" + u.getLogin()).send();
     }
 
+    /**
+     * Are projects enabled for organization boolean.
+     *
+     * @return the boolean
+     */
+    public boolean areOrganizationProjectsEnabled() {
+        return has_organization_projects;
+    }
+
+    /**
+     * Sets organization projects enabled status boolean
+     *
+     * @param newStatus
+     *            enable status
+     * @throws IOException
+     *             the io exception
+     */
+    public void enableOrganizationProjects(boolean newStatus) throws IOException {
+        edit("has_organization_projects", newStatus);
+    }
+
+    private void edit(String key, Object value) throws IOException {
+        root.createRequest()
+                .withUrlPath(String.format("/orgs/%s", login))
+                .method("PATCH")
+                .with(key, value)
+                .fetchInto(this);
+    }
+
     /**
      * Returns the projects for this organization.
      *
diff --git a/src/main/java/org/kohsuke/github/GHRateLimit.java b/src/main/java/org/kohsuke/github/GHRateLimit.java
index 8a1d90a28..0df3ce320 100644
--- a/src/main/java/org/kohsuke/github/GHRateLimit.java
+++ b/src/main/java/org/kohsuke/github/GHRateLimit.java
@@ -12,7 +12,6 @@ import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.util.Date;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Logger;
 
 import javax.annotation.CheckForNull;
@@ -345,7 +344,7 @@ public class GHRateLimit {
         private static final UnknownLimitRecord DEFAULT = new UnknownLimitRecord(Long.MIN_VALUE);
 
         // The starting current UnknownLimitRecord is an expired record.
-        private static final AtomicReference<UnknownLimitRecord> current = new AtomicReference<>(DEFAULT);
+        private static UnknownLimitRecord current = DEFAULT;
 
         /**
          * Create a new unknown record that resets at the specified time.
@@ -357,20 +356,18 @@ public class GHRateLimit {
             super(unknownLimit, unknownRemaining, resetEpochSeconds);
         }
 
-        static Record current() {
-            Record result = current.get();
-            if (result.isExpired()) {
-                current.set(new UnknownLimitRecord(System.currentTimeMillis() / 1000L + unknownLimitResetSeconds));
-                result = current.get();
+        static synchronized Record current() {
+            if (current.isExpired()) {
+                current = new UnknownLimitRecord(System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
             }
-            return result;
+            return current;
         }
 
         /**
          * Reset the current UnknownLimitRecord. For use during testing only.
          */
-        static void reset() {
-            current.set(DEFAULT);
+        static synchronized void reset() {
+            current = DEFAULT;
             unknownLimitResetSeconds = defaultUnknownLimitResetSeconds;
         }
     }
diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java
index 8d5883961..785a29774 100644
--- a/src/main/java/org/kohsuke/github/GitHubClient.java
+++ b/src/main/java/org/kohsuke/github/GitHubClient.java
@@ -14,7 +14,6 @@ import java.time.Instant;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.logging.Logger;
 
@@ -53,8 +52,10 @@ abstract class GitHubClient {
 
     private HttpConnector connector;
 
+    private final Object rateLimitLock = new Object();
+
     @Nonnull
-    private final AtomicReference<GHRateLimit> rateLimit = new AtomicReference<>(GHRateLimit.DEFAULT);
+    private GHRateLimit rateLimit = GHRateLimit.DEFAULT;
 
     private static final Logger LOGGER = Logger.getLogger(GitHubClient.class.getName());
 
@@ -254,7 +255,9 @@ abstract class GitHubClient {
     @Nonnull
     @Deprecated
     GHRateLimit lastRateLimit() {
-        return rateLimit.get();
+        synchronized (rateLimitLock) {
+            return rateLimit;
+        }
     }
 
     /**
@@ -274,19 +277,12 @@ abstract class GitHubClient {
      */
     @Nonnull
     GHRateLimit rateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException {
-        GHRateLimit result = rateLimit.get();
-        // Most of the time rate limit is not expired, so try to avoid locking.
-        if (result.getRecord(rateLimitTarget).isExpired()) {
-            // if the rate limit is expired, synchronize to ensure
-            // only one call to getRateLimit() is made to refresh it.
-            synchronized (this) {
-                if (rateLimit.get().getRecord(rateLimitTarget).isExpired()) {
-                    getRateLimit(rateLimitTarget);
-                }
+        synchronized (rateLimitLock) {
+            if (rateLimit.getRecord(rateLimitTarget).isExpired()) {
+                getRateLimit(rateLimitTarget);
             }
-            result = rateLimit.get();
+            return rateLimit;
         }
-        return result;
     }
 
     /**
@@ -299,9 +295,15 @@ abstract class GitHubClient {
      *            {@link GHRateLimit.Record} constructed from the response header information
      */
     private GHRateLimit updateRateLimit(@Nonnull GHRateLimit observed) {
-        GHRateLimit result = rateLimit.accumulateAndGet(observed, (current, x) -> current.getMergedRateLimit(x));
-        LOGGER.log(FINEST, "Rate limit now: {0}", rateLimit.get());
-        return result;
+        synchronized (rateLimitLock) {
+            observed = rateLimit.getMergedRateLimit(observed);
+
+            if (rateLimit != observed) {
+                rateLimit = observed;
+                LOGGER.log(FINE, "Rate limit now: {0}", rateLimit);
+            }
+            return rateLimit;
+        }
     }
 
     /**


Test patch:
diff --git a/src/test/java/org/kohsuke/github/GHOrganizationTest.java b/src/test/java/org/kohsuke/github/GHOrganizationTest.java
index 868b26edf..e7c48d714 100644
--- a/src/test/java/org/kohsuke/github/GHOrganizationTest.java
+++ b/src/test/java/org/kohsuke/github/GHOrganizationTest.java
@@ -29,6 +29,8 @@ public class GHOrganizationTest extends AbstractGitHubWireMockTest {
         if (team != null) {
             team.delete();
         }
+
+        getNonRecordingGitHub().getOrganization(GITHUB_API_TEST_ORG).enableOrganizationProjects(true);
     }
 
     @Test
@@ -231,4 +233,28 @@ public class GHOrganizationTest extends AbstractGitHubWireMockTest {
         assertThat(team.getDescription(), equalTo("Team description"));
         assertThat(team.getPrivacy(), equalTo(GHTeam.Privacy.CLOSED));
     }
+
+    @Test
+    public void testAreOrganizationProjectsEnabled() throws IOException {
+        // Arrange
+        GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
+
+        // Act
+        boolean result = org.areOrganizationProjectsEnabled();
+
+        // Assert
+        assertThat(result, is(true));
+    }
+
+    @Test
+    public void testEnableOrganizationProjects() throws IOException {
+        // Arrange
+        GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
+
+        // Act
+        org.enableOrganizationProjects(false);
+
+        // Assert
+        assertThat(org.areOrganizationProjectsEnabled(), is(false));
+    }
 }
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/__files/orgs_hub4j-test-org-2.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/__files/orgs_hub4j-test-org-2.json
new file mode 100644
index 000000000..37a11b213
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/__files/orgs_hub4j-test-org-2.json
@@ -0,0 +1,50 @@
+{
+  "login": "hub4j-test-org",
+  "id": 7544739,
+  "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
+  "url": "https://api.github.com/orgs/hub4j-test-org",
+  "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
+  "events_url": "https://api.github.com/orgs/hub4j-test-org/events",
+  "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
+  "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
+  "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
+  "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
+  "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4",
+  "description": "Hub4j Test Org Description (this could be null or blank too)",
+  "name": "Hub4j Test Org Name (this could be null or blank too)",
+  "company": null,
+  "blog": "https://hub4j.url.io/could/be/null",
+  "location": "Hub4j Test Org Location (this could be null or blank too)",
+  "email": "hub4jtestorgemail@could.be.null.com",
+  "twitter_username": null,
+  "is_verified": false,
+  "has_organization_projects": true,
+  "has_repository_projects": true,
+  "public_repos": 17,
+  "public_gists": 0,
+  "followers": 0,
+  "following": 0,
+  "html_url": "https://github.com/hub4j-test-org",
+  "created_at": "2014-05-10T19:39:11Z",
+  "updated_at": "2020-06-04T05:56:10Z",
+  "type": "Organization",
+  "total_private_repos": 2,
+  "owned_private_repos": 2,
+  "private_gists": 0,
+  "disk_usage": 31,
+  "collaborators": 0,
+  "billing_email": "kk@kohsuke.org",
+  "default_repository_permission": "none",
+  "members_can_create_repositories": false,
+  "two_factor_requirement_enabled": false,
+  "members_can_create_pages": true,
+  "members_can_create_public_pages": true,
+  "members_can_create_private_pages": true,
+  "plan": {
+    "name": "free",
+    "space": 976562499,
+    "private_repos": 10000,
+    "filled_seats": 23,
+    "seats": 3
+  }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/__files/user-1.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/__files/user-1.json
new file mode 100644
index 000000000..bc78437e6
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/__files/user-1.json
@@ -0,0 +1,46 @@
+{
+  "login": "akashRindhe",
+  "id": 14114123,
+  "node_id": "MDQ6VXNlcjE0MTE0MTIz",
+  "avatar_url": "https://avatars.githubusercontent.com/u/14114123?v=4",
+  "gravatar_id": "",
+  "url": "https://api.github.com/users/akashRindhe",
+  "html_url": "https://github.com/akashRindhe",
+  "followers_url": "https://api.github.com/users/akashRindhe/followers",
+  "following_url": "https://api.github.com/users/akashRindhe/following{/other_user}",
+  "gists_url": "https://api.github.com/users/akashRindhe/gists{/gist_id}",
+  "starred_url": "https://api.github.com/users/akashRindhe/starred{/owner}{/repo}",
+  "subscriptions_url": "https://api.github.com/users/akashRindhe/subscriptions",
+  "organizations_url": "https://api.github.com/users/akashRindhe/orgs",
+  "repos_url": "https://api.github.com/users/akashRindhe/repos",
+  "events_url": "https://api.github.com/users/akashRindhe/events{/privacy}",
+  "received_events_url": "https://api.github.com/users/akashRindhe/received_events",
+  "type": "User",
+  "site_admin": false,
+  "name": "Akash Rindhe",
+  "company": null,
+  "blog": "",
+  "location": "Singapore",
+  "email": null,
+  "hireable": null,
+  "bio": null,
+  "twitter_username": null,
+  "public_repos": 9,
+  "public_gists": 0,
+  "followers": 0,
+  "following": 6,
+  "created_at": "2015-09-03T18:07:43Z",
+  "updated_at": "2021-05-12T08:30:12Z",
+  "private_gists": 0,
+  "total_private_repos": 4,
+  "owned_private_repos": 4,
+  "disk_usage": 24551,
+  "collaborators": 1,
+  "two_factor_authentication": true,
+  "plan": {
+    "name": "free",
+    "space": 976562499,
+    "collaborators": 0,
+    "private_repos": 10000
+  }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/mappings/orgs_hub4j-test-org-2.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/mappings/orgs_hub4j-test-org-2.json
new file mode 100644
index 000000000..da0c5331f
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/mappings/orgs_hub4j-test-org-2.json
@@ -0,0 +1,47 @@
+{
+  "id": "d0322427-888c-40d9-a47d-b7c0f1d2fc71",
+  "name": "orgs_hub4j-test-org",
+  "request": {
+    "url": "/orgs/hub4j-test-org",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "orgs_hub4j-test-org-2.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Thu, 13 May 2021 16:11:19 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/\"43be1c5d1d3fa4718ddcd58ebd6be4a41f81ddb5c2102d6ddd628548059db30d\"",
+      "Last-Modified": "Thu, 04 Jun 2020 05:56:10 GMT",
+      "X-OAuth-Scopes": "admin:enterprise, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages",
+      "X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4958",
+      "X-RateLimit-Reset": "1620924638",
+      "X-RateLimit-Used": "42",
+      "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": "8358:203B:A245B6:B13792:609D4FA7"
+    }
+  },
+  "uuid": "d0322427-888c-40d9-a47d-b7c0f1d2fc71",
+  "persistent": true,
+  "insertionIndex": 2
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/mappings/user-1.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/mappings/user-1.json
new file mode 100644
index 000000000..a33910523
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testAreOrganizationProjectsEnabled/mappings/user-1.json
@@ -0,0 +1,47 @@
+{
+  "id": "28895477-e3e7-4621-9a69-834956cf26d7",
+  "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": "Thu, 13 May 2021 16:11:17 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/\"848579fd41f059ee3a1a46f41bea53042fd2303af9d8670074420dc05bcfbd73\"",
+      "Last-Modified": "Wed, 12 May 2021 08:30:12 GMT",
+      "X-OAuth-Scopes": "admin:enterprise, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages",
+      "X-Accepted-OAuth-Scopes": "",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4963",
+      "X-RateLimit-Reset": "1620924638",
+      "X-RateLimit-Used": "37",
+      "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": "8358:203B:A24554:B13727:609D4FA5"
+    }
+  },
+  "uuid": "28895477-e3e7-4621-9a69-834956cf26d7",
+  "persistent": true,
+  "insertionIndex": 1
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/orgs_hub4j-test-org-2.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/orgs_hub4j-test-org-2.json
new file mode 100644
index 000000000..37a11b213
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/orgs_hub4j-test-org-2.json
@@ -0,0 +1,50 @@
+{
+  "login": "hub4j-test-org",
+  "id": 7544739,
+  "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
+  "url": "https://api.github.com/orgs/hub4j-test-org",
+  "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
+  "events_url": "https://api.github.com/orgs/hub4j-test-org/events",
+  "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
+  "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
+  "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
+  "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
+  "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4",
+  "description": "Hub4j Test Org Description (this could be null or blank too)",
+  "name": "Hub4j Test Org Name (this could be null or blank too)",
+  "company": null,
+  "blog": "https://hub4j.url.io/could/be/null",
+  "location": "Hub4j Test Org Location (this could be null or blank too)",
+  "email": "hub4jtestorgemail@could.be.null.com",
+  "twitter_username": null,
+  "is_verified": false,
+  "has_organization_projects": true,
+  "has_repository_projects": true,
+  "public_repos": 17,
+  "public_gists": 0,
+  "followers": 0,
+  "following": 0,
+  "html_url": "https://github.com/hub4j-test-org",
+  "created_at": "2014-05-10T19:39:11Z",
+  "updated_at": "2020-06-04T05:56:10Z",
+  "type": "Organization",
+  "total_private_repos": 2,
+  "owned_private_repos": 2,
+  "private_gists": 0,
+  "disk_usage": 31,
+  "collaborators": 0,
+  "billing_email": "kk@kohsuke.org",
+  "default_repository_permission": "none",
+  "members_can_create_repositories": false,
+  "two_factor_requirement_enabled": false,
+  "members_can_create_pages": true,
+  "members_can_create_public_pages": true,
+  "members_can_create_private_pages": true,
+  "plan": {
+    "name": "free",
+    "space": 976562499,
+    "private_repos": 10000,
+    "filled_seats": 23,
+    "seats": 3
+  }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/orgs_hub4j-test-org-3.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/orgs_hub4j-test-org-3.json
new file mode 100644
index 000000000..d54a522b1
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/orgs_hub4j-test-org-3.json
@@ -0,0 +1,50 @@
+{
+  "login": "hub4j-test-org",
+  "id": 7544739,
+  "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
+  "url": "https://api.github.com/orgs/hub4j-test-org",
+  "repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
+  "events_url": "https://api.github.com/orgs/hub4j-test-org/events",
+  "hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
+  "issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
+  "members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
+  "public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
+  "avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4",
+  "description": "Hub4j Test Org Description (this could be null or blank too)",
+  "name": "Hub4j Test Org Name (this could be null or blank too)",
+  "company": null,
+  "blog": "https://hub4j.url.io/could/be/null",
+  "location": "Hub4j Test Org Location (this could be null or blank too)",
+  "email": "hub4jtestorgemail@could.be.null.com",
+  "twitter_username": null,
+  "is_verified": false,
+  "has_organization_projects": false,
+  "has_repository_projects": true,
+  "public_repos": 17,
+  "public_gists": 0,
+  "followers": 0,
+  "following": 0,
+  "html_url": "https://github.com/hub4j-test-org",
+  "created_at": "2014-05-10T19:39:11Z",
+  "updated_at": "2020-06-04T05:56:10Z",
+  "type": "Organization",
+  "total_private_repos": 2,
+  "owned_private_repos": 2,
+  "private_gists": 0,
+  "disk_usage": 31,
+  "collaborators": 0,
+  "billing_email": "kk@kohsuke.org",
+  "default_repository_permission": "none",
+  "members_can_create_repositories": false,
+  "two_factor_requirement_enabled": false,
+  "members_can_create_pages": true,
+  "members_can_create_public_pages": true,
+  "members_can_create_private_pages": true,
+  "plan": {
+    "name": "free",
+    "space": 976562499,
+    "private_repos": 10000,
+    "filled_seats": 23,
+    "seats": 3
+  }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/user-1.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/user-1.json
new file mode 100644
index 000000000..bc78437e6
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/__files/user-1.json
@@ -0,0 +1,46 @@
+{
+  "login": "akashRindhe",
+  "id": 14114123,
+  "node_id": "MDQ6VXNlcjE0MTE0MTIz",
+  "avatar_url": "https://avatars.githubusercontent.com/u/14114123?v=4",
+  "gravatar_id": "",
+  "url": "https://api.github.com/users/akashRindhe",
+  "html_url": "https://github.com/akashRindhe",
+  "followers_url": "https://api.github.com/users/akashRindhe/followers",
+  "following_url": "https://api.github.com/users/akashRindhe/following{/other_user}",
+  "gists_url": "https://api.github.com/users/akashRindhe/gists{/gist_id}",
+  "starred_url": "https://api.github.com/users/akashRindhe/starred{/owner}{/repo}",
+  "subscriptions_url": "https://api.github.com/users/akashRindhe/subscriptions",
+  "organizations_url": "https://api.github.com/users/akashRindhe/orgs",
+  "repos_url": "https://api.github.com/users/akashRindhe/repos",
+  "events_url": "https://api.github.com/users/akashRindhe/events{/privacy}",
+  "received_events_url": "https://api.github.com/users/akashRindhe/received_events",
+  "type": "User",
+  "site_admin": false,
+  "name": "Akash Rindhe",
+  "company": null,
+  "blog": "",
+  "location": "Singapore",
+  "email": null,
+  "hireable": null,
+  "bio": null,
+  "twitter_username": null,
+  "public_repos": 9,
+  "public_gists": 0,
+  "followers": 0,
+  "following": 6,
+  "created_at": "2015-09-03T18:07:43Z",
+  "updated_at": "2021-05-12T08:30:12Z",
+  "private_gists": 0,
+  "total_private_repos": 4,
+  "owned_private_repos": 4,
+  "disk_usage": 24551,
+  "collaborators": 1,
+  "two_factor_authentication": true,
+  "plan": {
+    "name": "free",
+    "space": 976562499,
+    "collaborators": 0,
+    "private_repos": 10000
+  }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/orgs_hub4j-test-org-2.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/orgs_hub4j-test-org-2.json
new file mode 100644
index 000000000..6c9cf39a1
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/orgs_hub4j-test-org-2.json
@@ -0,0 +1,47 @@
+{
+  "id": "47c14d2c-d8d5-4f11-8208-fb790e6fccca",
+  "name": "orgs_hub4j-test-org",
+  "request": {
+    "url": "/orgs/hub4j-test-org",
+    "method": "GET",
+    "headers": {
+      "Accept": {
+        "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
+      }
+    }
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "orgs_hub4j-test-org-2.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Thu, 13 May 2021 16:07:24 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/\"43be1c5d1d3fa4718ddcd58ebd6be4a41f81ddb5c2102d6ddd628548059db30d\"",
+      "Last-Modified": "Thu, 04 Jun 2020 05:56:10 GMT",
+      "X-OAuth-Scopes": "admin:enterprise, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages",
+      "X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4966",
+      "X-RateLimit-Reset": "1620924638",
+      "X-RateLimit-Used": "34",
+      "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": "834A:6C69:7FCEDB:8CA6DB:609D4EBC"
+    }
+  },
+  "uuid": "47c14d2c-d8d5-4f11-8208-fb790e6fccca",
+  "persistent": true,
+  "insertionIndex": 2
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/orgs_hub4j-test-org-3.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/orgs_hub4j-test-org-3.json
new file mode 100644
index 000000000..d9985cd68
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/orgs_hub4j-test-org-3.json
@@ -0,0 +1,56 @@
+{
+  "id": "eb95bb02-84c4-4e04-bfd4-851985556513",
+  "name": "orgs_hub4j-test-org",
+  "request": {
+    "url": "/orgs/hub4j-test-org",
+    "method": "POST",
+    "headers": {
+      "X-HTTP-Method-Override": {
+        "equalTo": "PATCH"
+      },
+      "Accept": {
+        "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
+      }
+    },
+    "bodyPatterns": [
+      {
+        "equalToJson": "{\"has_organization_projects\":false}",
+        "ignoreArrayOrder": true,
+        "ignoreExtraElements": false
+      }
+    ]
+  },
+  "response": {
+    "status": 200,
+    "bodyFileName": "orgs_hub4j-test-org-3.json",
+    "headers": {
+      "Server": "GitHub.com",
+      "Date": "Thu, 13 May 2021 16:07:25 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/\"5ec2678c3553042c50562ea4033d38b2719d9a5b624a7d95dfcef66108c719f5\"",
+      "X-OAuth-Scopes": "admin:enterprise, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages",
+      "X-Accepted-OAuth-Scopes": "admin:org, repo",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4965",
+      "X-RateLimit-Reset": "1620924638",
+      "X-RateLimit-Used": "35",
+      "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": "834A:6C69:7FCEED:8CA6EE:609D4EBC"
+    }
+  },
+  "uuid": "eb95bb02-84c4-4e04-bfd4-851985556513",
+  "persistent": true,
+  "insertionIndex": 3
+}
\ No newline at end of file
diff --git a/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/user-1.json b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/user-1.json
new file mode 100644
index 000000000..55386263b
--- /dev/null
+++ b/src/test/resources/org/kohsuke/github/GHOrganizationTest/wiremock/testEnableOrganizationProjects/mappings/user-1.json
@@ -0,0 +1,47 @@
+{
+  "id": "b9ae2ae4-ec5c-425c-9144-5863690aabf5",
+  "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": "Thu, 13 May 2021 16:07:22 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/\"848579fd41f059ee3a1a46f41bea53042fd2303af9d8670074420dc05bcfbd73\"",
+      "Last-Modified": "Wed, 12 May 2021 08:30:12 GMT",
+      "X-OAuth-Scopes": "admin:enterprise, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete:packages, delete_repo, gist, notifications, repo, user, workflow, write:discussion, write:packages",
+      "X-Accepted-OAuth-Scopes": "",
+      "X-GitHub-Media-Type": "unknown, github.v3",
+      "X-RateLimit-Limit": "5000",
+      "X-RateLimit-Remaining": "4971",
+      "X-RateLimit-Reset": "1620924638",
+      "X-RateLimit-Used": "29",
+      "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": "834A:6C69:7FCE81:8CA67D:609D4EB9"
+    }
+  },
+  "uuid": "b9ae2ae4-ec5c-425c-9144-5863690aabf5",
+  "persistent": true,
+  "insertionIndex": 1
+}
\ No newline at end of file