Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/Mailtrap.Example.SubAccount/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
"Created Sub Account: Id={Id}, Name={Name}",
createdSubAccount.Id,
createdSubAccount.Name);

// Delete the sub account permanently, together with all its data.
// Deleting the last sub account of the organization deletes the organization as well.
await organizationResource.SubAccount(createdSubAccount.Id).Delete();
logger.LogInformation("Deleted Sub Account: Id={Id}", createdSubAccount.Id);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,21 @@ public interface IOrganizationResource : IRestResource
/// Sub account collection resource for the organization, represented by this resource instance.
/// </returns>
public IOrganizationSubAccountCollectionResource SubAccounts();

/// <summary>
/// Gets resource for specific sub account, identified by <paramref name="subAccountId"/>.
/// </summary>
///
/// <param name="subAccountId">
/// ID of sub account to get resource for.
/// </param>
///
/// <returns>
/// Resource for the sub account with specified ID.
/// </returns>
///
/// <exception cref="ArgumentOutOfRangeException">
/// When <paramref name="subAccountId"/> is less than or equal to zero.
/// </exception>
public IOrganizationSubAccountResource SubAccount(long subAccountId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Mailtrap.Organizations;


/// <summary>
/// Represents organization sub account resource.
/// </summary>
public interface IOrganizationSubAccountResource : IRestResource
{
/// <summary>
/// Deletes the sub account, represented by the current resource instance.
/// </summary>
///
/// <param name="cancellationToken">
/// Token to control operation cancellation.
/// </param>
///
/// <returns>
/// Nothing is returned upon successful deletion.
/// </returns>
///
/// <remarks>
/// <para>
/// Requires sub account management permissions for the organization.
/// </para>
/// <para>
/// Deletion is permanent and removes all data of the sub account.<br />
/// Deleting the last sub account of the organization deletes the organization as well.
/// </para>
/// <para>
/// After deletion the sub account is no longer available, so a repeated call results in a 404 (Not Found) error.
/// </para>
/// <para>
/// The operation is rate limited to 10 requests per minute per organization.
/// </para>
/// </remarks>
public Task Delete(CancellationToken cancellationToken = default);
}
7 changes: 7 additions & 0 deletions src/Mailtrap/Organizations/OrganizationResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ public OrganizationResource(IRestResourceCommandFactory restResourceCommandFacto

public IOrganizationSubAccountCollectionResource SubAccounts()
=> new OrganizationSubAccountCollectionResource(RestResourceCommandFactory, ResourceUri.Append(UrlSegments.SubAccountsSegment));

public IOrganizationSubAccountResource SubAccount(long subAccountId)
{
Ensure.GreaterThanZero(subAccountId, nameof(subAccountId));

return new OrganizationSubAccountResource(RestResourceCommandFactory, ResourceUri.Append(UrlSegments.SubAccountsSegment).Append(subAccountId));
}
}
12 changes: 12 additions & 0 deletions src/Mailtrap/Organizations/OrganizationSubAccountResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Mailtrap.Organizations;


internal sealed class OrganizationSubAccountResource : RestResource, IOrganizationSubAccountResource
{
public OrganizationSubAccountResource(IRestResourceCommandFactory restResourceCommandFactory, Uri resourceUri)
: base(restResourceCommandFactory, resourceUri) { }


public async Task Delete(CancellationToken cancellationToken = default)
=> await DeleteWithStatusCodeResult(cancellationToken).ConfigureAwait(false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,39 @@ public async Task Create_Success()

result.Should().BeEquivalentTo(expectedResponse);
}

[Test]
public async Task Delete_Success()
{
// Arrange
var httpMethod = HttpMethod.Delete;
var subAccountId = TestContext.CurrentContext.Random.NextLong();
var requestUri = _resourceUri.Append(subAccountId).AbsoluteUri;

using var mockHttp = new MockHttpMessageHandler();
mockHttp
.Expect(httpMethod, requestUri)
.WithHeaders("Authorization", $"Bearer {_clientConfig.ApiToken}")
.WithHeaders("Accept", MimeTypes.Application.Json)
.WithHeaders("User-Agent", HeaderValues.UserAgent.ToString())
.Respond(HttpStatusCode.NoContent);

var serviceCollection = new ServiceCollection();
serviceCollection
.AddMailtrapClient(_clientConfig)
.ConfigurePrimaryHttpMessageHandler(() => mockHttp);

using var services = serviceCollection.BuildServiceProvider();
var client = services.GetRequiredService<IMailtrapOrganizationClient>();

// Act
await client
.Organization(_organizationId)
.SubAccount(subAccountId)
.Delete()
.ConfigureAwait(false);

// Assert
mockHttp.VerifyNoOutstandingExpectation();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
namespace Mailtrap.UnitTests.Organizations;


[TestFixture]
internal sealed class OrganizationResourceTests
{
private readonly IRestResourceCommandFactory _commandFactoryMock = Mock.Of<IRestResourceCommandFactory>();
private readonly Uri _resourceUri = EndpointsTestConstants.ApiDefaultUrl
.Append(
UrlSegmentsTestConstants.ApiRootSegment,
UrlSegmentsTestConstants.OrganizationsSegment)
.Append(TestContext.CurrentContext.Random.NextLong());


#region Constructor

[Test]
public void Constructor_ShouldThrowArgumentNullException_WhenCommandFactoryIsNull()
{
// Act
var act = () => new OrganizationResource(null!, _resourceUri);

// Assert
act.Should().Throw<ArgumentNullException>();
}

[Test]
public void Constructor_ShouldThrowArgumentNullException_WhenUriIsNull()
{
// Act
var act = () => new OrganizationResource(_commandFactoryMock, null!);

// Assert
act.Should().Throw<ArgumentNullException>();
}

[Test]
public void ResourceUri_ShouldBeInitializedProperly()
{
// Arrange
var client = CreateResource();

// Assert
client.ResourceUri.Should().Be(_resourceUri);
}

#endregion


#region Sub Accounts

[Test]
public void SubAccounts_ShouldReturnSubAccountCollectionResource()
{
// Arrange
var client = CreateResource();

// Act
var result = client.SubAccounts();

// Assert
ResourceValidator.Validate<IOrganizationSubAccountCollectionResource, OrganizationSubAccountCollectionResource>(
result, client.ResourceUri.Append(UrlSegmentsTestConstants.SubAccountsSegment));
}

[Test]
public void SubAccount_ShouldReturnSubAccountResource()
{
// Arrange
var client = CreateResource();
var subAccountId = TestContext.CurrentContext.Random.NextLong();

// Act
var result = client.SubAccount(subAccountId);

// Assert
ResourceValidator.Validate<IOrganizationSubAccountResource, OrganizationSubAccountResource>(
result, client.ResourceUri.Append(UrlSegmentsTestConstants.SubAccountsSegment).Append(subAccountId));
}

[Test]
public void SubAccount_ShouldThrowOutOfRangeException_WhenIdIsEqualOrLessThanZero([Values(0, -1)] long subAccountId)
{
// Arrange
var client = CreateResource();

// Act
var act = () => client.SubAccount(subAccountId);

// Assert
act.Should().Throw<ArgumentOutOfRangeException>();
}

#endregion



private OrganizationResource CreateResource() => new(_commandFactoryMock, _resourceUri);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace Mailtrap.UnitTests.Organizations;


[TestFixture]
internal sealed class OrganizationSubAccountResourceTests
{
private readonly IRestResourceCommandFactory _commandFactoryMock = Mock.Of<IRestResourceCommandFactory>();
private readonly Uri _resourceUri = EndpointsTestConstants.ApiDefaultUrl
.Append(
UrlSegmentsTestConstants.ApiRootSegment,
UrlSegmentsTestConstants.OrganizationsSegment)
.Append(TestContext.CurrentContext.Random.NextLong())
.Append(UrlSegmentsTestConstants.SubAccountsSegment)
.Append(TestContext.CurrentContext.Random.NextLong());


#region Constructor

[Test]
public void Constructor_ShouldThrowArgumentNullException_WhenCommandFactoryIsNull()
{
// Act
var act = () => new OrganizationSubAccountResource(null!, _resourceUri);

// Assert
act.Should().Throw<ArgumentNullException>();
}

[Test]
public void Constructor_ShouldThrowArgumentNullException_WhenUriIsNull()
{
// Act
var act = () => new OrganizationSubAccountResource(_commandFactoryMock, null!);

// Assert
act.Should().Throw<ArgumentNullException>();
}

[Test]
public void ResourceUri_ShouldBeInitializedProperly()
{
// Arrange
var client = CreateResource();

// Assert
client.ResourceUri.Should().Be(_resourceUri);
}

#endregion



private OrganizationSubAccountResource CreateResource() => new(_commandFactoryMock, _resourceUri);
}