sprints work
This commit is contained in:
@@ -16,6 +16,7 @@ public sealed class IntegrationService
|
||||
private readonly IIntegrationEventPublisher _eventPublisher;
|
||||
private readonly IIntegrationAuditLogger _auditLogger;
|
||||
private readonly IAuthRefResolver _authRefResolver;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ILogger<IntegrationService> _logger;
|
||||
|
||||
public IntegrationService(
|
||||
@@ -24,6 +25,7 @@ public sealed class IntegrationService
|
||||
IIntegrationEventPublisher eventPublisher,
|
||||
IIntegrationAuditLogger auditLogger,
|
||||
IAuthRefResolver authRefResolver,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<IntegrationService> logger)
|
||||
{
|
||||
_repository = repository;
|
||||
@@ -31,11 +33,13 @@ public sealed class IntegrationService
|
||||
_eventPublisher = eventPublisher;
|
||||
_auditLogger = auditLogger;
|
||||
_authRefResolver = authRefResolver;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IntegrationResponse> CreateAsync(CreateIntegrationRequest request, string? userId, string? tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var integration = new Integration
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -51,7 +55,9 @@ public sealed class IntegrationService
|
||||
Tags = request.Tags?.ToList() ?? [],
|
||||
CreatedBy = userId,
|
||||
UpdatedBy = userId,
|
||||
TenantId = tenantId
|
||||
TenantId = tenantId,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
};
|
||||
|
||||
var created = await _repository.CreateAsync(integration, cancellationToken);
|
||||
@@ -62,7 +68,7 @@ public sealed class IntegrationService
|
||||
created.Type,
|
||||
created.Provider,
|
||||
userId,
|
||||
DateTimeOffset.UtcNow), cancellationToken);
|
||||
_timeProvider.GetUtcNow()), cancellationToken);
|
||||
|
||||
await _auditLogger.LogAsync("integration.created", created.Id, userId, new { created.Name, created.Type, created.Provider }, cancellationToken);
|
||||
|
||||
@@ -119,7 +125,7 @@ public sealed class IntegrationService
|
||||
if (request.Tags is not null) integration.Tags = request.Tags.ToList();
|
||||
if (request.Status.HasValue) integration.Status = request.Status.Value;
|
||||
|
||||
integration.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
integration.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
integration.UpdatedBy = userId;
|
||||
|
||||
var updated = await _repository.UpdateAsync(integration, cancellationToken);
|
||||
@@ -128,7 +134,7 @@ public sealed class IntegrationService
|
||||
updated.Id,
|
||||
updated.Name,
|
||||
userId,
|
||||
DateTimeOffset.UtcNow), cancellationToken);
|
||||
_timeProvider.GetUtcNow()), cancellationToken);
|
||||
|
||||
if (oldStatus != updated.Status)
|
||||
{
|
||||
@@ -136,7 +142,7 @@ public sealed class IntegrationService
|
||||
updated.Id,
|
||||
oldStatus,
|
||||
updated.Status,
|
||||
DateTimeOffset.UtcNow), cancellationToken);
|
||||
_timeProvider.GetUtcNow()), cancellationToken);
|
||||
}
|
||||
|
||||
await _auditLogger.LogAsync("integration.updated", updated.Id, userId, new { updated.Name, OldStatus = oldStatus, NewStatus = updated.Status }, cancellationToken);
|
||||
@@ -156,7 +162,7 @@ public sealed class IntegrationService
|
||||
await _eventPublisher.PublishAsync(new IntegrationDeletedEvent(
|
||||
id,
|
||||
userId,
|
||||
DateTimeOffset.UtcNow), cancellationToken);
|
||||
_timeProvider.GetUtcNow()), cancellationToken);
|
||||
|
||||
await _auditLogger.LogAsync("integration.deleted", id, userId, new { integration.Name }, cancellationToken);
|
||||
|
||||
@@ -180,7 +186,7 @@ public sealed class IntegrationService
|
||||
$"No connector plugin available for provider {integration.Provider}",
|
||||
null,
|
||||
TimeSpan.Zero,
|
||||
DateTimeOffset.UtcNow);
|
||||
_timeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
var resolvedSecret = integration.AuthRefUri is not null
|
||||
@@ -189,9 +195,9 @@ public sealed class IntegrationService
|
||||
|
||||
var config = BuildConfig(integration, resolvedSecret);
|
||||
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
var result = await plugin.TestConnectionAsync(config, cancellationToken);
|
||||
var endTime = DateTimeOffset.UtcNow;
|
||||
var endTime = _timeProvider.GetUtcNow();
|
||||
|
||||
// Update integration status based on result
|
||||
var newStatus = result.Success ? IntegrationStatus.Active : IntegrationStatus.Failed;
|
||||
@@ -233,7 +239,7 @@ public sealed class IntegrationService
|
||||
HealthStatus.Unknown,
|
||||
$"No connector plugin available for provider {integration.Provider}",
|
||||
null,
|
||||
DateTimeOffset.UtcNow,
|
||||
_timeProvider.GetUtcNow(),
|
||||
TimeSpan.Zero);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +66,12 @@ public sealed class Integration
|
||||
/// <summary>
|
||||
/// UTC timestamp when the integration was created.
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// UTC timestamp when the integration was last updated.
|
||||
/// </summary>
|
||||
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User or system that created this integration.
|
||||
|
||||
@@ -10,10 +10,12 @@ namespace StellaOps.Integrations.Persistence;
|
||||
public sealed class PostgresIntegrationRepository : IIntegrationRepository
|
||||
{
|
||||
private readonly IntegrationDbContext _context;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresIntegrationRepository(IntegrationDbContext context)
|
||||
public PostgresIntegrationRepository(IntegrationDbContext context, TimeProvider? timeProvider = null)
|
||||
{
|
||||
_context = context;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Integration?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
@@ -93,7 +95,7 @@ public sealed class PostgresIntegrationRepository : IIntegrationRepository
|
||||
{
|
||||
entity.IsDeleted = true;
|
||||
entity.Status = IntegrationStatus.Archived;
|
||||
entity.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
entity.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ namespace StellaOps.Integrations.Plugin.GitHubApp;
|
||||
/// </summary>
|
||||
public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public GitHubAppConnectorPlugin(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public string Name => "github-app";
|
||||
|
||||
public IntegrationType Type => IntegrationType.Scm;
|
||||
@@ -21,7 +28,7 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
|
||||
public async Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
using var client = CreateHttpClient(config);
|
||||
|
||||
@@ -29,7 +36,7 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
{
|
||||
// Call GitHub API to verify authentication
|
||||
var response = await client.GetAsync("/app", cancellationToken);
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -63,7 +70,7 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
return new TestConnectionResult(
|
||||
Success: false,
|
||||
Message: $"Connection failed: {ex.Message}",
|
||||
@@ -78,7 +85,7 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
using var client = CreateHttpClient(config);
|
||||
|
||||
@@ -86,7 +93,7 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
{
|
||||
// Check GitHub API status
|
||||
var response = await client.GetAsync("/rate_limit", cancellationToken);
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -113,7 +120,7 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
["limit"] = limit.ToString(),
|
||||
["percentUsed"] = percentUsed.ToString()
|
||||
},
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
|
||||
@@ -121,17 +128,17 @@ public sealed class GitHubAppConnectorPlugin : IIntegrationConnectorPlugin
|
||||
Status: HealthStatus.Unhealthy,
|
||||
Message: $"GitHub returned {response.StatusCode}",
|
||||
Details: new Dictionary<string, string> { ["statusCode"] = ((int)response.StatusCode).ToString() },
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
return new HealthCheckResult(
|
||||
Status: HealthStatus.Unhealthy,
|
||||
Message: $"Health check failed: {ex.Message}",
|
||||
Details: new Dictionary<string, string> { ["error"] = ex.GetType().Name },
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@ namespace StellaOps.Integrations.Plugin.Harbor;
|
||||
/// </summary>
|
||||
public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public HarborConnectorPlugin(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public string Name => "harbor";
|
||||
|
||||
public IntegrationType Type => IntegrationType.Registry;
|
||||
@@ -22,7 +29,7 @@ public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
|
||||
public async Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
using var client = CreateHttpClient(config);
|
||||
|
||||
@@ -30,7 +37,7 @@ public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
{
|
||||
// Call Harbor health endpoint
|
||||
var response = await client.GetAsync("/api/v2.0/health", cancellationToken);
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -63,7 +70,7 @@ public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
return new TestConnectionResult(
|
||||
Success: false,
|
||||
Message: $"Connection failed: {ex.Message}",
|
||||
@@ -78,14 +85,14 @@ public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
using var client = CreateHttpClient(config);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.GetAsync("/api/v2.0/health", cancellationToken);
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -103,7 +110,7 @@ public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
Status: status,
|
||||
Message: $"Harbor status: {health?.Status}",
|
||||
Details: health?.Components?.ToDictionary(c => c.Name, c => c.Status) ?? new Dictionary<string, string>(),
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
|
||||
@@ -111,17 +118,17 @@ public sealed class HarborConnectorPlugin : IIntegrationConnectorPlugin
|
||||
Status: HealthStatus.Unhealthy,
|
||||
Message: $"Harbor returned {response.StatusCode}",
|
||||
Details: new Dictionary<string, string> { ["statusCode"] = ((int)response.StatusCode).ToString() },
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
return new HealthCheckResult(
|
||||
Status: HealthStatus.Unhealthy,
|
||||
Message: $"Health check failed: {ex.Message}",
|
||||
Details: new Dictionary<string, string> { ["error"] = ex.GetType().Name },
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,13 @@ namespace StellaOps.Integrations.Plugin.InMemory;
|
||||
/// </summary>
|
||||
public sealed class InMemoryConnectorPlugin : IIntegrationConnectorPlugin
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public InMemoryConnectorPlugin(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public string Name => "inmemory";
|
||||
|
||||
public IntegrationType Type => IntegrationType.Registry;
|
||||
@@ -19,12 +26,12 @@ public sealed class InMemoryConnectorPlugin : IIntegrationConnectorPlugin
|
||||
|
||||
public async Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
// Simulate network delay
|
||||
await Task.Delay(100, cancellationToken);
|
||||
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
|
||||
return new TestConnectionResult(
|
||||
Success: true,
|
||||
@@ -40,12 +47,12 @@ public sealed class InMemoryConnectorPlugin : IIntegrationConnectorPlugin
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
// Simulate health check
|
||||
await Task.Delay(50, cancellationToken);
|
||||
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
|
||||
return new HealthCheckResult(
|
||||
Status: HealthStatus.Healthy,
|
||||
@@ -55,7 +62,7 @@ public sealed class InMemoryConnectorPlugin : IIntegrationConnectorPlugin
|
||||
["endpoint"] = config.Endpoint,
|
||||
["uptime"] = "simulated"
|
||||
},
|
||||
CheckedAt: DateTimeOffset.UtcNow,
|
||||
CheckedAt: _timeProvider.GetUtcNow(),
|
||||
Duration: duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public class IntegrationServiceTests
|
||||
_eventPublisherMock.Object,
|
||||
_auditLoggerMock.Object,
|
||||
_authRefResolverMock.Object,
|
||||
TimeProvider.System,
|
||||
NullLogger<IntegrationService>.Instance);
|
||||
}
|
||||
|
||||
@@ -327,6 +328,7 @@ public class IntegrationServiceTests
|
||||
IntegrationType type = IntegrationType.Registry,
|
||||
IntegrationProvider provider = IntegrationProvider.Harbor)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
return new Integration
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -337,7 +339,9 @@ public class IntegrationServiceTests
|
||||
Endpoint = "https://example.com",
|
||||
Description = "Test description",
|
||||
Tags = ["test"],
|
||||
CreatedBy = "test-user"
|
||||
CreatedBy = "test-user",
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user