Skip to content

Commit aa98f69

Browse files
committed
Refactor KeycloakRealmResource and enhance validation
The `KeycloakRealmResource` class has been refactored to include null checks in its constructor for parameters `name`, `realmName`, and `parent`. A private field `_parentEndpoint` and a property `ParentEndpoint` have been added. The `Parent` and `RealmName` properties now rely on the constructor for initialization. New unit tests in `KeycloakPublicApiTests.cs` ensure that the constructor throws an `ArgumentNullException` for null parameters. Additional tests validate that the `AddRealm` method correctly handles null values for the builder and realm name, improving input validation across the API.
1 parent 88dff1a commit aa98f69

File tree

3 files changed

+117
-18
lines changed

3 files changed

+117
-18
lines changed

src/Aspire.Hosting.Keycloak/KeycloakRealmResource.cs

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,51 @@ namespace Aspire.Hosting.Keycloak;
88
/// <summary>
99
/// Represents a Keycloak Realm resource.
1010
/// </summary>
11-
/// <param name="name">The name of the realm resource.</param>
12-
/// <param name="realmName">The name of the realm.</param>
13-
/// <param name="parent">The Keycloak server resource associated with this database.</param>
14-
public sealed class KeycloakRealmResource(string name, string realmName, KeycloakResource parent) : Resource(name), IResourceWithParent<KeycloakResource>, IResourceWithConnectionString
11+
public sealed class KeycloakRealmResource : Resource, IResourceWithParent<KeycloakResource>, IResourceWithConnectionString
1512
{
1613
private EndpointReference? _parentEndpoint;
17-
private EndpointReference ParentEndpoint => _parentEndpoint ??= new(Parent, "http");
14+
private EndpointReferenceExpression? _parentUrl;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="KeycloakRealmResource"/> class.
18+
/// </summary>
19+
/// <param name="name">The name of the realm resource.</param>
20+
/// <param name="realmName">The name of the realm.</param>
21+
/// <param name="parent">The Keycloak server resource associated with this database.</param>
22+
public KeycloakRealmResource(string name, string realmName, KeycloakResource parent) : base(name)
23+
{
24+
ArgumentException.ThrowIfNullOrWhiteSpace(realmName);
25+
ArgumentNullException.ThrowIfNull(parent);
26+
27+
RealmName = realmName;
28+
RealmPath = $"realms/{realmName}";
29+
Parent = parent;
30+
}
31+
32+
private EndpointReferenceExpression ParentUrl => _parentUrl ??= ParentEndpoint.Property(EndpointProperty.Url);
33+
34+
/// <summary>
35+
/// Gets the parent endpoint reference.
36+
/// </summary>
37+
public EndpointReference ParentEndpoint => _parentEndpoint ??= new(Parent, "http");
1838

1939
/// <inheritdoc/>
20-
public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create($"{ParentEndpoint.Property(EndpointProperty.Url)}/realms/{RealmName}/");
40+
public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create($"{ParentUrl}/{RealmPath}/");
41+
42+
/// <summary>
43+
/// Gets the base address of the realm.
44+
/// </summary>
45+
public string RealmPath { get; }
2146

2247
/// <summary>
2348
/// Gets the issuer expression for the Keycloak realm.
2449
/// </summary>
25-
public ReferenceExpression IssuerExpression => ReferenceExpression.Create(
26-
$"{ParentEndpoint.Property(EndpointProperty.Url)}/realms/{RealmName}");
50+
public ReferenceExpression IssuerUrlExpression => ReferenceExpression.Create($"{ParentUrl}/{RealmPath}");
2751

2852
/// <summary>
2953
/// Gets or sets the metadata address for the Keycloak realm.
3054
/// </summary>
31-
public string MetadataAddress { get; set; } = ".well-known/openid-configuration";
55+
public string MetadataAddress => ".well-known/openid-configuration";
3256

3357
/// <summary>
3458
/// Gets the metadata address expression for the Keycloak realm.
@@ -38,7 +62,7 @@ public sealed class KeycloakRealmResource(string name, string realmName, Keycloa
3862
/// <summary>
3963
/// Gets or sets the 'authorization_endpoint' for the Keycloak realm.
4064
/// </summary>
41-
public string AuthorizationEndpoint { get; set; } = "protocol/openid-connect/auth";
65+
public string AuthorizationEndpoint => "protocol/openid-connect/auth";
4266

4367
/// <summary>
4468
/// Gets the 'authorization_endpoint' expression for the Keycloak realm.
@@ -48,7 +72,7 @@ public sealed class KeycloakRealmResource(string name, string realmName, Keycloa
4872
/// <summary>
4973
/// Gets or sets the 'token_endpoint' for the Keycloak realm.
5074
/// </summary>
51-
public string TokenEndpoint { get; set; } = "protocol/openid-connect/token";
75+
public string TokenEndpoint => "protocol/openid-connect/token";
5276

5377
/// <summary>
5478
/// Gets the 'token_endpoint' expression for the Keycloak realm.
@@ -58,7 +82,7 @@ public sealed class KeycloakRealmResource(string name, string realmName, Keycloa
5882
/// <summary>
5983
/// Gets or sets the 'introspection_endpoint' for the Keycloak realm.
6084
/// </summary>
61-
public string IntrospectionEndpoint { get; set; } = "protocol/openid-connect/token/introspect";
85+
public string IntrospectionEndpoint => "protocol/openid-connect/token/introspect";
6286

6387
/// <summary>
6488
/// Gets the 'introspection_endpoint' expression for the Keycloak realm.
@@ -68,7 +92,7 @@ public sealed class KeycloakRealmResource(string name, string realmName, Keycloa
6892
/// <summary>
6993
/// Gets or sets 'user_info_endpoint' for the Keycloak realm.
7094
/// </summary>
71-
public string UserInfoEndpoint { get; set; } = "protocol/openid-connect/userinfo";
95+
public string UserInfoEndpoint => "protocol/openid-connect/userinfo";
7296

7397
/// <summary>
7498
/// Gets 'user_info_endpoint' expression for the Keycloak realm.
@@ -78,7 +102,7 @@ public sealed class KeycloakRealmResource(string name, string realmName, Keycloa
78102
/// <summary>
79103
/// Gets or sets the 'end_session_endpoint' for the Keycloak realm.
80104
/// </summary>
81-
public string EndSessionEndpoint { get; set; } = "protocol/openid-connect/logout";
105+
public string EndSessionEndpoint => "protocol/openid-connect/logout";
82106

83107
/// <summary>
84108
/// Gets the 'end_session_endpoint' expression for the Keycloak realm.
@@ -88,18 +112,18 @@ public sealed class KeycloakRealmResource(string name, string realmName, Keycloa
88112
/// <summary>
89113
/// Gets or sets the 'registration_endpoint' for the Keycloak realm.
90114
/// </summary>
91-
public string RegistrationEndpoint { get; set; } = "clients-registrations/openid-connect";
115+
public string RegistrationEndpoint => "clients-registrations/openid-connect";
92116

93117
/// <summary>
94118
/// Gets the 'registration_endpoint' expression for the Keycloak realm.
95119
/// </summary>
96120
public ReferenceExpression RegistrationEndpointExpression => ReferenceExpression.Create($"{ConnectionStringExpression}{RegistrationEndpoint}");
97121

98122
/// <inheritdoc/>
99-
public KeycloakResource Parent { get; } = parent;
123+
public KeycloakResource Parent { get; }
100124

101125
/// <summary>
102126
/// Gets the name of the realm.
103127
/// </summary>
104-
public string RealmName { get; } = realmName;
128+
public string RealmName { get; }
105129
}

src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,24 @@ public static IResourceBuilder<KeycloakResource> WithRealmImport(
181181
/// <param name="builder">The Keycloak server resource builder.</param>
182182
/// <param name="name">The name of the realm.</param>
183183
/// <param name="realmName">The name of the realm. If not provided, the resource name will be used.</param>
184+
/// <param name="import">The directory containing the realm import files or a single import file.</param>
184185
/// <returns>A reference to the <see cref="IResourceBuilder{KeycloakRealmResource}"/>.</returns>
185186
public static IResourceBuilder<KeycloakRealmResource> AddRealm(
186187
this IResourceBuilder<KeycloakResource> builder,
187188
string name,
188-
string? realmName = null)
189+
string? realmName = null,
190+
string? import = null)
189191
{
190192
ArgumentNullException.ThrowIfNull(builder);
191193

192194
// Use the resource name as the realm name if it's not provided
193195
realmName ??= name;
194196

197+
if (import is not null)
198+
{
199+
builder.WithRealmImport(import);
200+
}
201+
195202
var keycloakRealm = new KeycloakRealmResource(name, realmName, builder.Resource);
196203

197204
return builder.ApplicationBuilder.AddResource(keycloakRealm);

tests/Aspire.Hosting.Keycloak.Tests/KeycloakPublicApiTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,49 @@ public void CtorKeycloakResourceShouldThrowWhenAdminPasswordIsNull()
3939
Assert.Equal(nameof(adminPassword), exception.ParamName);
4040
}
4141

42+
[Fact]
43+
public void CtorKeycloakRealmResourceShouldThrowWhenNameIsNull()
44+
{
45+
string name = null!;
46+
var realmName = "realm1";
47+
var builder = TestDistributedApplicationBuilder.Create();
48+
var adminPassword = builder.AddParameter("Password");
49+
var parent = new KeycloakResource("keycloak", default(ParameterResource?), adminPassword.Resource);
50+
51+
var action = () => new KeycloakRealmResource(name, realmName, parent);
52+
53+
var exception = Assert.Throws<ArgumentNullException>(action);
54+
Assert.Equal(nameof(name), exception.ParamName);
55+
}
56+
57+
[Fact]
58+
public void CtorMongoKeycloakRealmResourceShouldThrowWhenRealmNameIsNull()
59+
{
60+
var name = "keycloak";
61+
string realmName = null!;
62+
var builder = TestDistributedApplicationBuilder.Create();
63+
var adminPassword = builder.AddParameter("Password");
64+
var parent = new KeycloakResource("keycloak", default(ParameterResource?), adminPassword.Resource);
65+
66+
var action = () => new KeycloakRealmResource(name, realmName, parent);
67+
68+
var exception = Assert.Throws<ArgumentNullException>(action);
69+
Assert.Equal(nameof(realmName), exception.ParamName);
70+
}
71+
72+
[Fact]
73+
public void CtorMongoKeycloakRealmResourceShouldThrowWhenDatabaseParentIsNull()
74+
{
75+
var name = "keycloak";
76+
var realmName = "realm1";
77+
KeycloakResource parent = null!;
78+
79+
var action = () => new KeycloakRealmResource(name, realmName, parent);
80+
81+
var exception = Assert.Throws<ArgumentNullException>(action);
82+
Assert.Equal(nameof(parent), exception.ParamName);
83+
}
84+
4285
[Fact]
4386
public void AddKeycloakShouldThrowWhenBuilderIsNull()
4487
{
@@ -212,4 +255,29 @@ public void WithRealmImportFileAddsBindMountAnnotation(bool? isReadOnly)
212255
Assert.Equal(ContainerMountType.BindMount, containerAnnotation.Type);
213256
Assert.Equal(isReadOnly ?? false, containerAnnotation.IsReadOnly);
214257
}
258+
259+
[Fact]
260+
public void AddRealmShouldThrowWhenBuilderIsNull()
261+
{
262+
IResourceBuilder<KeycloakResource> builder = null!;
263+
const string name = "realm1";
264+
265+
var action = () => builder.AddRealm(name);
266+
267+
var exception = Assert.Throws<ArgumentNullException>(action);
268+
Assert.Equal(nameof(builder), exception.ParamName);
269+
}
270+
271+
[Fact]
272+
public void AddRealmShouldThrowWhenNameIsNull()
273+
{
274+
var builderResource = TestDistributedApplicationBuilder.Create();
275+
var MongoDB = builderResource.AddKeycloak("realm1");
276+
string name = null!;
277+
278+
var action = () => MongoDB.AddRealm(name);
279+
280+
var exception = Assert.Throws<ArgumentNullException>(action);
281+
Assert.Equal(nameof(name), exception.ParamName);
282+
}
215283
}

0 commit comments

Comments
 (0)