save progress

This commit is contained in:
StellaOps Bot
2025-12-18 09:10:36 +02:00
parent b4235c134c
commit 28823a8960
169 changed files with 11995 additions and 449 deletions

View File

@@ -0,0 +1,155 @@
using System.Net;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Scheduler.Storage.Postgres.Models;
using StellaOps.Scheduler.Storage.Postgres.Repositories;
namespace StellaOps.Scheduler.WebService.Tests;
public sealed class FailureSignatureEndpointTests : IClassFixture<SchedulerWebApplicationFactory>
{
private readonly SchedulerWebApplicationFactory _factory;
public FailureSignatureEndpointTests(SchedulerWebApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task BestMatch_WhenMissing_ReturnsNoContent()
{
var repository = new StubFailureSignatureRepository(match: null);
using var factory = _factory.WithWebHostBuilder(builder =>
builder.ConfigureServices(services =>
{
services.RemoveAll<IFailureSignatureRepository>();
services.AddSingleton<IFailureSignatureRepository>(repository);
}));
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Tenant-Id", "tenant-failure-signatures");
client.DefaultRequestHeaders.Add("X-Scopes", "scheduler.runs.read");
var response = await client.GetAsync("/api/v1/scheduler/failure-signatures/best-match?scopeType=repo&scopeId=acme/repo&toolchainHash=tch_123");
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
Assert.NotNull(repository.LastCall);
Assert.Equal(FailureSignatureScopeType.Repo, repository.LastCall!.Value.ScopeType);
Assert.Equal("acme/repo", repository.LastCall!.Value.ScopeId);
Assert.Equal("tch_123", repository.LastCall!.Value.ToolchainHash);
}
[Fact]
public async Task BestMatch_WhenPresent_ReturnsPayload()
{
var signatureId = Guid.Parse("e22132b0-2aa7-4cde-94a9-0b335d321c61");
var firstSeen = new DateTimeOffset(2025, 12, 18, 10, 0, 0, TimeSpan.Zero);
var lastSeen = firstSeen.AddMinutes(5);
var signature = new FailureSignatureEntity
{
SignatureId = signatureId,
TenantId = "tenant-failure-signatures",
ScopeType = FailureSignatureScopeType.Repo,
ScopeId = "acme/repo",
ToolchainHash = "tch_123",
ErrorCode = "E123",
ErrorCategory = ErrorCategory.Network,
OccurrenceCount = 7,
FirstSeenAt = firstSeen,
LastSeenAt = lastSeen,
PredictedOutcome = PredictedOutcome.Fail,
ConfidenceScore = 0.85m
};
var repository = new StubFailureSignatureRepository(signature);
using var factory = _factory.WithWebHostBuilder(builder =>
builder.ConfigureServices(services =>
{
services.RemoveAll<IFailureSignatureRepository>();
services.AddSingleton<IFailureSignatureRepository>(repository);
}));
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Tenant-Id", "tenant-failure-signatures");
client.DefaultRequestHeaders.Add("X-Scopes", "scheduler.runs.read");
var response = await client.GetAsync("/api/v1/scheduler/failure-signatures/best-match?scopeType=repo&scopeId=acme/repo&toolchainHash=tch_123");
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(signatureId, payload.GetProperty("signatureId").GetGuid());
Assert.Equal("repo", payload.GetProperty("scopeType").GetString());
Assert.Equal("acme/repo", payload.GetProperty("scopeId").GetString());
Assert.Equal("tch_123", payload.GetProperty("toolchainHash").GetString());
Assert.Equal("E123", payload.GetProperty("errorCode").GetString());
Assert.Equal("network", payload.GetProperty("errorCategory").GetString());
Assert.Equal("fail", payload.GetProperty("predictedOutcome").GetString());
Assert.Equal(7, payload.GetProperty("occurrenceCount").GetInt32());
Assert.Equal(0.85m, payload.GetProperty("confidenceScore").GetDecimal());
Assert.Equal(firstSeen, payload.GetProperty("firstSeenAt").GetDateTimeOffset());
Assert.Equal(lastSeen, payload.GetProperty("lastSeenAt").GetDateTimeOffset());
}
private sealed class StubFailureSignatureRepository : IFailureSignatureRepository
{
private readonly FailureSignatureEntity? _match;
public StubFailureSignatureRepository(FailureSignatureEntity? match)
{
_match = match;
}
public (string TenantId, FailureSignatureScopeType ScopeType, string ScopeId, string? ToolchainHash)? LastCall { get; private set; }
public Task<FailureSignatureEntity> CreateAsync(FailureSignatureEntity signature, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<FailureSignatureEntity?> GetByIdAsync(string tenantId, Guid signatureId, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<FailureSignatureEntity?> GetByKeyAsync(string tenantId, FailureSignatureScopeType scopeType, string scopeId, string toolchainHash, string? errorCode, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<IReadOnlyList<FailureSignatureEntity>> GetByScopeAsync(string tenantId, FailureSignatureScopeType scopeType, string scopeId, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<IReadOnlyList<FailureSignatureEntity>> GetUnresolvedAsync(string tenantId, int limit = 100, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<IReadOnlyList<FailureSignatureEntity>> GetByPredictedOutcomeAsync(string tenantId, PredictedOutcome outcome, decimal minConfidence = 0.5m, int limit = 100, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<FailureSignatureEntity> UpsertOccurrenceAsync(string tenantId, FailureSignatureScopeType scopeType, string scopeId, string toolchainHash, string? errorCode, ErrorCategory? errorCategory, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<bool> UpdateResolutionAsync(string tenantId, Guid signatureId, ResolutionStatus status, string? notes, string? resolvedBy, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<bool> UpdatePredictionAsync(string tenantId, Guid signatureId, PredictedOutcome outcome, decimal confidence, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<bool> DeleteAsync(string tenantId, Guid signatureId, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<int> PruneResolvedAsync(string tenantId, TimeSpan olderThan, CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public Task<FailureSignatureEntity?> GetBestMatchAsync(
string tenantId,
FailureSignatureScopeType scopeType,
string scopeId,
string? toolchainHash = null,
CancellationToken cancellationToken = default)
{
LastCall = (tenantId, scopeType, scopeId, toolchainHash);
return Task.FromResult(_match);
}
}
}