From 68b3fa5c3d2a7094fbf7afccfd6e26c13c7465e8 Mon Sep 17 00:00:00 2001
From: Skye McLeman <118782658+skyemcleman@users.noreply.github.com>
Date: Fri, 8 Mar 2024 14:37:59 +1100
Subject: [PATCH 1/4] Add API request to create org invitation
---
Octokit/Clients/IOrganizationMembersClient.cs | 15 ++++
Octokit/Clients/OrganizationMembersClient.cs | 22 +++++
Octokit/Helpers/ApiUrls.cs | 10 +++
.../Request/OrganizationInvitationRequest.cs | 87 +++++++++++++++++++
4 files changed, 134 insertions(+)
create mode 100644 Octokit/Models/Request/OrganizationInvitationRequest.cs
diff --git a/Octokit/Clients/IOrganizationMembersClient.cs b/Octokit/Clients/IOrganizationMembersClient.cs
index c16707f9d6..1a71af028f 100644
--- a/Octokit/Clients/IOrganizationMembersClient.cs
+++ b/Octokit/Clients/IOrganizationMembersClient.cs
@@ -310,6 +310,21 @@ public interface IOrganizationMembersClient
///
Task AddOrUpdateOrganizationMembership(string org, string user, OrganizationMembershipUpdate addOrUpdateRequest);
+ ///
+ /// Create an organization invitation for a user
+ ///
+ ///
+ /// This method requires authentication.
+ /// The authenticated user must be an organization owner.
+ /// See the API documentation
+ /// for more information.
+ ///
+ /// The login for the organization
+ /// An instance containing the
+ /// details of the organization invitation
+ ///
+ Task CreateOrganizationInvitation(string org, OrganizationInvitationRequest invitationRequest);
+
///
/// Remove a user's membership with an organization.
///
diff --git a/Octokit/Clients/OrganizationMembersClient.cs b/Octokit/Clients/OrganizationMembersClient.cs
index b5fd87de78..038d821be4 100644
--- a/Octokit/Clients/OrganizationMembersClient.cs
+++ b/Octokit/Clients/OrganizationMembersClient.cs
@@ -511,6 +511,28 @@ public Task AddOrUpdateOrganizationMembership(string org
return ApiConnection.Put(ApiUrls.OrganizationMemberships(org, user), addOrUpdateRequest);
}
+ ///
+ /// Create an organization invitation for a user
+ ///
+ ///
+ /// This method requires authentication.
+ /// The authenticated user must be an organization owner.
+ /// See the API documentation
+ /// for more information.
+ ///
+ /// The login for the organization
+ /// An instance containing the
+ /// details of the organization invitation
+ ///
+ [ManualRoute("POST", "/orgs/{org}/invitations")]
+ public Task CreateOrganizationInvitation(string org, OrganizationInvitationRequest invitationRequest)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(org, nameof(org));
+ Ensure.ArgumentNotNull(invitationRequest, nameof(invitationRequest));
+
+ return ApiConnection.Post(ApiUrls.OrganizationInvitations(org), invitationRequest);
+ }
+
///
/// Remove a user's membership with an organization.
///
diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs
index 50b8e1a0e9..7c8ecc27ed 100644
--- a/Octokit/Helpers/ApiUrls.cs
+++ b/Octokit/Helpers/ApiUrls.cs
@@ -922,6 +922,16 @@ public static Uri OrganizationMemberships(string org, string name)
{
return "orgs/{0}/memberships/{1}".FormatUri(org, name);
}
+
+ ///
+ /// Returns the for the organization's invitations
+ ///
+ /// The name of the organization
+ ///
+ public static Uri OrganizationInvitations(string org)
+ {
+ return "orgs/{0}/invitations".FormatUri(org);
+ }
///
/// Returns the for the organizations pending invitations
diff --git a/Octokit/Models/Request/OrganizationInvitationRequest.cs b/Octokit/Models/Request/OrganizationInvitationRequest.cs
new file mode 100644
index 0000000000..0db963146d
--- /dev/null
+++ b/Octokit/Models/Request/OrganizationInvitationRequest.cs
@@ -0,0 +1,87 @@
+using System.Diagnostics;
+using System.Globalization;
+using Octokit.Internal;
+
+namespace Octokit
+{
+ ///
+ /// Used as part of the request to invite a user to an organization.
+ ///
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public class OrganizationInvitationRequest
+ {
+ public OrganizationInvitationRequest(int inviteeId)
+ {
+ InviteeId = inviteeId;
+ }
+
+ public OrganizationInvitationRequest(string email)
+ {
+ Email = email;
+ }
+
+ public OrganizationInvitationRequest(int inviteeId, OrganizationMembershipRole role)
+ {
+ InviteeId = inviteeId;
+ Role = role;
+ }
+
+ public OrganizationInvitationRequest(string email, OrganizationMembershipRole role)
+ {
+ Email = email;
+ Role = role;
+ }
+
+ public OrganizationInvitationRequest(int inviteeId, int[] teamIds)
+ {
+ InviteeId = inviteeId;
+ TeamIds = teamIds;
+ }
+
+ public OrganizationInvitationRequest(string email, int[] teamIds)
+ {
+ Email = email;
+ TeamIds = teamIds;
+ }
+
+ public OrganizationInvitationRequest(int inviteeId, OrganizationMembershipRole role, int[] teamIds)
+ {
+ InviteeId = inviteeId;
+ Role = role;
+ TeamIds = teamIds;
+ }
+
+ public OrganizationInvitationRequest(string email, OrganizationMembershipRole role, int[] teamIds)
+ {
+ Email = email;
+ Role = role;
+ TeamIds = teamIds;
+ }
+
+ ///
+ /// The user ID of the person being invited. Required if Email is not specified.
+ ///
+ [Parameter(Key = "invitee_id")]
+ public int? InviteeId { get; set; }
+
+ ///
+ /// The email address of the person being invited. Required if InviteeId is not specified.
+ ///
+ [Parameter(Key = "email")]
+ public string Email { get; set; }
+
+ ///
+ /// The role to give the user in the organization. The default is .
+ ///
+ [Parameter(Key = "role")]
+ public OrganizationMembershipRole Role { get; set; } = OrganizationMembershipRole.DirectMember;
+
+ ///
+ /// The IDs for the team(s) to invite new members to
+ ///
+ [Parameter(Key = "team_ids")]
+ public int[] TeamIds { get; set; }
+
+ internal string DebuggerDisplay => $"InviteeId: {InviteeId}; Email: {Email}; Role: {Role}; Team IDs: {(TeamIds != null ? string.Join(", ", TeamIds) : "")}";
+ }
+}
From 0edfab51e6d61bc236f2f6315dc7473a8eab9c90 Mon Sep 17 00:00:00 2001
From: Skye McLeman <118782658+skyemcleman@users.noreply.github.com>
Date: Fri, 8 Mar 2024 14:38:36 +1100
Subject: [PATCH 2/4] Add tests for create org invitation
---
.../Clients/OrganizationMembersClientTests.cs | 93 ++++++++++++++++++-
.../Clients/OrganizationMembersClientTests.cs | 28 ++++++
Octokit.Tests/Helpers/RandomEmailGenerator.cs | 15 +++
.../OrganizationInvitationRequestTests.cs | 35 +++++++
.../OrganizationMembershipInvitation.cs | 4 +-
5 files changed, 173 insertions(+), 2 deletions(-)
create mode 100644 Octokit.Tests/Helpers/RandomEmailGenerator.cs
create mode 100644 Octokit.Tests/Models/OrganizationInvitationRequestTests.cs
diff --git a/Octokit.Tests.Integration/Clients/OrganizationMembersClientTests.cs b/Octokit.Tests.Integration/Clients/OrganizationMembersClientTests.cs
index d1fcb7f2c9..580d24fe09 100644
--- a/Octokit.Tests.Integration/Clients/OrganizationMembersClientTests.cs
+++ b/Octokit.Tests.Integration/Clients/OrganizationMembersClientTests.cs
@@ -1,5 +1,7 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Threading.Tasks;
+using Octokit.Tests.Helpers;
using Octokit.Tests.Integration.Helpers;
using Xunit;
@@ -172,6 +174,95 @@ public async Task ReturnsUsersPendingAdminOrganizationMembership()
}
}
+ public class TheCreateOrganizationInvitationMethod
+ {
+ readonly IGitHubClient _gitHub;
+
+ public TheCreateOrganizationInvitationMethod()
+ {
+ _gitHub = Helper.GetAuthenticatedClient();
+ }
+
+ [OrganizationTest]
+ public async Task ReturnsOrganizationMembershipInvitationViaUserId()
+ {
+ var user = await _gitHub.User.Get("alfhenrik-test-2");
+
+ var organizationInvitationRequest = new OrganizationInvitationRequest(user.Id);
+ var organizationMembershipInvitation = await _gitHub.Organization.Member.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest);
+
+ Assert.Equal("alfhenrik-test-2", organizationMembershipInvitation.Login);
+ Assert.Equal(OrganizationMembershipRole.DirectMember, organizationMembershipInvitation.Role.Value);
+ Assert.Equal(Helper.UserName, organizationMembershipInvitation.Inviter.Login);
+
+ await _gitHub.Organization.Member.RemoveOrganizationMembership(Helper.Organization, "alfhenrik-test-2");
+ }
+
+ [OrganizationTest]
+ public async Task ReturnsOrganizationMembershipInvitationViaUserEmail()
+ {
+ var email = RandomEmailGenerator.GenerateRandomEmail();
+
+ var organizationInvitationRequest = new OrganizationInvitationRequest(email);
+ var organizationMembershipInvitation = await _gitHub.Organization.Member.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest);
+
+ Assert.Equal(email, organizationMembershipInvitation.Email);
+ Assert.Equal(OrganizationMembershipRole.DirectMember, organizationMembershipInvitation.Role.Value);
+ Assert.Equal(Helper.UserName, organizationMembershipInvitation.Inviter.Login);
+
+ await _gitHub.Organization.Member.CancelOrganizationInvitation(Helper.Organization, organizationMembershipInvitation.Id);
+ }
+
+ [OrganizationTest]
+ public async Task ThrowsApiValidationExceptionForCurrentOrganizationMembers()
+ {
+ var user = await _gitHub.User.Get(Helper.UserName);
+ var organizationInvitationRequest = new OrganizationInvitationRequest(user.Id);
+
+ await Assert.ThrowsAsync(() => _gitHub.Organization.Member.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest));
+ }
+
+ [OrganizationTest]
+ public async Task ReturnsOrganizationMembershipInvitationSingleTeam()
+ {
+ var user = await _gitHub.User.Get("alfhenrik-test-2");
+
+ var team1 = await _gitHub.Organization.Team.Create(Helper.Organization, new NewTeam("TestTeam1"));
+
+ var organizationInvitationRequest = new OrganizationInvitationRequest(user.Id, new int[] {team1.Id});
+ var organizationMembershipInvitation = await _gitHub.Organization.Member.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest);
+
+ Assert.Equal("alfhenrik-test-2", organizationMembershipInvitation.Login);
+ Assert.Equal(OrganizationMembershipRole.DirectMember, organizationMembershipInvitation.Role.Value);
+ Assert.Equal(Helper.UserName, organizationMembershipInvitation.Inviter.Login);
+ Assert.Equal(1, organizationMembershipInvitation.TeamCount);
+
+ await _gitHub.Organization.Team.Delete(Helper.Organization, team1.Slug);
+ await _gitHub.Organization.Member.RemoveOrganizationMembership(Helper.Organization, "alfhenrik-test-2");
+ }
+
+ [OrganizationTest]
+ public async Task ReturnsOrganizationMembershipInvitationMultipleTeams()
+ {
+ var user = await _gitHub.User.Get("alfhenrik-test-2");
+
+ var team1 = await _gitHub.Organization.Team.Create(Helper.Organization, new NewTeam("TestTeam1"));
+ var team2 = await _gitHub.Organization.Team.Create(Helper.Organization, new NewTeam("TestTeam2"));
+
+ var organizationInvitationRequest = new OrganizationInvitationRequest(user.Id, new int[] {team1.Id, team2.Id});
+ var organizationMembershipInvitation = await _gitHub.Organization.Member.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest);
+
+ Assert.Equal("alfhenrik-test-2", organizationMembershipInvitation.Login);
+ Assert.Equal(OrganizationMembershipRole.DirectMember, organizationMembershipInvitation.Role.Value);
+ Assert.Equal(Helper.UserName, organizationMembershipInvitation.Inviter.Login);
+ Assert.Equal(2, organizationMembershipInvitation.TeamCount);
+
+ await _gitHub.Organization.Team.Delete(Helper.Organization, team1.Slug);
+ await _gitHub.Organization.Team.Delete(Helper.Organization, team2.Slug);
+ await _gitHub.Organization.Member.RemoveOrganizationMembership(Helper.Organization, "alfhenrik-test-2");
+ }
+ }
+
public class TheRemoveOrganizationMembershipMethod
{
readonly IGitHubClient _gitHub;
diff --git a/Octokit.Tests/Clients/OrganizationMembersClientTests.cs b/Octokit.Tests/Clients/OrganizationMembersClientTests.cs
index d09ed836aa..838cca8644 100644
--- a/Octokit.Tests/Clients/OrganizationMembersClientTests.cs
+++ b/Octokit.Tests/Clients/OrganizationMembersClientTests.cs
@@ -2,6 +2,7 @@
using System.Net;
using System.Threading.Tasks;
using NSubstitute;
+using NSubstitute.Core.DependencyInjection;
using Octokit.Internal;
using Xunit;
@@ -562,6 +563,33 @@ public async Task EnsureNonNullArguments()
}
}
+ public class TheCreateOrganizationInvitationMethod
+ {
+ [Fact]
+ public void PostsToTheCorrectUrl()
+ {
+ var organizationInvitationRequest = new OrganizationInvitationRequest("email");
+
+ var connection = Substitute.For();
+ var client = new OrganizationMembersClient(connection);
+
+ client.CreateOrganizationInvitation("org", organizationInvitationRequest);
+
+ connection.Received().Post(Arg.Is(u => u.ToString() == "orgs/org/invitations"), Arg.Any