241 lines
7.6 KiB
C#
241 lines
7.6 KiB
C#
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using SlpModularCms.Modules.Availability.Data.Entities;
|
|
using SlpModularCms.Modules.Availability.Repositories;
|
|
using SlpModularCms.Modules.Availability.Services;
|
|
|
|
namespace SlpModularCms.Modules.Availability.Tests.Services;
|
|
|
|
public class MasterAvailabilityServiceTests : IDisposable
|
|
{
|
|
private readonly IMasterRegistrationRepository _repo;
|
|
private readonly IMasterApiKeyProtector _protector;
|
|
private readonly MasterAvailabilityService _svc;
|
|
|
|
public MasterAvailabilityServiceTests()
|
|
{
|
|
_repo = Substitute.For<IMasterRegistrationRepository>();
|
|
_protector = Substitute.For<IMasterApiKeyProtector>();
|
|
|
|
var deps = new MasterAvailabilityServiceDependencies(
|
|
_repo,
|
|
_protector,
|
|
NullLogger<MasterAvailabilityService>.Instance);
|
|
|
|
_svc = new MasterAvailabilityService(deps);
|
|
MasterAvailabilityService.ResetStaticCacheForTest();
|
|
}
|
|
|
|
public void Dispose() => MasterAvailabilityService.ResetStaticCacheForTest();
|
|
|
|
// --- RegisterAsync ---
|
|
|
|
[Fact]
|
|
public async Task RegisterAsync_CreatesNewRegistration_WhenNoneExists()
|
|
{
|
|
_repo.GetAsync().Returns((MasterRegistration?)null);
|
|
_protector.Protect("key123").Returns("enc:key123");
|
|
|
|
var result = await _svc.RegisterAsync("https://master.example.com", "key123");
|
|
|
|
result.Should().BeTrue();
|
|
await _repo.Received(1).AddAsync(Arg.Is<MasterRegistration>(r =>
|
|
r.MasterUrl == "https://master.example.com" &&
|
|
r.ApiKey == "enc:key123" &&
|
|
r.Id == MasterRegistration.SingletonId));
|
|
await _repo.Received(1).SaveChangesAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RegisterAsync_ReturnsTrue_OnReregistrationWithMatchingKey()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://old.example.com",
|
|
ApiKey = "enc:key123",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key123").Returns("key123");
|
|
|
|
var result = await _svc.RegisterAsync("https://new.example.com", "key123");
|
|
|
|
result.Should().BeTrue();
|
|
existing.MasterUrl.Should().Be("https://new.example.com");
|
|
_repo.Received(1).Update(existing);
|
|
await _repo.Received(1).SaveChangesAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RegisterAsync_ReturnsFalse_WhenKeyMismatch()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "enc:key123",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key123").Returns("key123");
|
|
|
|
var result = await _svc.RegisterAsync("https://master.example.com", "wrong-key");
|
|
|
|
result.Should().BeFalse();
|
|
await _repo.DidNotReceive().SaveChangesAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RegisterAsync_ReturnsFalse_WhenDecryptionFails()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "corrupt",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("corrupt").Returns((string?)null);
|
|
|
|
var result = await _svc.RegisterAsync("https://master.example.com", "key123");
|
|
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
// --- PushStatusAsync ---
|
|
|
|
[Fact]
|
|
public async Task PushStatusAsync_UpdatesStaticCache_WhenKeyValid()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "enc:key",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key").Returns("key");
|
|
|
|
var result = await _svc.PushStatusAsync("key", false, "Under maintenance");
|
|
|
|
result.Should().BeTrue();
|
|
var status = _svc.GetMasterStatus();
|
|
status.IsAvailable.Should().BeFalse();
|
|
status.DisableMessage.Should().Be("Under maintenance");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PushStatusAsync_ReturnsFalse_WhenNoRegistration()
|
|
{
|
|
_repo.GetAsync().Returns((MasterRegistration?)null);
|
|
|
|
var result = await _svc.PushStatusAsync("key", false, null);
|
|
|
|
result.Should().BeFalse();
|
|
await _repo.DidNotReceive().SaveChangesAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PushStatusAsync_ReturnsFalse_WhenKeyMismatch()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "enc:key",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key").Returns("correct-key");
|
|
|
|
var result = await _svc.PushStatusAsync("wrong-key", false, null);
|
|
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
// --- GetRegisteredUrlAsync ---
|
|
|
|
[Fact]
|
|
public async Task GetRegisteredUrlAsync_ReturnsMasterUrl_WhenKeyValid()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "enc:key",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key").Returns("key");
|
|
|
|
var result = await _svc.GetRegisteredUrlAsync("key");
|
|
|
|
result.Should().Be("https://master.example.com");
|
|
_repo.Received(1).Update(existing);
|
|
await _repo.Received(1).SaveChangesAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetRegisteredUrlAsync_ReturnsNull_WhenNoRegistration()
|
|
{
|
|
_repo.GetAsync().Returns((MasterRegistration?)null);
|
|
|
|
var result = await _svc.GetRegisteredUrlAsync("key");
|
|
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetRegisteredUrlAsync_ReturnsNull_WhenKeyMismatch()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "enc:key",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key").Returns("correct-key");
|
|
|
|
var result = await _svc.GetRegisteredUrlAsync("wrong-key");
|
|
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
// --- GetMasterStatus ---
|
|
|
|
[Fact]
|
|
public async Task GetMasterStatus_ReflectsLastPushedStatus()
|
|
{
|
|
var existing = new MasterRegistration
|
|
{
|
|
Id = MasterRegistration.SingletonId,
|
|
MasterUrl = "https://master.example.com",
|
|
ApiKey = "enc:key",
|
|
RegisteredAt = DateTimeOffset.UtcNow
|
|
};
|
|
_repo.GetAsync().Returns(existing);
|
|
_protector.Unprotect("enc:key").Returns("key");
|
|
|
|
await _svc.PushStatusAsync("key", false, "Down for maintenance");
|
|
var status = _svc.GetMasterStatus();
|
|
|
|
status.IsAvailable.Should().BeFalse();
|
|
status.DisableMessage.Should().Be("Down for maintenance");
|
|
}
|
|
|
|
[Fact]
|
|
public void GetMasterStatus_ReturnsAvailableByDefault()
|
|
{
|
|
var status = _svc.GetMasterStatus();
|
|
|
|
status.IsAvailable.Should().BeTrue();
|
|
status.DisableMessage.Should().BeNull();
|
|
}
|
|
}
|