Skip to content

Commit 44eb07a

Browse files
Feat: Application Info in Agent Signature (via IAgentSignatureAugmenter) (#37)
* feat: agent signature augmenter * test: agent signature augmenter * refactor: don't allow null just use an empty dictionary * chore: fix warning
1 parent bd4b735 commit 44eb07a

File tree

8 files changed

+141
-12
lines changed

8 files changed

+141
-12
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Collections.Generic;
2+
3+
namespace EntityDb.Abstractions.Agents;
4+
5+
/// <summary>
6+
/// Represents a type that can augment an agent signature by
7+
/// providing additional, application-specific information.
8+
/// </summary>
9+
public interface IAgentSignatureAugmenter
10+
{
11+
/// <summary>
12+
/// Returns a dictionary of application-specific information.
13+
/// </summary>
14+
/// <returns>A dictionary of application-specific information.</returns>
15+
Dictionary<string, string> GetApplicationInfo();
16+
}

src/EntityDb.Mvc/Agents/HttpContextAgent.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
using EntityDb.Abstractions.ValueObjects;
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.Extensions.Options;
5+
using System.Collections.Generic;
56

67
namespace EntityDb.Mvc.Agents;
78

8-
internal record HttpContextAgent(HttpContext HttpContext, IOptionsFactory<HttpContextAgentSignatureOptions> HttpContextAgentSignatureOptionsFactory) : IAgent
9+
internal record HttpContextAgent
10+
(
11+
HttpContext HttpContext,
12+
IOptionsFactory<HttpContextAgentSignatureOptions> HttpContextAgentSignatureOptionsFactory,
13+
Dictionary<string, string> ApplicationInfo
14+
) : IAgent
915
{
1016
public TimeStamp GetTimeStamp()
1117
{
@@ -14,6 +20,11 @@ public TimeStamp GetTimeStamp()
1420

1521
public object GetSignature(string signatureOptionsName)
1622
{
17-
return HttpContextAgentSignature.GetSnapshot(HttpContext, HttpContextAgentSignatureOptionsFactory.Create(signatureOptionsName));
23+
return HttpContextAgentSignature.GetSnapshot
24+
(
25+
HttpContext,
26+
HttpContextAgentSignatureOptionsFactory.Create(signatureOptionsName),
27+
ApplicationInfo
28+
);
1829
}
1930
}

src/EntityDb.Mvc/Agents/HttpContextAgentAccessor.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,30 @@
33
using EntityDb.Common.Exceptions;
44
using Microsoft.AspNetCore.Http;
55
using Microsoft.Extensions.Options;
6+
using System.Collections.Generic;
67

78
namespace EntityDb.Mvc.Agents;
89

910
internal sealed class HttpContextAgentAccessor : AgentAccessorBase
1011
{
1112
private readonly IHttpContextAccessor _httpContextAccessor;
1213
private readonly IOptionsFactory<HttpContextAgentSignatureOptions> _httpContextAgentOptionsFactory;
14+
private readonly IAgentSignatureAugmenter? _agentSignatureAugmenter;
1315

14-
public HttpContextAgentAccessor(IHttpContextAccessor httpContextAccessor, IOptionsFactory<HttpContextAgentSignatureOptions> httpContextAgentOptionsFactory)
16+
public HttpContextAgentAccessor
17+
(
18+
IHttpContextAccessor httpContextAccessor,
19+
IOptionsFactory<HttpContextAgentSignatureOptions> httpContextAgentOptionsFactory,
20+
IAgentSignatureAugmenter? agentSignatureAugmenter = null
21+
)
1522
{
1623
_httpContextAccessor = httpContextAccessor;
1724
_httpContextAgentOptionsFactory = httpContextAgentOptionsFactory;
25+
_agentSignatureAugmenter = agentSignatureAugmenter;
1826
}
1927

28+
private static readonly Dictionary<string, string> DefaultApplicationInfo = new();
29+
2030
protected override IAgent CreateAgent()
2131
{
2232
var httpContext = _httpContextAccessor.HttpContext;
@@ -26,6 +36,9 @@ protected override IAgent CreateAgent()
2636
throw new NoAgentException();
2737
}
2838

29-
return new HttpContextAgent(httpContext, _httpContextAgentOptionsFactory);
39+
var applicationInfo = _agentSignatureAugmenter?
40+
.GetApplicationInfo() ?? DefaultApplicationInfo;
41+
42+
return new HttpContextAgent(httpContext, _httpContextAgentOptionsFactory, applicationInfo);
3043
}
3144
}

src/EntityDb.Mvc/Agents/HttpContextAgentSignature.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ int LocalPort
5252
public sealed record Snapshot
5353
(
5454
RequestSnapshot Request,
55-
ConnectionSnapshot Connection
55+
ConnectionSnapshot Connection,
56+
Dictionary<string, string> ApplicationInfo
5657
);
5758

5859
private static NameValuesPairSnapshot[] GetNameValuesPairSnapshots(IEnumerable<KeyValuePair<string, StringValues>> dictionary, string[] redactedKeys, string redactedValue)
@@ -92,12 +93,18 @@ private static ConnectionSnapshot GetConnectionSnapshot(ConnectionInfo connectio
9293
);
9394
}
9495

95-
internal static Snapshot GetSnapshot(HttpContext httpContext, HttpContextAgentSignatureOptions httpContextAgentOptions)
96+
internal static Snapshot GetSnapshot
97+
(
98+
HttpContext httpContext,
99+
HttpContextAgentSignatureOptions httpContextAgentOptions,
100+
Dictionary<string, string> applicationInfo
101+
)
96102
{
97103
return new Snapshot
98104
(
99105
GetRequestSnapshot(httpContext.Request, httpContextAgentOptions),
100-
GetConnectionSnapshot(httpContext.Connection)
106+
GetConnectionSnapshot(httpContext.Connection),
107+
applicationInfo
101108
);
102109
}
103110
}

test/EntityDb.Common.Tests/Agents/AgentAccessorTestsBase.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using Shouldly;
55
using System;
66
using System.Collections.Generic;
7+
using Microsoft.Extensions.DependencyInjection.Extensions;
8+
using Moq;
79
using Xunit;
810

911
namespace EntityDb.Common.Tests.Agents;
@@ -19,6 +21,8 @@ protected AgentAccessorTestsBase(IServiceProvider startupServiceProvider) : base
1921

2022
protected abstract void ConfigureActiveAgentAccessor(IServiceCollection serviceCollection, TAgentAccessorConfiguration agentAccessorConfiguration);
2123

24+
protected abstract Dictionary<string, string>? GetApplicationInfo(object agentSignature);
25+
2226
protected abstract IEnumerable<TAgentAccessorConfiguration> GetAgentAccessorOptions();
2327

2428
[Fact]
@@ -109,4 +113,74 @@ public void GivenBackingServiceActive_WhenGettingAgentSignature_ThenReturnAgentS
109113
agentSignature.ShouldNotBeNull();
110114
}
111115
}
116+
117+
[Fact]
118+
public void GivenBackingServiceActiveAndNoSignatureAugmenter_WhenGettingApplicationInfo_ThenReturnEmptyApplicationInfo()
119+
{
120+
foreach (var agentAccessorConfiguration in GetAgentAccessorOptions())
121+
{
122+
// ARRANGE
123+
124+
using var serviceScope = CreateServiceScope(serviceCollection =>
125+
{
126+
ConfigureActiveAgentAccessor(serviceCollection, agentAccessorConfiguration);
127+
128+
serviceCollection.RemoveAll(typeof(IAgentSignatureAugmenter));
129+
});
130+
131+
var agentAccessor = serviceScope.ServiceProvider
132+
.GetRequiredService<IAgentAccessor>();
133+
134+
// ACT
135+
136+
var agentSignature = agentAccessor.GetAgent().GetSignature("").ShouldNotBeNull();
137+
138+
var applicationInfo = GetApplicationInfo(agentSignature);
139+
140+
// ASSERT
141+
142+
applicationInfo.ShouldBeEmpty();
143+
}
144+
}
145+
146+
147+
[Fact]
148+
public void GivenBackingServiceActiveAndHasSignatureAugmenter_WhenGettingApplicationInfo_ThenReturnExpectedApplicationInfo()
149+
{
150+
foreach (var agentAccessorConfiguration in GetAgentAccessorOptions())
151+
{
152+
// ARRANGE
153+
154+
var expectedApplicationInfo = new Dictionary<string, string>
155+
{
156+
["UserId"] = Guid.NewGuid().ToString()
157+
};
158+
159+
var agentSignatureAugmenterMock = new Mock<IAgentSignatureAugmenter>(MockBehavior.Strict);
160+
161+
agentSignatureAugmenterMock
162+
.Setup(x => x.GetApplicationInfo())
163+
.Returns(expectedApplicationInfo);
164+
165+
using var serviceScope = CreateServiceScope(serviceCollection =>
166+
{
167+
ConfigureActiveAgentAccessor(serviceCollection, agentAccessorConfiguration);
168+
169+
serviceCollection.AddSingleton(agentSignatureAugmenterMock.Object);
170+
});
171+
172+
var agentAccessor = serviceScope.ServiceProvider
173+
.GetRequiredService<IAgentAccessor>();
174+
175+
// ACT
176+
177+
var agentSignature = agentAccessor.GetAgent().GetSignature("");
178+
179+
var actualApplicationInfo = GetApplicationInfo(agentSignature);
180+
181+
// ASSERT
182+
183+
actualApplicationInfo.ShouldBe(expectedApplicationInfo);
184+
}
185+
}
112186
}

test/EntityDb.Mvc.Tests/Agents/HttpContextAgentAccessorTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Moq;
66
using System;
77
using System.Collections.Generic;
8+
using EntityDb.Mvc.Agents;
89

910
namespace EntityDb.Mvc.Tests.Agents;
1011

@@ -58,4 +59,11 @@ protected override IEnumerable<HttpContextSeederOptions> GetAgentAccessorOptions
5859
}
5960
};
6061
}
62+
63+
protected override Dictionary<string, string>? GetApplicationInfo(object agentSignature)
64+
{
65+
return agentSignature is not HttpContextAgentSignature.Snapshot httpContextAgentSignature
66+
? null
67+
: httpContextAgentSignature.ApplicationInfo;
68+
}
6169
}

test/EntityDb.Mvc.Tests/Agents/HttpContextAgentSignatureTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void GivenNoRedactedHeaders_WhenHttpContextHasHeader_ThenAgentSignatureHa
3232

3333
// ACT
3434

35-
var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
35+
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);
3636

3737
// ASSERT
3838

@@ -67,7 +67,7 @@ public void GivenRedactedHeader_WhenHttpContextContainsOnlyThatHeader_ThenAgentS
6767

6868
// ACT
6969

70-
var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
70+
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);
7171

7272
// ASSERT
7373

@@ -100,7 +100,7 @@ public void GivenNoRedactedQueryStringParams_WhenHttpContextHasQueryStringParam_
100100

101101
// ACT
102102

103-
var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
103+
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);
104104

105105
// ASSERT
106106

@@ -135,7 +135,7 @@ public void GivenRedactedQueryStringParam_WhenHttpContextContainsOnlyThatQuerySt
135135

136136
// ACT
137137

138-
var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
138+
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);
139139

140140
// ASSERT
141141

test/EntityDb.Mvc.Tests/Seeder/HttpContextSeeder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ private static ConnectionInfo CreateConnectionInfo(HttpContextSeederOptions http
1515

1616
connectionInfoMock
1717
.SetupGet(info => info.Id)
18-
.Returns(Id.NewId().ToString());
18+
.Returns(Id.NewId().ToString()!);
1919

2020
var faker = new Faker();
2121

0 commit comments

Comments
 (0)