Skip to content

Commit 907e187

Browse files
committed
Include backoffice api example integration test
1 parent 4e75290 commit 907e187

File tree

8 files changed

+152
-60
lines changed

8 files changed

+152
-60
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Umbraco.Cms.Api.Management.Controllers;
3+
using Umbraco.Cms.Api.Management.Routing;
4+
5+
namespace TestingExample.Website.BackofficeApi;
6+
7+
[VersionedApiBackOfficeRoute("exampleapi")]
8+
[ApiExplorerSettings(GroupName = "Example Backoffice API")]
9+
public class ExampleBackofficeApiController
10+
: ManagementApiControllerBase
11+
{
12+
[HttpGet]
13+
public IActionResult GetResource()
14+
{
15+
return Ok(new { Message = "This is an example response from the backoffice API." });
16+
}
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Net;
2+
3+
namespace TestingExample.Website.IntegrationTests;
4+
5+
[Collection(nameof(SqlServerWebsiteFixture))]
6+
public sealed class BackofficeApiTests(SqlServerWebsiteFixture websiteFixture)
7+
{
8+
private readonly SqlServerWebsiteFixture _websiteFixture = websiteFixture;
9+
10+
[Fact]
11+
public async Task ShouldReturnOkResult()
12+
{
13+
// given
14+
var backofficeClient = await _websiteFixture.CreateBackofficeClientAsync(TestContext.Current.CancellationToken);
15+
16+
// when
17+
var response = await backofficeClient.GetAsync("/umbraco/management/api/v1/exampleapi", TestContext.Current.CancellationToken);
18+
19+
// then
20+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
21+
}
22+
}

test/TestingExample.Website.IntegrationTests/HomepageTests.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
namespace TestingExample.Website.IntegrationTests;
44

55
[Collection(nameof(SqlServerWebsiteFixture))]
6-
public class HomepageTests(SqlServerWebsiteFixture websiteFixture)
7-
: IntegrationTestBase(websiteFixture.Website)
6+
public sealed class HomepageTests(SqlServerWebsiteFixture websiteFixture)
87
{
8+
private readonly SqlServerWebsiteFixture _websiteFixture = websiteFixture;
9+
910
[Fact]
1011
public async Task ShouldGetTheHomepage()
1112
{
12-
// given / when
13-
var response = await Client.GetAsync("/", TestContext.Current.CancellationToken);
13+
// given
14+
var client = _websiteFixture.CreateAnonymousClient();
15+
16+
// when
17+
var response = await client.GetAsync("/", TestContext.Current.CancellationToken);
1418

1519
// then
1620
Assert.Equal(HttpStatusCode.OK, response.StatusCode);

test/TestingExample.Website.IntegrationTests/IntegrationTestBase.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.

test/TestingExample.Website.IntegrationTests/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,11 @@ Specifically for xUnit v3, a compromise was necessary. In short: type finder set
3636
}
3737
}
3838
```
39+
40+
## Running tests on the backoffice
41+
42+
The management api is protected, so you can't simply call an endpoint and get a response. You need an authenticated backoffice user to fetch a result from a backoffice api endpoint.
43+
44+
### Relevant source code
45+
- [Example test that tests a backoffice endpoint](./BackofficeApiTests.cs)
46+
- [The BackofficeCredentialsProvider authenticates an HttpClient so that it can acces backoffice APIs](./Website/BackofficeCredentialsProvider.cs)

test/TestingExample.Website.IntegrationTests/TestingExample.Website.IntegrationTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
</ItemGroup>
3232

3333
<ItemGroup>
34+
<PackageReference Include="Duende.IdentityModel" Version="7.1.0" />
3435
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.7" />
3536
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
3637
<PackageReference Include="Scrutor" Version="6.1.0" />
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Duende.IdentityModel.Client;
2+
using Umbraco.Cms.Core.Models;
3+
using Umbraco.Cms.Core.Models.Membership;
4+
using Umbraco.Cms.Core.Security;
5+
using Umbraco.Cms.Core.Services;
6+
using Umbraco.Cms.Core;
7+
8+
namespace TestingExample.Website.IntegrationTests.Website;
9+
10+
public sealed class BackofficeCredentialsProvider(IUserService userService, IBackOfficeUserClientCredentialsManager clientCredentialService)
11+
{
12+
private readonly IUserService _userService = userService;
13+
private readonly IBackOfficeUserClientCredentialsManager _clientCredentialService = clientCredentialService;
14+
15+
private BackofficeCredentials? _credentials = null;
16+
17+
public async Task AuthenticateAsBackofficeUserAsync(HttpClient client, CancellationToken cancellationToken = default)
18+
{
19+
// If no credentials are set, create a new backoffice user and client credentials
20+
_credentials ??= await CreateBackofficeCredentialsAsync();
21+
22+
// Use the credentials to authenticate the HttpClient
23+
await AuthenticateHttpClientAsync(client, _credentials, cancellationToken);
24+
}
25+
26+
private static async Task AuthenticateHttpClientAsync(HttpClient client, BackofficeCredentials credentials, CancellationToken cancellationToken)
27+
{
28+
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
29+
{
30+
Address = "/umbraco/management/api/v1/security/back-office/token",
31+
ClientId = credentials.ClientId,
32+
ClientSecret = credentials.ClientSecret
33+
}, cancellationToken);
34+
35+
if (string.IsNullOrWhiteSpace(tokenResponse.AccessToken)) throw new InvalidOperationException("Failed to obtain access token for backoffice user.");
36+
37+
client.SetBearerToken(tokenResponse.AccessToken);
38+
}
39+
40+
private async Task<BackofficeCredentials> CreateBackofficeCredentialsAsync()
41+
{
42+
var userCreateResult = await _userService.CreateAsync(Constants.Security.SuperUserKey, new UserCreateModel
43+
{
44+
Name = "Test User",
45+
UserName = "test@email.com",
46+
Email = "test@email.com",
47+
Kind = UserKind.Api,
48+
UserGroupKeys = new HashSet<Guid> { Constants.Security.AdminGroupKey }
49+
}, approveUser: true);
50+
51+
var user = userCreateResult.Result.CreatedUser!;
52+
53+
var result = new BackofficeCredentials("umbraco-back-office-" + Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
54+
var clientCredentialResult = await _clientCredentialService.SaveAsync(user.Key, result.ClientId, result.ClientSecret);
55+
56+
if (!clientCredentialResult.Success) throw new InvalidOperationException("Failed to create backoffice client credentials.");
57+
58+
return result;
59+
}
60+
61+
private record BackofficeCredentials(string ClientId, string ClientSecret);
62+
}

test/TestingExample.Website.IntegrationTests/WebsiteFixture.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,54 @@
1+
using System.Text.Json;
2+
using Microsoft.Extensions.DependencyInjection;
13
using TestingExample.Website.IntegrationTests.Database;
24
using TestingExample.Website.IntegrationTests.Website;
5+
using Umbraco.Cms.Core.Security;
6+
using Umbraco.Cms.Core.Services;
37

48
namespace TestingExample.Website.IntegrationTests;
59

6-
public class WebsiteFixture(IDatabaseResource database)
7-
: IAsyncLifetime
10+
public class WebsiteFixture(IDatabaseResource database) : IAsyncLifetime
811
{
912
private readonly IDatabaseResource _database = database;
1013
private readonly WebsiteResource _website = new(database);
1114

15+
private AsyncServiceScope _scope;
16+
private HttpClient? _client = null;
17+
private BackofficeCredentialsProvider? _backofficeCredentialsProvider = null;
18+
1219
public WebsiteResource Website => _website;
1320

21+
public HttpClient Client => _client ??= _website.CreateClient();
22+
23+
private BackofficeCredentialsProvider BackofficeCredentialsProvider => _backofficeCredentialsProvider ??= new BackofficeCredentialsProvider(
24+
_scope.ServiceProvider.GetRequiredService<IUserService>(),
25+
_scope.ServiceProvider.GetRequiredService<IBackOfficeUserClientCredentialsManager>()
26+
);
27+
28+
public HttpClient CreateAnonymousClient()
29+
{
30+
return Website.CreateClient();
31+
}
32+
33+
public async Task<HttpClient> CreateBackofficeClientAsync(CancellationToken cancellationToken = default)
34+
{
35+
var client = Website.CreateClient();
36+
await BackofficeCredentialsProvider.AuthenticateAsBackofficeUserAsync(client, cancellationToken);
37+
return client;
38+
}
39+
1440
public async ValueTask InitializeAsync()
1541
{
1642
await _database.InitializeAsync();
17-
await Website.StartAsync();
43+
await _website.StartAsync();
44+
_scope = Website.Services.CreateAsyncScope();
1845
}
1946

2047
public async ValueTask DisposeAsync()
2148
{
22-
if (_website is not null) await _website.DisposeAsync();
23-
if (_database is not null) await _database.DisposeAsync();
49+
await _scope.DisposeAsync();
50+
await _website.DisposeAsync();
51+
await _database.DisposeAsync();
2452

2553
GC.SuppressFinalize(this);
2654
}
@@ -38,4 +66,4 @@ public sealed class SqlServerWebsiteFixture()
3866
: WebsiteFixture(new SqlServerDatabase())
3967
, ICollectionFixture<SqlServerWebsiteFixture>
4068
{
41-
}
69+
}

0 commit comments

Comments
 (0)