Skip to content
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
16 changes: 16 additions & 0 deletions src/EntityDb.Abstractions/Agents/IAgentSignatureAugmenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;

namespace EntityDb.Abstractions.Agents;

/// <summary>
/// Represents a type that can augment an agent signature by
/// providing additional, application-specific information.
/// </summary>
public interface IAgentSignatureAugmenter
{
/// <summary>
/// Returns a dictionary of application-specific information.
/// </summary>
/// <returns>A dictionary of application-specific information.</returns>
Dictionary<string, string> GetApplicationInfo();
}
15 changes: 13 additions & 2 deletions src/EntityDb.Mvc/Agents/HttpContextAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
using EntityDb.Abstractions.ValueObjects;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Collections.Generic;

namespace EntityDb.Mvc.Agents;

internal record HttpContextAgent(HttpContext HttpContext, IOptionsFactory<HttpContextAgentSignatureOptions> HttpContextAgentSignatureOptionsFactory) : IAgent
internal record HttpContextAgent
(
HttpContext HttpContext,
IOptionsFactory<HttpContextAgentSignatureOptions> HttpContextAgentSignatureOptionsFactory,
Dictionary<string, string> ApplicationInfo
) : IAgent
{
public TimeStamp GetTimeStamp()
{
Expand All @@ -14,6 +20,11 @@ public TimeStamp GetTimeStamp()

public object GetSignature(string signatureOptionsName)
{
return HttpContextAgentSignature.GetSnapshot(HttpContext, HttpContextAgentSignatureOptionsFactory.Create(signatureOptionsName));
return HttpContextAgentSignature.GetSnapshot
(
HttpContext,
HttpContextAgentSignatureOptionsFactory.Create(signatureOptionsName),
ApplicationInfo
);
}
}
17 changes: 15 additions & 2 deletions src/EntityDb.Mvc/Agents/HttpContextAgentAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@
using EntityDb.Common.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Collections.Generic;

namespace EntityDb.Mvc.Agents;

internal sealed class HttpContextAgentAccessor : AgentAccessorBase
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptionsFactory<HttpContextAgentSignatureOptions> _httpContextAgentOptionsFactory;
private readonly IAgentSignatureAugmenter? _agentSignatureAugmenter;

public HttpContextAgentAccessor(IHttpContextAccessor httpContextAccessor, IOptionsFactory<HttpContextAgentSignatureOptions> httpContextAgentOptionsFactory)
public HttpContextAgentAccessor
(
IHttpContextAccessor httpContextAccessor,
IOptionsFactory<HttpContextAgentSignatureOptions> httpContextAgentOptionsFactory,
IAgentSignatureAugmenter? agentSignatureAugmenter = null
)
{
_httpContextAccessor = httpContextAccessor;
_httpContextAgentOptionsFactory = httpContextAgentOptionsFactory;
_agentSignatureAugmenter = agentSignatureAugmenter;
}

private static readonly Dictionary<string, string> DefaultApplicationInfo = new();

protected override IAgent CreateAgent()
{
var httpContext = _httpContextAccessor.HttpContext;
Expand All @@ -26,6 +36,9 @@ protected override IAgent CreateAgent()
throw new NoAgentException();
}

return new HttpContextAgent(httpContext, _httpContextAgentOptionsFactory);
var applicationInfo = _agentSignatureAugmenter?
.GetApplicationInfo() ?? DefaultApplicationInfo;

return new HttpContextAgent(httpContext, _httpContextAgentOptionsFactory, applicationInfo);
}
}
13 changes: 10 additions & 3 deletions src/EntityDb.Mvc/Agents/HttpContextAgentSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ int LocalPort
public sealed record Snapshot
(
RequestSnapshot Request,
ConnectionSnapshot Connection
ConnectionSnapshot Connection,
Dictionary<string, string> ApplicationInfo
);

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

internal static Snapshot GetSnapshot(HttpContext httpContext, HttpContextAgentSignatureOptions httpContextAgentOptions)
internal static Snapshot GetSnapshot
(
HttpContext httpContext,
HttpContextAgentSignatureOptions httpContextAgentOptions,
Dictionary<string, string> applicationInfo
)
{
return new Snapshot
(
GetRequestSnapshot(httpContext.Request, httpContextAgentOptions),
GetConnectionSnapshot(httpContext.Connection)
GetConnectionSnapshot(httpContext.Connection),
applicationInfo
);
}
}
74 changes: 74 additions & 0 deletions test/EntityDb.Common.Tests/Agents/AgentAccessorTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Shouldly;
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq;
using Xunit;

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

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

protected abstract Dictionary<string, string>? GetApplicationInfo(object agentSignature);

protected abstract IEnumerable<TAgentAccessorConfiguration> GetAgentAccessorOptions();

[Fact]
Expand Down Expand Up @@ -109,4 +113,74 @@ public void GivenBackingServiceActive_WhenGettingAgentSignature_ThenReturnAgentS
agentSignature.ShouldNotBeNull();
}
}

[Fact]
public void GivenBackingServiceActiveAndNoSignatureAugmenter_WhenGettingApplicationInfo_ThenReturnEmptyApplicationInfo()
{
foreach (var agentAccessorConfiguration in GetAgentAccessorOptions())
{
// ARRANGE

using var serviceScope = CreateServiceScope(serviceCollection =>
{
ConfigureActiveAgentAccessor(serviceCollection, agentAccessorConfiguration);

serviceCollection.RemoveAll(typeof(IAgentSignatureAugmenter));
});

var agentAccessor = serviceScope.ServiceProvider
.GetRequiredService<IAgentAccessor>();

// ACT

var agentSignature = agentAccessor.GetAgent().GetSignature("").ShouldNotBeNull();

var applicationInfo = GetApplicationInfo(agentSignature);

// ASSERT

applicationInfo.ShouldBeEmpty();
}
}


[Fact]
public void GivenBackingServiceActiveAndHasSignatureAugmenter_WhenGettingApplicationInfo_ThenReturnExpectedApplicationInfo()
{
foreach (var agentAccessorConfiguration in GetAgentAccessorOptions())
{
// ARRANGE

var expectedApplicationInfo = new Dictionary<string, string>
{
["UserId"] = Guid.NewGuid().ToString()
};

var agentSignatureAugmenterMock = new Mock<IAgentSignatureAugmenter>(MockBehavior.Strict);

agentSignatureAugmenterMock
.Setup(x => x.GetApplicationInfo())
.Returns(expectedApplicationInfo);

using var serviceScope = CreateServiceScope(serviceCollection =>
{
ConfigureActiveAgentAccessor(serviceCollection, agentAccessorConfiguration);

serviceCollection.AddSingleton(agentSignatureAugmenterMock.Object);
});

var agentAccessor = serviceScope.ServiceProvider
.GetRequiredService<IAgentAccessor>();

// ACT

var agentSignature = agentAccessor.GetAgent().GetSignature("");

var actualApplicationInfo = GetApplicationInfo(agentSignature);

// ASSERT

actualApplicationInfo.ShouldBe(expectedApplicationInfo);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Moq;
using System;
using System.Collections.Generic;
using EntityDb.Mvc.Agents;

namespace EntityDb.Mvc.Tests.Agents;

Expand Down Expand Up @@ -58,4 +59,11 @@ protected override IEnumerable<HttpContextSeederOptions> GetAgentAccessorOptions
}
};
}

protected override Dictionary<string, string>? GetApplicationInfo(object agentSignature)
{
return agentSignature is not HttpContextAgentSignature.Snapshot httpContextAgentSignature
? null
: httpContextAgentSignature.ApplicationInfo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void GivenNoRedactedHeaders_WhenHttpContextHasHeader_ThenAgentSignatureHa

// ACT

var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);

// ASSERT

Expand Down Expand Up @@ -67,7 +67,7 @@ public void GivenRedactedHeader_WhenHttpContextContainsOnlyThatHeader_ThenAgentS

// ACT

var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);

// ASSERT

Expand Down Expand Up @@ -100,7 +100,7 @@ public void GivenNoRedactedQueryStringParams_WhenHttpContextHasQueryStringParam_

// ACT

var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);

// ASSERT

Expand Down Expand Up @@ -135,7 +135,7 @@ public void GivenRedactedQueryStringParam_WhenHttpContextContainsOnlyThatQuerySt

// ACT

var (request, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions);
var (request, _, _) = HttpContextAgentSignature.GetSnapshot(httpContext, httpContextAgentOptions, default!);

// ASSERT

Expand Down
2 changes: 1 addition & 1 deletion test/EntityDb.Mvc.Tests/Seeder/HttpContextSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private static ConnectionInfo CreateConnectionInfo(HttpContextSeederOptions http

connectionInfoMock
.SetupGet(info => info.Id)
.Returns(Id.NewId().ToString());
.Returns(Id.NewId().ToString()!);

var faker = new Faker();

Expand Down