Skip to content

Commit fbb9f47

Browse files
add test for circular dependencies
1 parent 15629c0 commit fbb9f47

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using Duende.AccessTokenManagement.OpenIdConnect;
5+
using Duende.IdentityModel;
6+
using Duende.IdentityModel.Client;
7+
using Microsoft.AspNetCore.Authentication;
8+
using Microsoft.AspNetCore.Authentication.Cookies;
9+
using Microsoft.Extensions.DependencyInjection;
10+
11+
namespace Duende.AccessTokenManagement;
12+
13+
/// <summary>
14+
/// Tests that verify the DI registration does not create circular dependencies.
15+
/// See https://github.com/DuendeSoftware/foss/pull/347 for context.
16+
/// </summary>
17+
public class CircularDependencyTests
18+
{
19+
/// <summary>
20+
/// Reproduces the circular dependency described in PR #347:
21+
///
22+
/// IClientAssertionService (user impl)
23+
/// → IOpenIdConnectConfigurationService
24+
/// → IOptionsMonitor&lt;OpenIdConnectOptions&gt;
25+
/// → IConfigureOptions&lt;OpenIdConnectOptions&gt; (ConfigureOpenIdConnectOptions)
26+
/// → IClientAssertionService ← CYCLE
27+
///
28+
/// The fix in ConfigureOpenIdConnectOptions resolves IClientAssertionService
29+
/// lazily via IServiceProvider instead of constructor injection, breaking the cycle.
30+
/// </summary>
31+
[Fact]
32+
public void IClientAssertionService_depending_on_IOpenIdConnectConfigurationService_should_not_cause_circular_dependency()
33+
{
34+
var services = new ServiceCollection();
35+
36+
// Register authentication with an OpenIdConnect scheme (minimal setup).
37+
services.AddAuthentication(options =>
38+
{
39+
options.DefaultChallengeScheme = "oidc";
40+
options.DefaultSignInScheme = "cookie";
41+
})
42+
.AddCookie("cookie")
43+
.AddOpenIdConnect("oidc", options =>
44+
{
45+
options.Authority = "https://demo.duendesoftware.com";
46+
options.ClientId = "test-client";
47+
options.ClientSecret = "secret";
48+
});
49+
50+
// Register ATM's OpenIdConnect services (includes ConfigureOpenIdConnectOptions).
51+
services.AddOpenIdConnectAccessTokenManagement();
52+
53+
// Register a custom IClientAssertionService that depends on
54+
// IOpenIdConnectConfigurationService — the exact pattern from the
55+
// WebJarJwt sample that triggered the circular dependency before the fix.
56+
services.AddTransient<IClientAssertionService, ClientAssertionServiceWithOidcDependency>();
57+
58+
// ValidateOnBuild detects circular dependencies at container build time.
59+
// Before the fix, this would throw:
60+
// "A circular dependency was detected for the service of type
61+
// 'Duende.AccessTokenManagement.IClientAssertionService'."
62+
var act = () => services.BuildServiceProvider(new ServiceProviderOptions
63+
{
64+
ValidateOnBuild = true,
65+
ValidateScopes = true,
66+
});
67+
68+
act.ShouldNotThrow();
69+
}
70+
71+
/// <summary>
72+
/// A test implementation of IClientAssertionService that depends on
73+
/// IOpenIdConnectConfigurationService, reproducing the dependency chain
74+
/// from the WebJarJwt sample that caused the circular dependency.
75+
/// </summary>
76+
private sealed class ClientAssertionServiceWithOidcDependency(
77+
IOpenIdConnectConfigurationService configurationService) : IClientAssertionService
78+
{
79+
// Keep a reference to prove DI resolved the dependency successfully.
80+
private readonly IOpenIdConnectConfigurationService _configurationService = configurationService;
81+
82+
public Task<ClientAssertion?> GetClientAssertionAsync(
83+
ClientCredentialsClientName? clientName = null,
84+
TokenRequestParameters? parameters = null,
85+
CancellationToken ct = default) =>
86+
Task.FromResult<ClientAssertion?>(null);
87+
}
88+
}

0 commit comments

Comments
 (0)