Frontend gaps fill work. Testing fixes work. Auditing in progress.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Integrations.Core;
|
||||
using StellaOps.Integrations.WebService;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Integrations.Tests;
|
||||
|
||||
public class IntegrationPluginLoaderTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void Plugins_ReturnsEmptyInitially()
|
||||
{
|
||||
// Arrange
|
||||
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
|
||||
|
||||
// Act
|
||||
var plugins = loader.Plugins;
|
||||
|
||||
// Assert
|
||||
plugins.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void GetByProvider_WithNoPlugins_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
|
||||
|
||||
// Act
|
||||
var result = loader.GetByProvider(IntegrationProvider.Harbor);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void GetByType_WithNoPlugins_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
|
||||
|
||||
// Act
|
||||
var result = loader.GetByType(IntegrationType.Registry);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadFromDirectory_WithNonExistentDirectory_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
|
||||
|
||||
// Act
|
||||
var result = loader.LoadFromDirectory("/non/existent/path");
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void LoadFromAssemblies_WithEmptyAssemblies_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var loader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
|
||||
|
||||
// Act
|
||||
var result = loader.LoadFromAssemblies([]);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.Integrations.Contracts;
|
||||
using StellaOps.Integrations.Core;
|
||||
using StellaOps.Integrations.Persistence;
|
||||
using StellaOps.Integrations.WebService;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Integrations.Tests;
|
||||
|
||||
public class IntegrationServiceTests
|
||||
{
|
||||
private readonly Mock<IIntegrationRepository> _repositoryMock;
|
||||
private readonly Mock<IIntegrationEventPublisher> _eventPublisherMock;
|
||||
private readonly Mock<IIntegrationAuditLogger> _auditLoggerMock;
|
||||
private readonly Mock<IAuthRefResolver> _authRefResolverMock;
|
||||
private readonly IntegrationPluginLoader _pluginLoader;
|
||||
private readonly IntegrationService _service;
|
||||
|
||||
public IntegrationServiceTests()
|
||||
{
|
||||
_repositoryMock = new Mock<IIntegrationRepository>();
|
||||
_eventPublisherMock = new Mock<IIntegrationEventPublisher>();
|
||||
_auditLoggerMock = new Mock<IIntegrationAuditLogger>();
|
||||
_authRefResolverMock = new Mock<IAuthRefResolver>();
|
||||
_pluginLoader = new IntegrationPluginLoader(NullLogger<IntegrationPluginLoader>.Instance);
|
||||
|
||||
_service = new IntegrationService(
|
||||
_repositoryMock.Object,
|
||||
_pluginLoader,
|
||||
_eventPublisherMock.Object,
|
||||
_auditLoggerMock.Object,
|
||||
_authRefResolverMock.Object,
|
||||
NullLogger<IntegrationService>.Instance);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task CreateAsync_WithValidRequest_CreatesIntegration()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateIntegrationRequest(
|
||||
Name: "Test Registry",
|
||||
Description: "Test description",
|
||||
Type: IntegrationType.Registry,
|
||||
Provider: IntegrationProvider.Harbor,
|
||||
Endpoint: "https://harbor.example.com",
|
||||
AuthRefUri: "authref://vault/harbor#credentials",
|
||||
OrganizationId: "myorg",
|
||||
ExtendedConfig: null,
|
||||
Tags: ["test", "dev"]);
|
||||
|
||||
_repositoryMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<Integration>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<Integration, CancellationToken>((i, _) => Task.FromResult(i));
|
||||
|
||||
// Act
|
||||
var result = await _service.CreateAsync(request, "test-user", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Name.Should().Be("Test Registry");
|
||||
result.Type.Should().Be(IntegrationType.Registry);
|
||||
result.Provider.Should().Be(IntegrationProvider.Harbor);
|
||||
result.Status.Should().Be(IntegrationStatus.Pending);
|
||||
result.Endpoint.Should().Be("https://harbor.example.com");
|
||||
|
||||
_repositoryMock.Verify(r => r.CreateAsync(
|
||||
It.IsAny<Integration>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_eventPublisherMock.Verify(e => e.PublishAsync(
|
||||
It.IsAny<IntegrationCreatedEvent>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_auditLoggerMock.Verify(a => a.LogAsync(
|
||||
"integration.created",
|
||||
It.IsAny<Guid>(),
|
||||
"test-user",
|
||||
It.IsAny<object>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_WithExistingId_ReturnsIntegration()
|
||||
{
|
||||
// Arrange
|
||||
var integration = CreateTestIntegration();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(integration.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(integration);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetByIdAsync(integration.Id);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Id.Should().Be(integration.Id);
|
||||
result.Name.Should().Be(integration.Name);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_WithNonExistingId_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((Integration?)null);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetByIdAsync(id);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task ListAsync_WithFilters_ReturnsFilteredResults()
|
||||
{
|
||||
// Arrange
|
||||
var integrations = new[]
|
||||
{
|
||||
CreateTestIntegration(type: IntegrationType.Registry),
|
||||
CreateTestIntegration(type: IntegrationType.Registry),
|
||||
CreateTestIntegration(type: IntegrationType.Scm)
|
||||
};
|
||||
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetAllAsync(
|
||||
It.Is<IntegrationQuery>(q => q.Type == IntegrationType.Registry),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(integrations.Where(i => i.Type == IntegrationType.Registry).ToList());
|
||||
|
||||
_repositoryMock
|
||||
.Setup(r => r.CountAsync(
|
||||
It.Is<IntegrationQuery>(q => q.Type == IntegrationType.Registry),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(2);
|
||||
|
||||
var query = new ListIntegrationsQuery(Type: IntegrationType.Registry);
|
||||
|
||||
// Act
|
||||
var result = await _service.ListAsync(query, "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Items.Should().HaveCount(2);
|
||||
result.Items.Should().OnlyContain(i => i.Type == IntegrationType.Registry);
|
||||
result.TotalCount.Should().Be(2);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task UpdateAsync_WithExistingIntegration_UpdatesAndPublishesEvent()
|
||||
{
|
||||
// Arrange
|
||||
var integration = CreateTestIntegration();
|
||||
var request = new UpdateIntegrationRequest(
|
||||
Name: "Updated Name",
|
||||
Description: "Updated description",
|
||||
Endpoint: "https://updated.example.com",
|
||||
AuthRefUri: null,
|
||||
OrganizationId: null,
|
||||
ExtendedConfig: null,
|
||||
Tags: ["updated"],
|
||||
Status: null);
|
||||
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(integration.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(integration);
|
||||
_repositoryMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<Integration>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<Integration, CancellationToken>((i, _) => Task.FromResult(i));
|
||||
|
||||
// Act
|
||||
var result = await _service.UpdateAsync(integration.Id, request, "test-user");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Name.Should().Be("Updated Name");
|
||||
result.Description.Should().Be("Updated description");
|
||||
result.Endpoint.Should().Be("https://updated.example.com");
|
||||
|
||||
_eventPublisherMock.Verify(e => e.PublishAsync(
|
||||
It.IsAny<IntegrationUpdatedEvent>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task UpdateAsync_WithNonExistingIntegration_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((Integration?)null);
|
||||
|
||||
var request = new UpdateIntegrationRequest(
|
||||
Name: "Updated", Description: null, Endpoint: null,
|
||||
AuthRefUri: null, OrganizationId: null, ExtendedConfig: null,
|
||||
Tags: null, Status: null);
|
||||
|
||||
// Act
|
||||
var result = await _service.UpdateAsync(id, request, "test-user");
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task DeleteAsync_WithExistingIntegration_DeletesAndPublishesEvent()
|
||||
{
|
||||
// Arrange
|
||||
var integration = CreateTestIntegration();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(integration.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(integration);
|
||||
_repositoryMock
|
||||
.Setup(r => r.DeleteAsync(integration.Id, It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _service.DeleteAsync(integration.Id, "test-user");
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
_repositoryMock.Verify(r => r.DeleteAsync(integration.Id, It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_eventPublisherMock.Verify(e => e.PublishAsync(
|
||||
It.IsAny<IntegrationDeletedEvent>(),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task DeleteAsync_WithNonExistingIntegration_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((Integration?)null);
|
||||
|
||||
// Act
|
||||
var result = await _service.DeleteAsync(id, "test-user");
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task TestConnectionAsync_WithNoPlugin_ReturnsFailureResult()
|
||||
{
|
||||
// Arrange
|
||||
var integration = CreateTestIntegration(provider: IntegrationProvider.Harbor);
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(integration.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(integration);
|
||||
|
||||
// No plugins loaded in _pluginLoader
|
||||
|
||||
// Act
|
||||
var result = await _service.TestConnectionAsync(integration.Id, "test-user");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Success.Should().BeFalse();
|
||||
result.Message.Should().Contain("No connector plugin");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task TestConnectionAsync_WithNonExistingIntegration_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((Integration?)null);
|
||||
|
||||
// Act
|
||||
var result = await _service.TestConnectionAsync(id, "test-user");
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task CheckHealthAsync_WithNoPlugin_ReturnsUnknownStatus()
|
||||
{
|
||||
// Arrange
|
||||
var integration = CreateTestIntegration();
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByIdAsync(integration.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(integration);
|
||||
|
||||
// No plugins loaded
|
||||
|
||||
// Act
|
||||
var result = await _service.CheckHealthAsync(integration.Id);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Status.Should().Be(HealthStatus.Unknown);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public void GetSupportedProviders_WithNoPlugins_ReturnsEmpty()
|
||||
{
|
||||
// Act
|
||||
var result = _service.GetSupportedProviders();
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
private static Integration CreateTestIntegration(
|
||||
IntegrationType type = IntegrationType.Registry,
|
||||
IntegrationProvider provider = IntegrationProvider.Harbor)
|
||||
{
|
||||
return new Integration
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Test Integration",
|
||||
Type = type,
|
||||
Provider = provider,
|
||||
Status = IntegrationStatus.Active,
|
||||
Endpoint = "https://example.com",
|
||||
Description = "Test description",
|
||||
Tags = ["test"],
|
||||
CreatedBy = "test-user"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Integrations.WebService/StellaOps.Integrations.WebService.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Integrations.Contracts/StellaOps.Integrations.Contracts.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Integrations.Core/StellaOps.Integrations.Core.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user