Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSOE-617: Content management API client #53

Merged
merged 8 commits into from
Jul 17, 2024
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
33 changes: 29 additions & 4 deletions Lombiq.OrchardCoreApiClient.Tester/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using Lombiq.OrchardCoreApiClient.Clients;
using Lombiq.OrchardCoreApiClient.Models;
using OrchardCore.ContentManagement;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

namespace Lombiq.OrchardCoreApiClient.Tester;
Expand All @@ -18,18 +21,20 @@ public static class Program
public static async Task Main(string[] arguments)
{
var port = int.TryParse(arguments.FirstOrDefault(), out var customPort) ? customPort : 44335;
using var apiClient = new ApiClient(new ApiClientSettings
var apiClientSettings = new ApiClientSettings
{
ClientId = ClientId,
ClientSecret = ClientSecret,
DefaultTenantUri = new Uri("https://localhost:" + port.ToTechnicalString()),
});
};

using var tenantsApiClient = new TenantsApiClient(apiClientSettings);

// A suffix is used to avoid name clashes on an existing site, as this test doesn't delete tenants.
var suffix = DateTime.Now.Ticks.ToTechnicalString();
var name = "ApiClientTenant" + suffix;

await apiClient.CreateAndSetupTenantAsync(
await tenantsApiClient.CreateAndSetupTenantAsync(
new TenantApiModel
{
Description = "Tenant created by API Client",
Expand Down Expand Up @@ -69,8 +74,28 @@ await apiClient.CreateAndSetupTenantAsync(
};

// Requires Orchard Core 1.6.0 or newer, on a 1.5.0 server this returns a 404 error.
await apiClient.OrchardCoreApi.EditAsync(editModel);
await tenantsApiClient.OrchardCoreApi.EditAsync(editModel);

Console.WriteLine("Editing the tenant succeeded.");

using var contentsApiClient = new ContentsApiClient(apiClientSettings);

var taxonomy = new ContentItem
{
ContentType = "Taxonomy",
DisplayText = "Taxonomy created by API Client",
};

var response = await contentsApiClient.OrchardCoreApi.CreateOrUpdateAsync(taxonomy);
var contentItemIdFromApi = JsonSerializer.Deserialize<ContentItem>(response.Content).ContentItemId;

Console.WriteLine("Creating the taxonomy succeeded.");

taxonomy.ContentItemId = contentItemIdFromApi;
taxonomy.DisplayText = "Taxonomy edited by API Client";

await contentsApiClient.OrchardCoreApi.CreateOrUpdateAsync(taxonomy);

Console.WriteLine("Editing the taxonomy succeeded.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
{
"name": "feature",
"enable": [
"OrchardCore.Contents",
"OrchardCore.ContentTypes",
"OrchardCore.OpenId",
"OrchardCore.OpenId.Management",
"OrchardCore.OpenId.Server",
"OrchardCore.OpenId.Validation",
"OrchardCore.Taxonomies",
"OrchardCore.Tenants"
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using Atata;
using Lombiq.OrchardCoreApiClient.Clients;
using Lombiq.OrchardCoreApiClient.Models;
using Lombiq.Tests.UI.Extensions;
using Lombiq.Tests.UI.Services;
using OpenQA.Selenium;
using OrchardCore.Autoroute.Models;
using OrchardCore.ContentManagement;
using OrchardCore.Taxonomies.Models;
using Shouldly;
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

namespace Lombiq.OrchardCoreApiClient.Tests.UI.Extensions;
Expand Down Expand Up @@ -73,13 +78,15 @@ public static async Task TestOrchardCoreApiClientBehaviorAsync(
Category = "UI Test Tenants - Edited",
};

using var apiClient = new ApiClient(new ApiClientSettings
var apiClientSettings = new ApiClientSettings
{
ClientId = clientId,
ClientSecret = clientSecret,
DefaultTenantUri = context.Scope.BaseUri,
DisableCertificateValidation = true,
});
};

using var tenantsApiClient = new TenantsApiClient(apiClientSettings);

const string defaultClientRecipe = "Lombiq.OrchardCoreApiClient.Tests.UI.OpenId";
context.Scope.AtataContext.Log.Info($"Executing the default client recipe \"{defaultClientRecipe}\": {isDefaultClient}");
Expand All @@ -97,16 +104,33 @@ public static async Task TestOrchardCoreApiClientBehaviorAsync(
await context.SignInDirectlyAsync();
}

await TestTenantCreateAsync(context, apiClient, createApiModel);
await TestTenantSetupAsync(context, apiClient, createApiModel, setupApiModel);
await TestTenantEditAsync(context, apiClient, editModel, setupApiModel);
await TestTenantDisableAsync(context, apiClient, editModel);
await TestTenantRemoveAsync(context, apiClient, editModel);
await TestTenantCreateAsync(context, tenantsApiClient, createApiModel);
await TestTenantSetupAsync(context, tenantsApiClient, createApiModel, setupApiModel);
await TestTenantEditAsync(context, tenantsApiClient, editModel, setupApiModel);
await TestTenantDisableAsync(context, tenantsApiClient, editModel);
await TestTenantRemoveAsync(context, tenantsApiClient, editModel);

var taxonomy = new ContentItem
{
ContentType = "Taxonomy",
DisplayText = "Taxonomy created by UI test",
};

var taxonomyPart = new TaxonomyPart { TermContentType = "Tag" };
var autoRoutePart = new AutoroutePart { RouteContainedItems = true };
taxonomy.Apply(taxonomyPart);
taxonomy.Apply(autoRoutePart);

using var contentsApiClient = new ContentsApiClient(apiClientSettings);

taxonomy.ContentItemId = await TestContentCreateAsync(context, contentsApiClient, taxonomy);
await TestContentGetAsync(contentsApiClient, taxonomy);
await TestContentRemoveAsync(context, contentsApiClient, taxonomy);
}

private static async Task TestTenantCreateAsync(
UITestContext context,
ApiClient apiClient,
TenantsApiClient apiClient,
TenantApiModel createApiModel)
{
using (var response = await apiClient.OrchardCoreApi.CreateAsync(createApiModel))
Expand Down Expand Up @@ -141,7 +165,7 @@ private static async Task TestTenantCreateAsync(

private static async Task TestTenantSetupAsync(
UITestContext context,
ApiClient apiClient,
TenantsApiClient apiClient,
TenantApiModel createApiModel,
TenantSetupApiModel setupApiModel)
{
Expand All @@ -159,7 +183,7 @@ private static async Task TestTenantSetupAsync(

private static async Task TestTenantEditAsync(
UITestContext context,
ApiClient apiClient,
TenantsApiClient apiClient,
TenantApiModel editModel,
TenantSetupApiModel setupApiModel)
{
Expand All @@ -186,7 +210,7 @@ private static async Task TestTenantEditAsync(

private static async Task TestTenantDisableAsync(
UITestContext context,
ApiClient apiClient,
TenantsApiClient apiClient,
TenantApiModel editModel)
{
await apiClient.OrchardCoreApi.DisableAsync(editModel.Name);
Expand All @@ -199,7 +223,7 @@ private static async Task TestTenantDisableAsync(

private static async Task TestTenantRemoveAsync(
UITestContext context,
ApiClient apiClient,
TenantsApiClient apiClient,
TenantApiModel editModel)
{
await apiClient.OrchardCoreApi.RemoveAsync(editModel.Name);
Expand All @@ -209,6 +233,60 @@ private static async Task TestTenantRemoveAsync(
context.Configuration.TestOutputHelper.WriteLine("Removing the tenant succeeded.");
}

private static async Task<string> TestContentCreateAsync(
UITestContext context,
ContentsApiClient apiClient,
ContentItem contentItem)
{
var response = await apiClient.OrchardCoreApi.CreateOrUpdateAsync(contentItem);
var contentItemIdFromApi = JsonSerializer.Deserialize<ContentItem>(response.Content).ContentItemId;
await context.GoToContentItemEditorByIdAsync(contentItemIdFromApi);

context.Get(By.Id("TitlePart_Title")).GetValue().ShouldBe(contentItem.DisplayText);

context.Get(By.Id("AutoroutePart_RouteContainedItems")).GetValue()
.ShouldBe(contentItem.As<AutoroutePart>().RouteContainedItems.ToString().ToLowerFirstLetter());

context.Get(By.CssSelector("#TaxonomyPart_TermContentType option[selected]")).Text
.ShouldBe(contentItem.As<TaxonomyPart>().TermContentType);

return contentItemIdFromApi;
}

private static async Task TestContentGetAsync(
ContentsApiClient apiClient,
ContentItem contentItem)
{
var response = await apiClient.OrchardCoreApi.GetAsync(contentItem.ContentItemId);
var contentItemFromApi = JsonSerializer.Deserialize<ContentItem>(response.Content);

var document = JsonDocument.Parse(response.Content);

var contentItemFromApiAutoroutePart = JsonSerializer.Deserialize<AutoroutePart>(
document.RootElement.GetProperty("AutoroutePart").GetRawText());
var contentItemFromApiTaxonomyPart = JsonSerializer.Deserialize<TaxonomyPart>(
document.RootElement.GetProperty("TaxonomyPart").GetRawText());

contentItemFromApi.DisplayText.ShouldBe(contentItem.DisplayText);
contentItemFromApi.ContentType.ShouldBe(contentItem.ContentType);
contentItemFromApiAutoroutePart.RouteContainedItems.ShouldBe(contentItem.As<AutoroutePart>().RouteContainedItems);
contentItemFromApiTaxonomyPart.TermContentType.ShouldBe(contentItem.As<TaxonomyPart>().TermContentType);
}

private static async Task TestContentRemoveAsync(
UITestContext context,
ContentsApiClient apiClient,
ContentItem contentItem)
{
await context.GoToContentItemListAsync();
context.Exists(By.XPath($"//a[contains(text(), '{contentItem.DisplayText}')]"));

await apiClient.OrchardCoreApi.RemoveAsync(contentItem.ContentItemId);

context.Refresh();
context.Missing(By.XPath($"//a[contains(text(), '{contentItem.DisplayText}')]"));
}

private static async Task GoToTenantUrlAndAssertHeaderAsync(
UITestContext context,
TenantApiModel apiModel,
Expand Down
40 changes: 0 additions & 40 deletions Lombiq.OrchardCoreApiClient/ApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Lombiq.HelpfulLibraries.Refit.Helpers;
using Lombiq.OrchardCoreApiClient.Constants;
using Lombiq.OrchardCoreApiClient.Exceptions;
using Lombiq.OrchardCoreApiClient.Interfaces;
using Lombiq.OrchardCoreApiClient.Models;
Expand All @@ -12,14 +11,6 @@

namespace Lombiq.OrchardCoreApiClient;

public class ApiClient : ApiClient<IOrchardCoreApi>
{
public ApiClient(ApiClientSettings apiClientSettings)
: base(apiClientSettings)
{
}
}

public class ApiClient<TApi> : IDisposable
where TApi : IOrchardCoreApi
{
Expand Down Expand Up @@ -74,37 +65,6 @@ public async Task<TResult> ExecuteWithRetryPolicyAsync<TResult>(
}
}

public async Task CreateAndSetupTenantAsync(
TenantApiModel createApiViewModel,
TenantSetupApiModel setupApiViewModel)
{
if (!Timezones.TimezoneIds.Contains(setupApiViewModel.SiteTimeZone))
{
throw new ApiClientException(
$"Invalid timezone ID {setupApiViewModel.SiteTimeZone}. For the list of all valid timezone IDs " +
"follow this list: https://gist.github.com/jrolstad/5ca7d78dbfe182d7c1be");
}

try
{
using var response = await OrchardCoreApi.CreateAsync(createApiViewModel).ConfigureAwait(false);
await response.EnsureSuccessStatusCodeAsync();
}
catch (ApiException ex)
{
throw new ApiClientException("Tenant creation failed.", ex);
}

try
{
await OrchardCoreApi.SetupAsync(setupApiViewModel).ConfigureAwait(false);
}
catch (ApiException ex)
{
throw new ApiClientException("Tenant setup failed.", ex);
}
}

public void Dispose()
{
Dispose(disposing: true);
Expand Down
12 changes: 12 additions & 0 deletions Lombiq.OrchardCoreApiClient/Clients/ContentsApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Lombiq.OrchardCoreApiClient.Interfaces;
using Lombiq.OrchardCoreApiClient.Models;

namespace Lombiq.OrchardCoreApiClient.Clients;

public class ContentsApiClient : ApiClient<IOrchardCoreContentsApi>
{
public ContentsApiClient(ApiClientSettings apiClientSettings)
: base(apiClientSettings)
{
}
}
47 changes: 47 additions & 0 deletions Lombiq.OrchardCoreApiClient/Clients/TenantsApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Lombiq.OrchardCoreApiClient.Constants;
using Lombiq.OrchardCoreApiClient.Exceptions;
using Lombiq.OrchardCoreApiClient.Interfaces;
using Lombiq.OrchardCoreApiClient.Models;
using Refit;
using System.Threading.Tasks;

namespace Lombiq.OrchardCoreApiClient.Clients;

public class TenantsApiClient : ApiClient<IOrchardCoreTenantsApi>
{
public TenantsApiClient(ApiClientSettings apiClientSettings)
: base(apiClientSettings)
{
}

public async Task CreateAndSetupTenantAsync(
TenantApiModel createApiViewModel,
TenantSetupApiModel setupApiViewModel)
{
if (!Timezones.TimezoneIds.Contains(setupApiViewModel.SiteTimeZone))
{
throw new ApiClientException(
$"Invalid timezone ID {setupApiViewModel.SiteTimeZone}. For the list of all valid timezone IDs " +
"follow this list: https://gist.github.com/jrolstad/5ca7d78dbfe182d7c1be");
}

try
{
using var response = await OrchardCoreApi.CreateAsync(createApiViewModel).ConfigureAwait(false);
await response.EnsureSuccessStatusCodeAsync();
}
catch (ApiException ex)
{
throw new ApiClientException("Tenant creation failed.", ex);
}

try
{
await OrchardCoreApi.SetupAsync(setupApiViewModel).ConfigureAwait(false);
}
catch (ApiException ex)
{
throw new ApiClientException("Tenant setup failed.", ex);
}
}
}
7 changes: 0 additions & 7 deletions Lombiq.OrchardCoreApiClient/Exceptions/ApiClientExceptions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Runtime.Serialization;

namespace Lombiq.OrchardCoreApiClient.Exceptions;

[Serializable]
public class ApiClientException : Exception
{
public ApiClientException()
Expand All @@ -19,9 +17,4 @@ public ApiClientException(string message, Exception exception)
: base(message, exception)
{
}

protected ApiClientException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
Loading