Frontend gaps fill work. Testing fixes work. Auditing in progress.

This commit is contained in:
StellaOps Bot
2025-12-30 01:22:58 +02:00
parent 1dc4bcbf10
commit 7a5210e2aa
928 changed files with 183942 additions and 3941 deletions

View File

@@ -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();
}
}

View File

@@ -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"
};
}
}

View File

@@ -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>