Skip to content
Merged
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
26 changes: 26 additions & 0 deletions src/Auth0.ManagementApi/Clients/CustomDomainsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Auth0.ManagementApi.Paging;
using Newtonsoft.Json;

namespace Auth0.ManagementApi.Clients;

Expand All @@ -12,6 +14,8 @@ namespace Auth0.ManagementApi.Clients;
/// </summary>
public class CustomDomainsClient : BaseClient, ICustomDomainsClient
{
private readonly JsonConverter[] checkpointConverters = [new CheckpointPagedListConverter<CustomDomain>("custom_domains")];

/// <summary>
/// Initializes a new instance of <see cref="CustomDomainsClient"/>.
/// </summary>
Expand Down Expand Up @@ -56,6 +60,28 @@ public Task<IList<CustomDomain>> GetAllAsync(CancellationToken cancellationToken
return Connection.GetAsync<IList<CustomDomain>>(BuildUri("custom-domains"), DefaultHeaders, cancellationToken: cancellationToken);
}

/// <inheritdoc />
public Task<ICheckpointPagedList<CustomDomain>> GetAllAsync(
CustomDomainsGetAllRequest request,
CheckpointPaginationInfo? checkpointPaginationInfo,
CancellationToken cancellationToken = default)
{
request.ThrowIfNull();

var queryStrings = new Dictionary<string, string>();
queryStrings.AddIfNotEmpty("q", request.Query!);
queryStrings.AddIfNotEmpty("fields", request.Fields!);
queryStrings.AddIfNotEmpty("include_fields", request.IncludeFields.ToString().ToLower());
queryStrings.AddIfNotEmpty("sort", request.Sort!);

if (checkpointPaginationInfo != null)
{
queryStrings["from"] = checkpointPaginationInfo.From?.ToString();
queryStrings["take"] = checkpointPaginationInfo.Take.ToString();
}
return Connection.GetAsync<ICheckpointPagedList<CustomDomain>>(BuildUri("custom-domains", queryStrings), DefaultHeaders, checkpointConverters , cancellationToken: cancellationToken);
}

/// <summary>
/// Retrieves a custom domain status by its ID
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions src/Auth0.ManagementApi/Clients/ICustomDomainsClient.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Auth0.ManagementApi.Paging;

namespace Auth0.ManagementApi.Clients;

using System.Collections.Generic;
Expand Down Expand Up @@ -30,6 +32,15 @@ public interface ICustomDomainsClient
/// </summary>
/// <returns>A <see cref="IList{CustomDomain}"/> containing the details of every custom domain.</returns>
Task<IList<CustomDomain>> GetAllAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Retrieve details on <a href="https://auth0.com/docs/custom-domains"> custom domains </a>.
/// </summary>
/// <param name="request"><see cref="CustomDomainsGetAllRequest"/></param>
/// <param name="checkpointPaginationInfo"><see cref="CheckpointPaginationInfo"/></param>
/// <param name="cancellationToken"><see cref="CancellationToken"/></param>
/// <returns></returns>
Task<ICheckpointPagedList<CustomDomain>> GetAllAsync(CustomDomainsGetAllRequest request, CheckpointPaginationInfo? checkpointPaginationInfo, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a custom domain status by its ID
Expand Down
10 changes: 9 additions & 1 deletion src/Auth0.ManagementApi/Models/CustomDomain/CustomDomain.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
namespace Auth0.ManagementApi.Models;
using Newtonsoft.Json;

namespace Auth0.ManagementApi.Models;

/// <summary>
/// Represents a Custom Domain
/// </summary>
public class CustomDomain : CustomDomainBase
{
/// <summary>
/// Indicates whether this is the default custom domain.
/// There can only be one default custom domain per tenant.
/// </summary>
[JsonProperty("is_default")]
public bool? IsDefault { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Auth0.ManagementApi.Models;

/// <summary>
/// Represents a request to retrieve all custom domains with optional filtering, field selection, and sorting.
/// </summary>
public class CustomDomainsGetAllRequest
{
/// <summary>
/// Query in <a href="http://www.lucenetutorial.com/lucene-query-syntax.html">Lucene query string syntax</a>.
/// </summary>
public string? Query { get; set; }

/// <summary>
/// Comma-separated list of fields to include or exclude
/// (based on value provided for include_fields) in the result.
/// Leave empty to retrieve all fields.
/// </summary>
public string? Fields { get; set; }

/// <summary>
/// Whether specified fields are to be included (true) or excluded (false).
/// </summary>
public bool? IncludeFields { get; set; } = null;

/// <summary>
/// Field to sort by.
/// Only domain:1 (ascending order by domain) is supported at this time.
/// </summary>
public string? Sort { get; set; }
}
161 changes: 160 additions & 1 deletion tests/Auth0.ManagementApi.IntegrationTests/CustomDomainsTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

using Auth0.Core.Exceptions;
using Auth0.IntegrationTests.Shared.CleanUp;
using Auth0.ManagementApi.Clients;
using Auth0.ManagementApi.IntegrationTests.Testing;
using Auth0.ManagementApi.Models;
using Auth0.ManagementApi.Paging;

using FluentAssertions;
using Moq;
using Newtonsoft.Json;
using Xunit;

namespace Auth0.ManagementApi.IntegrationTests;
Expand All @@ -27,11 +32,18 @@ public override async Task DisposeAsync()

public class CustomDomainsTests : IClassFixture<CustomDomainsTestsFixture>
{
private readonly Mock<IManagementConnection> _mockConnection;
private readonly CustomDomainsClient _client;
private readonly CustomDomainsTestsFixture fixture;

public CustomDomainsTests(CustomDomainsTestsFixture fixture)
{
this.fixture = fixture;
_mockConnection = new Mock<IManagementConnection>();
_client = new CustomDomainsClient(
_mockConnection.Object,
new Uri("https://test.auth0.com/api/v2/"),
new Dictionary<string, string>());
}
[Fact]
public async Task Test_custom_domains()
Expand Down Expand Up @@ -79,8 +91,155 @@ public async Task Test_custom_domains()

await fixture.ApiClient.CustomDomains.DeleteAsync(id);

var afterRunCustomDomains = await fixture.ApiClient.CustomDomains.GetAllAsync();
var afterRunCustomDomains =
await fixture.ApiClient.CustomDomains.GetAllAsync(
new CustomDomainsGetAllRequest()
{
Sort = "domain:1"
}, new CheckpointPaginationInfo());

afterRunCustomDomains.Should().NotContain(x => x.CustomDomainId == id);
fixture.UnTrackIdentifier(CleanUpType.CustomDomains, id);
}

/// <summary>
/// Sets up the mock to capture the URI passed to GetAsync. Returns a holder
/// whose <c>Value</c> is populated after the call under test completes.
/// </summary>
private (Func<Uri> GetUri, Func<JsonConverter[]> GetConverters) SetupCapture(
ICheckpointPagedList<CustomDomain> response = null)
{
Uri capturedUri = null;
JsonConverter[] capturedConverters = null;

_mockConnection
.Setup(c => c.GetAsync<ICheckpointPagedList<CustomDomain>>(
It.IsAny<Uri>(),
It.IsAny<IDictionary<string, string>>(),
It.IsAny<JsonConverter[]>(),
It.IsAny<CancellationToken>()))
.Callback<Uri, IDictionary<string, string>, JsonConverter[], CancellationToken>(
(uri, _, converters, _) =>
{
capturedUri = uri;
capturedConverters = converters;
})
.ReturnsAsync(response ?? new CheckpointPagedList<CustomDomain>());

return (() => capturedUri, () => capturedConverters);
}

[Fact]
public async Task GetAllAsync_Throws_When_Request_Is_Null()
{
await Assert.ThrowsAsync<ArgumentNullException>(() =>
_client.GetAllAsync(null!, null));
}

[Fact]
public async Task GetAllAsync_Returns_Result_From_Connection()
{
var expected = new CheckpointPagedList<CustomDomain>
{
new CustomDomain { IsDefault = true },
new CustomDomain { IsDefault = false }
};
SetupCapture(expected);

var result = await _client.GetAllAsync(new CustomDomainsGetAllRequest(), null);

result.Should().HaveCount(2);
}

[Fact]
public async Task GetAllAsync_Calls_Correct_Endpoint()
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(new CustomDomainsGetAllRequest(), null);

getUri().AbsolutePath.Should().EndWith("custom-domains");
}

[Fact]
public async Task GetAllAsync_Includes_Query_In_QueryString_When_Provided()
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(new CustomDomainsGetAllRequest { Query = "domain:example.com" }, null);

getUri().Query.Should().Contain("q=domain%3Aexample.com");
}

[Fact]
public async Task GetAllAsync_Includes_Fields_In_QueryString_When_Provided()
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(new CustomDomainsGetAllRequest { Fields = "domain,status" }, null);

getUri().Query.Should().Contain("fields=domain%2Cstatus");
}

[Theory]
[InlineData(true, "include_fields=true")]
[InlineData(false, "include_fields=false")]
public async Task GetAllAsync_Includes_IncludeFields_In_QueryString_When_Provided(bool includeFields, string expectedParam)
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(new CustomDomainsGetAllRequest { IncludeFields = includeFields }, null);

getUri().Query.Should().Contain(expectedParam);
}

[Fact]
public async Task GetAllAsync_Includes_Sort_In_QueryString_When_Provided()
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(new CustomDomainsGetAllRequest { Sort = "domain:1" }, null);

getUri().Query.Should().Contain("sort=domain%3A1");
}

[Fact]
public async Task GetAllAsync_Includes_Take_And_From_When_CheckpointPaginationInfo_Provided()
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(
new CustomDomainsGetAllRequest(),
new CheckpointPaginationInfo(take: 25, from: "cd_abc123"));

getUri().Query.Should().Contain("take=25");
getUri().Query.Should().Contain("from=cd_abc123");
}

[Fact]
public async Task GetAllAsync_Omits_From_Param_When_CheckpointPaginationInfo_From_Is_Null()
{
var (getUri, _) = SetupCapture();

await _client.GetAllAsync(
new CustomDomainsGetAllRequest(),
new CheckpointPaginationInfo(take: 50));

getUri().Query.Should().Contain("take=50");
getUri().Query.Should().NotContain("from=");
}

[Fact]
public async Task GetAllAsync_Passes_CheckpointConverters_To_Connection()
{
var (_, getConverters) = SetupCapture();

await _client.GetAllAsync(new CustomDomainsGetAllRequest(), null);

getConverters().Should().NotBeNull();
getConverters().Should().HaveCount(1);
getConverters()[0].Should().BeAssignableTo<JsonConverter>();
getConverters()[0].CanConvert(typeof(ICheckpointPagedList<CustomDomain>)).Should().BeTrue();
}

}
Loading