From eb2577e7fd92f283e440decb81a82822643d0d36 Mon Sep 17 00:00:00 2001 From: Rory Kelly Date: Mon, 13 Apr 2026 09:48:00 +0100 Subject: [PATCH 1/3] Add builder for updating multiple GHTeam properties. --- src/main/java/org/kohsuke/github/GHTeam.java | 23 ++++ .../kohsuke/github/GHTeamUpdateBuilder.java | 123 ++++++++++++++++++ .../github/GHTeamUpdateBuilderTest.java | 97 ++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 src/main/java/org/kohsuke/github/GHTeamUpdateBuilder.java create mode 100644 src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java index 49be640321..0ac0ad4a90 100644 --- a/src/main/java/org/kohsuke/github/GHTeam.java +++ b/src/main/java/org/kohsuke/github/GHTeam.java @@ -48,6 +48,20 @@ public enum Role { MEMBER } + /** + * Notification setting across a team + */ + public enum NotificationSetting { + /** + * Team members receive notifications when the team is @mentioned. + */ + NOTIFICATIONS_ENABLED, + /** + * No one receives notifications. + */ + NOTIFICATIONS_DISABLED + } + /** * Path for external group-related operations */ @@ -511,6 +525,15 @@ public void setPrivacy(Privacy privacy) throws IOException { root().createRequest().method("PATCH").with("privacy", privacy).withUrlPath(api("")).send(); } + /** + * Start constructing an update request for multiple properties of this team. + * + * @return a builder to update this team + */ + public GHTeamUpdateBuilder updateTeam() { + return new GHTeamUpdateBuilder(root(), organization.getLogin(), name); + } + private String api(String tail) { if (organization == null) { // Teams returned from pull requests to do not have an organization. Attempt to use url. diff --git a/src/main/java/org/kohsuke/github/GHTeamUpdateBuilder.java b/src/main/java/org/kohsuke/github/GHTeamUpdateBuilder.java new file mode 100644 index 0000000000..2a1e4357c5 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHTeamUpdateBuilder.java @@ -0,0 +1,123 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; + +/** + * Updates a team. + * + * @see Update team docs + * @author Rory Kelly + */ +public class GHTeamUpdateBuilder extends GitHubInteractiveObject { + + private final String orgName; + private final String existingName; + /** The builder. */ + protected final Requester builder; + + /** + * Instantiates a new GH team update builder. + * + * @param root + * the root + * @param orgName + * the org name + * @param existingName + * the current name of the existing team + */ + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected") + public GHTeamUpdateBuilder(GitHub root, String orgName, String existingName) { + super(root); + this.orgName = orgName; + this.existingName = existingName; + this.builder = root.createRequest(); + } + + /** + * Updates a team with all the provided parameters. + * + * @return the gh team + * @throws IOException + * if team cannot be updated + */ + public GHTeam update() throws IOException { + return builder.method("PATCH").withUrlPath("/orgs/" + orgName + "/teams/" + existingName).fetch(GHTeam.class).wrapUp(root()); + } + + /** + * Name for this team. + * + * @param name + * name of team + * @return a builder to continue with building + */ + public GHTeamUpdateBuilder name(String name) { + this.builder.with("name", name); + return this; + } + + /** + * Description for this team. + * + * @param description + * description of team + * @return a builder to continue with building + */ + public GHTeamUpdateBuilder description(String description) { + this.builder.with("description", description); + return this; + } + + /** + * Privacy for this team. + * + * @param privacy + * privacy of team + * @return a builder to continue with building + */ + public GHTeamUpdateBuilder privacy(GHTeam.Privacy privacy) { + this.builder.with("privacy", privacy); + return this; + } + + /** + * The notification setting explicitly set for this team. + * + * @param notificationSetting + * notification setting to be applied + * @return a builder to continue with building + */ + public GHTeamUpdateBuilder notifications(GHTeam.NotificationSetting notificationSetting) { + this.builder.with("notification_setting", notificationSetting); + return this; + } + + /** + * The permission that new repositories will be added to the team with when none is specified. + * + * @param permission + * permission to be applied + * @return a builder to continue with building + * @deprecated see + * Update team docs + */ + @Deprecated + public GHTeamUpdateBuilder permission(GHOrganization.Permission permission) { + this.builder.with("permission", permission); + return this; + } + + /** + * Parent team id for this team. + * + * @param parentTeamId + * parentTeamId of team, or null if you are removing the parent + * @return a builder to continue with building + */ + public GHTeamUpdateBuilder parentTeamId(Long parentTeamId) { + this.builder.with("parent_team_id", parentTeamId); + return this; + } +} diff --git a/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java b/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java new file mode 100644 index 0000000000..4428735a1c --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java @@ -0,0 +1,97 @@ +package org.kohsuke.github; + +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.Matchers.equalTo; + +// TODO: Auto-generated Javadoc + +/** + * The Class GHTeamUpdateBuilderTest. + * + * @author Rory Kelly + */ +public class GHTeamUpdateBuilderTest extends AbstractGitHubWireMockTest { + + private static final String TEAM_TO_UPDATE_SLUG = "dummy-team-to-update"; + + private static final String TEAM_TO_UPDATE_NEW_NAME = "dummy-team-updated"; + private static final String TEAM_TO_UPDATE_NEW_DESCRIPTION = "This is an updated description!"; + private static final GHTeam.Privacy TEAM_TO_UPDATE_NEW_PRIVACY = GHTeam.Privacy.SECRET; + private static final GHTeam.NotificationSetting TEAM_TO_UPDATE_NEW_NOTIFICATIONS = GHTeam.NotificationSetting.NOTIFICATIONS_DISABLED; + @Deprecated + private static final GHOrganization.Permission TEAM_TO_UPDATE_NEW_PERMISSIONS = GHOrganization.Permission.PUSH; + + // private static final String CURRENT_PARENT_TEAM_SLUG = "dummy-current-parent-team"; + private static final String NEW_PARENT_TEAM_SLUG = "dummy-new-parent-team"; + + /** + * Create default GHTeamBuilderTest instance + */ + public GHTeamUpdateBuilderTest() { + } + + // update name, description, privacy, notifications, permission, no change to parent team + // update parent team + // update removal of parent team + + @Test + public void testUpdateTeamWithNewParentTeam() throws IOException { + // Get the parent team + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam teamToUpdate = org.getTeamBySlug(TEAM_TO_UPDATE_SLUG); + GHTeam newParentTeam = org.getTeamBySlug(NEW_PARENT_TEAM_SLUG); + + GHTeam updatedTeam = getCommonBuilder(teamToUpdate) + .parentTeamId(newParentTeam.getId()) + .update(); + + assertUpdatedTeam(updatedTeam); + // assertThat(updatedTeam.getParentTeam().getId(), equalTo(newParentTeam.getId())); + } + + @Test + public void testUpdateTeamWithNoChangeToParentTeam() throws IOException { + // Get the parent team + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam teamToUpdate = org.getTeamBySlug(TEAM_TO_UPDATE_SLUG); + // GHTeam existingParentTeam = org.getTeamBySlug(CURRENT_PARENT_TEAM_SLUG); + + GHTeam updatedTeam = getCommonBuilder(teamToUpdate).update(); + + assertUpdatedTeam(updatedTeam); + // assertThat(teamToUpdate.getParentTeam().getId(), equalTo(existingParentTeam.getId())); + } + + @Test + public void testUpdateTeamWithRemovedParentTeam() throws IOException { + GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); + GHTeam teamToUpdate = org.getTeamBySlug(TEAM_TO_UPDATE_SLUG); + + GHTeam updatedTeam = getCommonBuilder(teamToUpdate) + .parentTeamId(null) + .update(); + + assertUpdatedTeam(updatedTeam); + // assertThat(teamToUpdate.getParentTeam(), equalTo(null)); + } + + private GHTeamUpdateBuilder getCommonBuilder(GHTeam teamToUpdate) { + return teamToUpdate.updateTeam() + .name(TEAM_TO_UPDATE_NEW_NAME) + .description(TEAM_TO_UPDATE_NEW_DESCRIPTION) + .privacy(TEAM_TO_UPDATE_NEW_PRIVACY) + .notifications(TEAM_TO_UPDATE_NEW_NOTIFICATIONS) + .permission(TEAM_TO_UPDATE_NEW_PERMISSIONS); + } + + private void assertUpdatedTeam(GHTeam updatedTeam) { + assertThat(updatedTeam.getName(), equalTo(TEAM_TO_UPDATE_NEW_NAME)); + assertThat(updatedTeam.getDescription(), equalTo(TEAM_TO_UPDATE_NEW_DESCRIPTION)); + assertThat(updatedTeam.getPrivacy(), equalTo(TEAM_TO_UPDATE_NEW_PRIVACY)); + // assertThat(updatedTeam.getNotificationSetting(), equalTo(TEAM_TO_UPDATE_NEW_NOTIFICATIONS)); + assertThat(updatedTeam.getPermission(), equalTo(TEAM_TO_UPDATE_NEW_PERMISSIONS)); + } +} From d68318bc39cbee47cf75eaf1285c36ea2aa21269 Mon Sep 17 00:00:00 2001 From: Rory Kelly Date: Mon, 13 Apr 2026 10:15:50 +0100 Subject: [PATCH 2/3] Add UNKNOWN value for nested GHTeam enums where missing. --- src/main/java/org/kohsuke/github/GHTeam.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java index 0ac0ad4a90..16648b4f86 100644 --- a/src/main/java/org/kohsuke/github/GHTeam.java +++ b/src/main/java/org/kohsuke/github/GHTeam.java @@ -45,7 +45,9 @@ public enum Role { */ MAINTAINER, /** A normal member of the team. */ - MEMBER + MEMBER, + /** Unknown role. */ + UNKNOWN } /** @@ -59,7 +61,11 @@ public enum NotificationSetting { /** * No one receives notifications. */ - NOTIFICATIONS_DISABLED + NOTIFICATIONS_DISABLED, + /** + * Unknown notification setting. + */ + UNKNOWN } /** From 7f3d758a43c1e7c0b6184ddfa349ecb33b9a9427 Mon Sep 17 00:00:00 2001 From: Rory Kelly Date: Mon, 13 Apr 2026 13:12:15 +0100 Subject: [PATCH 3/3] Update test comments. --- .../github/GHTeamUpdateBuilderTest.java | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java b/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java index 4428735a1c..cc5cd230b5 100644 --- a/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java +++ b/src/test/java/org/kohsuke/github/GHTeamUpdateBuilderTest.java @@ -33,17 +33,18 @@ public class GHTeamUpdateBuilderTest extends AbstractGitHubWireMockTest { public GHTeamUpdateBuilderTest() { } - // update name, description, privacy, notifications, permission, no change to parent team - // update parent team - // update removal of parent team - + /** + * Given a team, when updating the team with a different parent team, then the team is updated with the new parent team. + * @throws IOException exception thrown if there is an issue with Wiremock + */ @Test public void testUpdateTeamWithNewParentTeam() throws IOException { - // Get the parent team + // Get the org and teams GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam teamToUpdate = org.getTeamBySlug(TEAM_TO_UPDATE_SLUG); GHTeam newParentTeam = org.getTeamBySlug(NEW_PARENT_TEAM_SLUG); + // Update team with different parent team GHTeam updatedTeam = getCommonBuilder(teamToUpdate) .parentTeamId(newParentTeam.getId()) .update(); @@ -52,24 +53,35 @@ public void testUpdateTeamWithNewParentTeam() throws IOException { // assertThat(updatedTeam.getParentTeam().getId(), equalTo(newParentTeam.getId())); } + /** + * Given a team, when updating the team with no change to parent team, then the team is updated with no change to the parent team. + * @throws IOException exception thrown if there is an issue with Wiremock + */ @Test public void testUpdateTeamWithNoChangeToParentTeam() throws IOException { - // Get the parent team + // Get the org and team GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam teamToUpdate = org.getTeamBySlug(TEAM_TO_UPDATE_SLUG); // GHTeam existingParentTeam = org.getTeamBySlug(CURRENT_PARENT_TEAM_SLUG); + // update team with no change to parent team GHTeam updatedTeam = getCommonBuilder(teamToUpdate).update(); assertUpdatedTeam(updatedTeam); // assertThat(teamToUpdate.getParentTeam().getId(), equalTo(existingParentTeam.getId())); } + /** + * Given a team, when updating the team with a removed parent team, then the team is updated and has no parent team. + * @throws IOException exception thrown if there is an issue with Wiremock + */ @Test public void testUpdateTeamWithRemovedParentTeam() throws IOException { + // Get the org and team GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); GHTeam teamToUpdate = org.getTeamBySlug(TEAM_TO_UPDATE_SLUG); + // Update team with removed parent team GHTeam updatedTeam = getCommonBuilder(teamToUpdate) .parentTeamId(null) .update(); @@ -78,6 +90,12 @@ public void testUpdateTeamWithRemovedParentTeam() throws IOException { // assertThat(teamToUpdate.getParentTeam(), equalTo(null)); } + /** + * Get the GHTeamUpdateBuilder instance with the common fields set for updating a team, to be used in the different update scenarios. + * + * @param teamToUpdate the base team to update + * @return the GHTeamUpdateBuilder instance with the common fields set for updating a team + */ private GHTeamUpdateBuilder getCommonBuilder(GHTeam teamToUpdate) { return teamToUpdate.updateTeam() .name(TEAM_TO_UPDATE_NEW_NAME) @@ -87,6 +105,11 @@ private GHTeamUpdateBuilder getCommonBuilder(GHTeam teamToUpdate) { .permission(TEAM_TO_UPDATE_NEW_PERMISSIONS); } + /** + * Assert that the updated team has the expected updated values. + * + * @param updatedTeam the team to assert the updated values on + */ private void assertUpdatedTeam(GHTeam updatedTeam) { assertThat(updatedTeam.getName(), equalTo(TEAM_TO_UPDATE_NEW_NAME)); assertThat(updatedTeam.getDescription(), equalTo(TEAM_TO_UPDATE_NEW_DESCRIPTION));