shoc-backend/Api.SeaHavenIndustries.Tests/WorkOrderWebhookSecretProviderTests.cs
2026-07-24 21:03:50 -03:00

98 lines
3.6 KiB
C#

using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
using Api.SeaHavenIndustries.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using SeaHaven.Services.Configuration;
using SeaHaven.Services.Interfaces;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public sealed class WorkOrderWebhookSecretProviderTests
{
[Fact]
public async Task Known_key_is_cached_and_callers_receive_independent_secret_copies()
{
var client = new Mock<IAmazonSecretsManager>(MockBehavior.Strict);
client.Setup(c => c.GetSecretValueAsync(
It.Is<GetSecretValueRequest>(r => r.SecretId == "arn:configured"),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new GetSecretValueResponse { SecretString = "shared-secret" });
var provider = CreateProvider(client.Object);
var first = await provider.GetSecretAsync("current", CancellationToken.None);
first.Secret![0] = 0;
var second = await provider.GetSecretAsync("current", CancellationToken.None);
Assert.Equal(WorkOrderWebhookSecretStatus.Found, second.Status);
Assert.Equal("shared-secret", System.Text.Encoding.UTF8.GetString(second.Secret!));
client.VerifyAll();
client.Verify(
c => c.GetSecretValueAsync(
It.IsAny<GetSecretValueRequest>(),
It.IsAny<CancellationToken>()),
Times.Once);
}
[Fact]
public async Task Unknown_key_never_influences_an_aws_request()
{
var client = new Mock<IAmazonSecretsManager>(MockBehavior.Strict);
var provider = CreateProvider(client.Object);
var result = await provider.GetSecretAsync(
"attacker-controlled-key",
CancellationToken.None);
Assert.Equal(WorkOrderWebhookSecretStatus.UnknownKey, result.Status);
client.VerifyNoOtherCalls();
}
[Fact]
public async Task Aws_failure_returns_unavailable_without_exposing_the_exception()
{
var client = new Mock<IAmazonSecretsManager>(MockBehavior.Strict);
client.Setup(c => c.GetSecretValueAsync(
It.IsAny<GetSecretValueRequest>(),
It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("sensitive provider detail"));
var provider = CreateProvider(client.Object);
var result = await provider.GetSecretAsync("current", CancellationToken.None);
Assert.Equal(WorkOrderWebhookSecretStatus.Unavailable, result.Status);
Assert.Null(result.Secret);
}
private static AwsWorkOrderWebhookSecretProvider CreateProvider(
IAmazonSecretsManager client) =>
new(
client,
new TestOptionsMonitor(new WorkOrderWebhookOptions
{
SecretCacheSeconds = 300,
KeySecrets = new Dictionary<string, string>
{
["current"] = "arn:configured"
}
}),
TimeProvider.System,
NullLogger<AwsWorkOrderWebhookSecretProvider>.Instance);
private sealed class TestOptionsMonitor : IOptionsMonitor<WorkOrderWebhookOptions>
{
public TestOptionsMonitor(WorkOrderWebhookOptions currentValue)
{
CurrentValue = currentValue;
}
public WorkOrderWebhookOptions CurrentValue { get; }
public WorkOrderWebhookOptions Get(string? name) => CurrentValue;
public IDisposable? OnChange(
Action<WorkOrderWebhookOptions, string?> listener) => null;
}
}