feat: Add create organization invitation via email (#2895)

* Add API request to create org invitation

* Add tests for create org invitation

* Add IObservable API request to create org invitation

* Add tests for IObservable create org invitation
This commit is contained in:
skyemcleman
2024-03-12 05:02:31 +11:00
committed by GitHub
parent d27d1f0a22
commit 41d5d240ec
13 changed files with 464 additions and 3 deletions

View File

@@ -304,6 +304,21 @@ namespace Octokit.Reactive
/// <returns></returns>
IObservable<OrganizationMembership> AddOrUpdateOrganizationMembership(string org, string user, OrganizationMembershipUpdate addOrUpdateRequest);
/// <summary>
/// Create an organization invitation for a user
/// </summary>
/// <remarks>
/// This method requires authentication.
/// The authenticated user must be an organization owner.
/// See the <a href="https://developer.github.com/v3/orgs/members/#create-an-organization-invitation">API documentation</a>
/// for more information.
/// </remarks>
/// <param name="org">The login for the organization</param>
/// <param name="invitationRequest">An <see cref="OrganizationInvitationRequest"/> instance containing the
/// details of the organization invitation</param>
/// <returns></returns>
IObservable<OrganizationMembershipInvitation> CreateOrganizationInvitation(string org, OrganizationInvitationRequest invitationRequest);
/// <summary>
/// Remove a user's membership with an organization.
/// </summary>

View File

@@ -418,6 +418,27 @@ namespace Octokit.Reactive
return _client.AddOrUpdateOrganizationMembership(org, user, addOrUpdateRequest).ToObservable();
}
/// <summary>
/// Create an organization invitation for a user
/// </summary>
/// <remarks>
/// This method requires authentication.
/// The authenticated user must be an organization owner.
/// See the <a href="https://developer.github.com/v3/orgs/members/#create-an-organization-invitation">API documentation</a>
/// for more information.
/// </remarks>
/// <param name="org">The login for the organization</param>
/// <param name="invitationRequest">An <see cref="OrganizationInvitationRequest"/> instance containing the
/// details of the organization invitation</param>
/// <returns></returns>
public IObservable<OrganizationMembershipInvitation> CreateOrganizationInvitation(string org, OrganizationInvitationRequest invitationRequest)
{
Ensure.ArgumentNotNullOrEmptyString(org, nameof(org));
Ensure.ArgumentNotNull(invitationRequest, nameof(invitationRequest));
return _client.CreateOrganizationInvitation(org, invitationRequest).ToObservable();
}
/// <summary>
/// Remove a user's membership with an organization.

View File

@@ -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 @@ namespace Octokit.Tests.Integration.Clients
}
}
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<ApiValidationException>(() => _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;

View File

@@ -1,7 +1,9 @@
using System.Reactive.Linq;
using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
using Octokit.Reactive;
using Octokit.Tests.Helpers;
using Octokit.Tests.Integration.Helpers;
using Xunit;
@@ -62,6 +64,97 @@ namespace Octokit.Tests.Integration.Reactive
await _client.RemoveOrganizationMembership(Helper.Organization, "alfhenrik-test-2");
}
}
public class TheCreateOrganizationInvitationMethod
{
readonly IGitHubClient _gitHub;
readonly ObservableOrganizationMembersClient _client;
public TheCreateOrganizationInvitationMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
_client = new ObservableOrganizationMembersClient(_gitHub);
}
[OrganizationTest]
public async Task ReturnsOrganizationMembershipInvitationViaUserId()
{
var user = await _gitHub.User.Get("alfhenrik-test-2");
var organizationInvitationRequest = new OrganizationInvitationRequest(user.Id);
var organizationMembershipInvitation = await _client.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 _client.RemoveOrganizationMembership(Helper.Organization, "alfhenrik-test-2");
}
[OrganizationTest]
public async Task ReturnsOrganizationMembershipInvitationViaUserEmail()
{
var email = RandomEmailGenerator.GenerateRandomEmail();
var organizationInvitationRequest = new OrganizationInvitationRequest(email);
var organizationMembershipInvitation = await _client.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest);
Assert.Equal(email, organizationMembershipInvitation.Email);
Assert.Equal(OrganizationMembershipRole.DirectMember, organizationMembershipInvitation.Role.Value);
Assert.Equal(Helper.UserName, organizationMembershipInvitation.Inviter.Login);
await _client.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<ApiValidationException>(() => _client.CreateOrganizationInvitation(Helper.Organization, organizationInvitationRequest).ToTask());
}
[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 _client.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 _client.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 _client.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 _client.RemoveOrganizationMembership(Helper.Organization, "alfhenrik-test-2");
}
}
public class TheRemoveOrganizationMembershipMethod
{

View File

@@ -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 @@ namespace Octokit.Tests.Clients
}
}
public class TheCreateOrganizationInvitationMethod
{
[Fact]
public void PostsToTheCorrectUrl()
{
var organizationInvitationRequest = new OrganizationInvitationRequest("email");
var connection = Substitute.For<IApiConnection>();
var client = new OrganizationMembersClient(connection);
client.CreateOrganizationInvitation("org", organizationInvitationRequest);
connection.Received().Post<OrganizationMembershipInvitation>(Arg.Is<Uri>(u => u.ToString() == "orgs/org/invitations"), Arg.Any<object>());
}
[Fact]
public async Task EnsureNonNullArguments()
{
var organizationInvitationRequest = new OrganizationInvitationRequest("email");
var client = new OrganizationMembersClient(Substitute.For<IApiConnection>());
await Assert.ThrowsAsync<ArgumentNullException>(() => client.CreateOrganizationInvitation(null, organizationInvitationRequest));
await Assert.ThrowsAsync<ArgumentException>(() => client.CreateOrganizationInvitation("", organizationInvitationRequest));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.CreateOrganizationInvitation("org", null));
}
}
public class TheDeleteOrganizationMembershipMethod
{
[Fact]

View File

@@ -0,0 +1,15 @@
using System;
namespace Octokit.Tests.Helpers
{
public static class RandomEmailGenerator
{
public static string GenerateRandomEmail()
{
var randomUsername = Guid.NewGuid().ToString();
var randomDomain = Guid.NewGuid().ToString();
return $"{randomUsername}@{randomDomain}.com";
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Threading.Tasks;
using Xunit;
namespace Octokit.Tests.Models
{
public class OrganizationInvitationRequestTests
{
public class TheConstructor
{
[Fact]
public void CreatesOrganizationInvitationRequestByUserId()
{
const int userId = 1;
var organizationInvitationRequest = new OrganizationInvitationRequest(userId);
Assert.Equal(userId, organizationInvitationRequest.InviteeId);
Assert.Equal(OrganizationMembershipRole.DirectMember, organizationInvitationRequest.Role);
Assert.Null(organizationInvitationRequest.Email);
Assert.Null(organizationInvitationRequest.TeamIds);
}
[Fact]
public async Task CreatesOrganizationInvitationRequestByUserEmail()
{
const string email = "testemail";
var organizationInvitationRequest = new OrganizationInvitationRequest(email);
Assert.Equal(email, organizationInvitationRequest.Email);
Assert.Equal(OrganizationMembershipRole.DirectMember, organizationInvitationRequest.Role);
Assert.Null(organizationInvitationRequest.InviteeId);
Assert.Null(organizationInvitationRequest.TeamIds);
}
}
}
}

View File

@@ -361,6 +361,33 @@ namespace Octokit.Tests.Reactive
await Assert.ThrowsAsync<ArgumentNullException>(() => client.AddOrUpdateOrganizationMembership("org", "username", null).ToTask());
}
}
public class TheCreateOrganizationInvitationMethod
{
[Fact]
public void CreateOrganizationInvitationFromClientOrganizationMember()
{
var gitHubClient = Substitute.For<IGitHubClient>();
var client = new ObservableOrganizationMembersClient(gitHubClient);
var organizationInvitationRequest = new OrganizationInvitationRequest(1);
client.CreateOrganizationInvitation("org", organizationInvitationRequest);
gitHubClient.Organization.Member.Received().CreateOrganizationInvitation("org", organizationInvitationRequest);
}
[Fact]
public async Task EnsureNonNullArguments()
{
var client = new ObservableOrganizationMembersClient(Substitute.For<IGitHubClient>());
var organizationInvitationRequest = new OrganizationInvitationRequest(1);
await Assert.ThrowsAsync<ArgumentNullException>(() => client.CreateOrganizationInvitation(null, organizationInvitationRequest).ToTask());
await Assert.ThrowsAsync<ArgumentException>(() => client.CreateOrganizationInvitation("", organizationInvitationRequest).ToTask());
await Assert.ThrowsAsync<ArgumentNullException>(() => client.CreateOrganizationInvitation("org", null).ToTask());
}
}
public class TheDeleteOrganizationMembershipMethod
{

View File

@@ -310,6 +310,21 @@ namespace Octokit
/// <returns></returns>
Task<OrganizationMembership> AddOrUpdateOrganizationMembership(string org, string user, OrganizationMembershipUpdate addOrUpdateRequest);
/// <summary>
/// Create an organization invitation for a user
/// </summary>
/// <remarks>
/// This method requires authentication.
/// The authenticated user must be an organization owner.
/// See the <a href="https://developer.github.com/v3/orgs/members/#create-an-organization-invitation">API documentation</a>
/// for more information.
/// </remarks>
/// <param name="org">The login for the organization</param>
/// <param name="invitationRequest">An <see cref="OrganizationInvitationRequest"/> instance containing the
/// details of the organization invitation</param>
/// <returns></returns>
Task<OrganizationMembershipInvitation> CreateOrganizationInvitation(string org, OrganizationInvitationRequest invitationRequest);
/// <summary>
/// Remove a user's membership with an organization.
/// </summary>

View File

@@ -511,6 +511,28 @@ namespace Octokit
return ApiConnection.Put<OrganizationMembership>(ApiUrls.OrganizationMemberships(org, user), addOrUpdateRequest);
}
/// <summary>
/// Create an organization invitation for a user
/// </summary>
/// <remarks>
/// This method requires authentication.
/// The authenticated user must be an organization owner.
/// See the <a href="https://developer.github.com/v3/orgs/members/#create-an-organization-invitation">API documentation</a>
/// for more information.
/// </remarks>
/// <param name="org">The login for the organization</param>
/// <param name="invitationRequest">An <see cref="OrganizationInvitationRequest"/> instance containing the
/// details of the organization invitation</param>
/// <returns></returns>
[ManualRoute("POST", "/orgs/{org}/invitations")]
public Task<OrganizationMembershipInvitation> CreateOrganizationInvitation(string org, OrganizationInvitationRequest invitationRequest)
{
Ensure.ArgumentNotNullOrEmptyString(org, nameof(org));
Ensure.ArgumentNotNull(invitationRequest, nameof(invitationRequest));
return ApiConnection.Post<OrganizationMembershipInvitation>(ApiUrls.OrganizationInvitations(org), invitationRequest);
}
/// <summary>
/// Remove a user's membership with an organization.
/// </summary>

View File

@@ -922,6 +922,16 @@ namespace Octokit
{
return "orgs/{0}/memberships/{1}".FormatUri(org, name);
}
/// <summary>
/// Returns the <see cref="Uri"/> for the organization's invitations
/// </summary>
/// <param name="org">The name of the organization</param>
/// <returns></returns>
public static Uri OrganizationInvitations(string org)
{
return "orgs/{0}/invitations".FormatUri(org);
}
/// <summary>
/// Returns the <see cref="Uri"/> for the organizations pending invitations

View File

@@ -0,0 +1,87 @@
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
/// <summary>
/// Used as part of the request to invite a user to an organization.
/// </summary>
[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;
}
/// <summary>
/// The user ID of the person being invited. Required if Email is not specified.
/// </summary>
[Parameter(Key = "invitee_id")]
public int? InviteeId { get; set; }
/// <summary>
/// The email address of the person being invited. Required if InviteeId is not specified.
/// </summary>
[Parameter(Key = "email")]
public string Email { get; set; }
/// <summary>
/// The role to give the user in the organization. The default is <see cref="OrganizationMembershipRole.DirectMember"/>.
/// </summary>
[Parameter(Key = "role")]
public OrganizationMembershipRole Role { get; set; } = OrganizationMembershipRole.DirectMember;
/// <summary>
/// The IDs for the team(s) to invite new members to
/// </summary>
[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) : "")}";
}
}

View File

@@ -12,7 +12,7 @@ namespace Octokit
{
}
public OrganizationMembershipInvitation(int id, string nodeId, string login, string email, OrganizationMembershipRole role, DateTimeOffset createdAt, User inviter)
public OrganizationMembershipInvitation(int id, string nodeId, string login, string email, OrganizationMembershipRole role, DateTimeOffset createdAt, User inviter, int teamCount)
{
Id = id;
NodeId = nodeId;
@@ -21,6 +21,7 @@ namespace Octokit
Role = role;
CreatedAt = createdAt;
Inviter = inviter;
TeamCount = teamCount;
}
public int Id { get; private set; }
@@ -35,6 +36,7 @@ namespace Octokit
public StringEnum<OrganizationMembershipRole> Role { get; private set; }
public DateTimeOffset CreatedAt { get; private set; }
public User Inviter { get; private set; }
public int TeamCount { get; private set; }
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
Justification = "Used by DebuggerDisplayAttribute")]