audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories
This commit is contained in:
@@ -689,7 +689,7 @@ static Task<IResult> HandlePolicyCompile(
|
||||
BundleName = request.BundleName,
|
||||
Version = "1.0.0",
|
||||
RuleCount = request.RuleIds.Count,
|
||||
CompiledAt = now.ToString("O"),
|
||||
CompiledAt = now.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
|
||||
ContentHash = $"sha256:{contentHash}",
|
||||
SignatureId = null // Would be signed in production
|
||||
};
|
||||
@@ -751,10 +751,10 @@ static async Task<IResult> HandleGetConsent(
|
||||
return Results.Ok(new AiConsentStatusResponse
|
||||
{
|
||||
Consented = record.Consented,
|
||||
ConsentedAt = record.ConsentedAt?.ToString("O"),
|
||||
ConsentedAt = record.ConsentedAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
|
||||
ConsentedBy = record.UserId,
|
||||
Scope = record.Scope,
|
||||
ExpiresAt = record.ExpiresAt?.ToString("O"),
|
||||
ExpiresAt = record.ExpiresAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
|
||||
SessionLevel = record.SessionLevel
|
||||
});
|
||||
}
|
||||
@@ -763,6 +763,7 @@ static async Task<IResult> HandleGrantConsent(
|
||||
HttpContext httpContext,
|
||||
AiConsentGrantRequest request,
|
||||
IAiConsentStore consentStore,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!request.DataShareAcknowledged)
|
||||
@@ -786,8 +787,8 @@ static async Task<IResult> HandleGrantConsent(
|
||||
return Results.Ok(new AiConsentGrantResponse
|
||||
{
|
||||
Consented = record.Consented,
|
||||
ConsentedAt = record.ConsentedAt?.ToString("O") ?? DateTimeOffset.UtcNow.ToString("O"),
|
||||
ExpiresAt = record.ExpiresAt?.ToString("O")
|
||||
ConsentedAt = record.ConsentedAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture) ?? timeProvider.GetUtcNow().ToString("O", System.Globalization.CultureInfo.InvariantCulture),
|
||||
ExpiresAt = record.ExpiresAt?.ToString("O", System.Globalization.CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -863,7 +864,7 @@ static async Task<IResult> HandleJustify(
|
||||
ConfidenceScore = result.ConfidenceScore,
|
||||
EvidenceSuggestions = result.EvidenceSuggestions,
|
||||
ModelVersion = result.ModelVersion,
|
||||
GeneratedAt = result.GeneratedAt.ToString("O"),
|
||||
GeneratedAt = result.GeneratedAt.ToString("O", System.Globalization.CultureInfo.InvariantCulture),
|
||||
TraceId = result.TraceId
|
||||
});
|
||||
}
|
||||
@@ -919,7 +920,7 @@ static Task<IResult> HandleGetRateLimits(
|
||||
Feature = l.Feature,
|
||||
Limit = l.Limit,
|
||||
Remaining = l.Remaining,
|
||||
ResetsAt = l.ResetsAt.ToString("O")
|
||||
ResetsAt = l.ResetsAt.ToString("O", System.Globalization.CultureInfo.InvariantCulture)
|
||||
}).ToList();
|
||||
|
||||
return Task.FromResult(Results.Ok(response));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -16,6 +17,7 @@ public sealed class EvidenceAnchoredExplanationGenerator : IExplanationGenerator
|
||||
private readonly IExplanationInferenceClient _inferenceClient;
|
||||
private readonly ICitationExtractor _citationExtractor;
|
||||
private readonly IExplanationStore _store;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private const double EvidenceBackedThreshold = 0.8;
|
||||
|
||||
@@ -24,13 +26,15 @@ public sealed class EvidenceAnchoredExplanationGenerator : IExplanationGenerator
|
||||
IExplanationPromptService promptService,
|
||||
IExplanationInferenceClient inferenceClient,
|
||||
ICitationExtractor citationExtractor,
|
||||
IExplanationStore store)
|
||||
IExplanationStore store,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_evidenceService = evidenceService;
|
||||
_promptService = promptService;
|
||||
_inferenceClient = inferenceClient;
|
||||
_citationExtractor = citationExtractor;
|
||||
_store = store;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ExplanationResult> GenerateAsync(ExplanationRequest request, CancellationToken cancellationToken = default)
|
||||
@@ -91,7 +95,7 @@ public sealed class EvidenceAnchoredExplanationGenerator : IExplanationGenerator
|
||||
ModelId = inferenceResult.ModelId,
|
||||
PromptTemplateVersion = prompt.TemplateVersion,
|
||||
InputHashes = inputHashes,
|
||||
GeneratedAt = DateTime.UtcNow.ToString("O"),
|
||||
GeneratedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
|
||||
OutputHash = outputHash
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -14,6 +15,7 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
|
||||
private readonly IPolicyPromptService _promptService;
|
||||
private readonly IPolicyInferenceClient _inferenceClient;
|
||||
private readonly IPolicyIntentStore _intentStore;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private static readonly string[] FewShotExamples = new[]
|
||||
{
|
||||
@@ -27,11 +29,13 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
|
||||
public AiPolicyIntentParser(
|
||||
IPolicyPromptService promptService,
|
||||
IPolicyInferenceClient inferenceClient,
|
||||
IPolicyIntentStore intentStore)
|
||||
IPolicyIntentStore intentStore,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_promptService = promptService;
|
||||
_inferenceClient = inferenceClient;
|
||||
_intentStore = intentStore;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<PolicyParseResult> ParseAsync(
|
||||
@@ -61,7 +65,7 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
|
||||
Success = intent.Confidence >= 0.7,
|
||||
ErrorMessage = intent.Confidence < 0.7 ? "Ambiguous input - clarification needed" : null,
|
||||
ModelId = inferenceResult.ModelId,
|
||||
ParsedAt = DateTime.UtcNow.ToString("O")
|
||||
ParsedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,7 +97,7 @@ public sealed class AiPolicyIntentParser : IPolicyIntentParser
|
||||
Intent = clarifiedIntent,
|
||||
Success = clarifiedIntent.Confidence >= 0.8,
|
||||
ModelId = inferenceResult.ModelId,
|
||||
ParsedAt = DateTime.UtcNow.ToString("O")
|
||||
ParsedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@@ -10,6 +11,13 @@ namespace StellaOps.AdvisoryAI.PolicyStudio;
|
||||
/// </summary>
|
||||
public sealed class LatticeRuleGenerator : IPolicyRuleGenerator
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public LatticeRuleGenerator(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<RuleGenerationResult> GenerateAsync(
|
||||
PolicyIntent intent,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -58,7 +66,7 @@ public sealed class LatticeRuleGenerator : IPolicyRuleGenerator
|
||||
Success = true,
|
||||
Warnings = warnings,
|
||||
IntentId = intent.IntentId,
|
||||
GeneratedAt = DateTime.UtcNow.ToString("O")
|
||||
GeneratedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -391,7 +392,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
|
||||
Warnings = warnings,
|
||||
ValidationReport = validationReport,
|
||||
TestReport = testReport,
|
||||
CompiledAt = _timeProvider.GetUtcNow().ToString("O")
|
||||
CompiledAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -412,7 +413,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
|
||||
Warnings = warnings,
|
||||
ValidationReport = validationReport,
|
||||
TestReport = testReport,
|
||||
CompiledAt = DateTime.UtcNow.ToString("O"),
|
||||
CompiledAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
|
||||
BundleDigest = bundleDigest
|
||||
};
|
||||
}
|
||||
@@ -492,7 +493,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
|
||||
ContentDigest = contentDigest,
|
||||
Signature = string.Empty,
|
||||
Algorithm = "none",
|
||||
SignedAt = _timeProvider.GetUtcNow().ToString("O")
|
||||
SignedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -509,7 +510,7 @@ public sealed class PolicyBundleCompiler : IPolicyBundleCompiler
|
||||
Algorithm = signature.Algorithm,
|
||||
KeyId = options.KeyId,
|
||||
SignerIdentity = options.SignerIdentity,
|
||||
SignedAt = _timeProvider.GetUtcNow().ToString("O"),
|
||||
SignedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
|
||||
CertificateChain = signature.CertificateChain
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@@ -10,6 +11,13 @@ namespace StellaOps.AdvisoryAI.PolicyStudio;
|
||||
/// </summary>
|
||||
public sealed class PropertyBasedTestSynthesizer : ITestCaseSynthesizer
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PropertyBasedTestSynthesizer(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<PolicyTestCase>> SynthesizeAsync(
|
||||
IReadOnlyList<LatticeRule> rules,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -53,7 +61,7 @@ public sealed class PropertyBasedTestSynthesizer : ITestCaseSynthesizer
|
||||
Passed = results.Count(r => r.Passed),
|
||||
Failed = results.Count(r => !r.Passed),
|
||||
Results = results,
|
||||
RunAt = DateTime.UtcNow.ToString("O")
|
||||
RunAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.AdvisoryAI.Abstractions;
|
||||
@@ -106,7 +107,7 @@ internal sealed class ConcelierAdvisoryDocumentProvider : IAdvisoryDocumentProvi
|
||||
["vendor"] = record.Document.Source.Vendor,
|
||||
["connector"] = record.Document.Source.Connector,
|
||||
["content_hash"] = record.Document.Upstream.ContentHash,
|
||||
["ingested_at"] = record.IngestedAt.UtcDateTime.ToString("O"),
|
||||
["ingested_at"] = record.IngestedAt.UtcDateTime.ToString("O", CultureInfo.InvariantCulture),
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(record.Document.Source.Stream))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -88,7 +89,7 @@ public sealed class AiRemediationPlanner : IRemediationPlanner
|
||||
NotReadyReason = notReadyReason,
|
||||
ConfidenceScore = inferenceResult.Confidence,
|
||||
ModelId = inferenceResult.ModelId,
|
||||
GeneratedAt = _timeProvider.GetUtcNow().ToString("O"),
|
||||
GeneratedAt = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture),
|
||||
InputHashes = inputHashes,
|
||||
EvidenceRefs = new List<string> { versionResult.CurrentVersion, versionResult.RecommendedVersion }
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Remediation;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,42 +9,56 @@ namespace StellaOps.AdvisoryAI.Remediation;
|
||||
/// </summary>
|
||||
public sealed class AzureDevOpsPullRequestGenerator : IPullRequestGenerator
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly Func<Guid> _guidFactory;
|
||||
private readonly Func<int, int, int> _randomFactory;
|
||||
|
||||
public AzureDevOpsPullRequestGenerator(
|
||||
TimeProvider? timeProvider = null,
|
||||
Func<Guid>? guidFactory = null,
|
||||
Func<int, int, int>? randomFactory = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidFactory = guidFactory ?? Guid.NewGuid;
|
||||
_randomFactory = randomFactory ?? Random.Shared.Next;
|
||||
}
|
||||
|
||||
public string ScmType => "azure-devops";
|
||||
|
||||
public Task<PullRequestResult> CreatePullRequestAsync(
|
||||
RemediationPlan plan,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var nowStr = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
|
||||
if (!plan.PrReady)
|
||||
{
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
PrId = $"ado-pr-{Guid.NewGuid():N}",
|
||||
PrId = $"ado-pr-{_guidFactory():N}",
|
||||
PrNumber = 0,
|
||||
Url = string.Empty,
|
||||
BranchName = string.Empty,
|
||||
Status = PullRequestStatus.Failed,
|
||||
StatusMessage = plan.NotReadyReason ?? "Plan is not PR-ready",
|
||||
CreatedAt = DateTime.UtcNow.ToString("O"),
|
||||
UpdatedAt = DateTime.UtcNow.ToString("O")
|
||||
CreatedAt = nowStr,
|
||||
UpdatedAt = nowStr
|
||||
});
|
||||
}
|
||||
|
||||
var branchName = GenerateBranchName(plan);
|
||||
var prId = $"ado-pr-{Guid.NewGuid():N}";
|
||||
var now = DateTime.UtcNow.ToString("O");
|
||||
var branchName = GenerateBranchName(plan, _timeProvider);
|
||||
var prId = $"ado-pr-{_guidFactory():N}";
|
||||
|
||||
// In a real implementation, this would use Azure DevOps REST API
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
PrId = prId,
|
||||
PrNumber = new Random().Next(1000, 9999),
|
||||
PrNumber = _randomFactory(1000, 9999),
|
||||
Url = $"https://dev.azure.com/{ExtractOrgProject(plan.Request.RepositoryUrl)}/_git/{ExtractRepoName(plan.Request.RepositoryUrl)}/pullrequest/{prId}",
|
||||
BranchName = branchName,
|
||||
Status = PullRequestStatus.Creating,
|
||||
StatusMessage = "Pull request is being created",
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
CreatedAt = nowStr,
|
||||
UpdatedAt = nowStr
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,7 +66,7 @@ public sealed class AzureDevOpsPullRequestGenerator : IPullRequestGenerator
|
||||
string prId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTime.UtcNow.ToString("O");
|
||||
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
PrId = prId,
|
||||
@@ -80,10 +96,10 @@ public sealed class AzureDevOpsPullRequestGenerator : IPullRequestGenerator
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string GenerateBranchName(RemediationPlan plan)
|
||||
private static string GenerateBranchName(RemediationPlan plan, TimeProvider timeProvider)
|
||||
{
|
||||
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
|
||||
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd");
|
||||
var timestamp = timeProvider.GetUtcNow().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
return $"stellaops/fix-{vulnId}-{timestamp}";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Remediation;
|
||||
|
||||
/// <summary>
|
||||
@@ -9,11 +11,19 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
|
||||
{
|
||||
private readonly IRemediationPlanStore _planStore;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly Func<Guid> _guidFactory;
|
||||
private readonly Func<int, int, int> _randomFactory;
|
||||
|
||||
public GitHubPullRequestGenerator(IRemediationPlanStore planStore, TimeProvider? timeProvider = null)
|
||||
public GitHubPullRequestGenerator(
|
||||
IRemediationPlanStore planStore,
|
||||
TimeProvider? timeProvider = null,
|
||||
Func<Guid>? guidFactory = null,
|
||||
Func<int, int, int>? randomFactory = null)
|
||||
{
|
||||
_planStore = planStore;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidFactory = guidFactory ?? Guid.NewGuid;
|
||||
_randomFactory = randomFactory ?? Random.Shared.Next;
|
||||
}
|
||||
|
||||
public string ScmType => "github";
|
||||
@@ -22,19 +32,20 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
|
||||
RemediationPlan plan,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var nowStr = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
|
||||
// Validate plan is PR-ready
|
||||
if (!plan.PrReady)
|
||||
{
|
||||
return new PullRequestResult
|
||||
{
|
||||
PrId = $"pr-{Guid.NewGuid():N}",
|
||||
PrId = $"pr-{_guidFactory():N}",
|
||||
PrNumber = 0,
|
||||
Url = string.Empty,
|
||||
BranchName = string.Empty,
|
||||
Status = PullRequestStatus.Failed,
|
||||
StatusMessage = plan.NotReadyReason ?? "Plan is not PR-ready",
|
||||
CreatedAt = _timeProvider.GetUtcNow().ToString("O"),
|
||||
UpdatedAt = _timeProvider.GetUtcNow().ToString("O")
|
||||
CreatedAt = nowStr,
|
||||
UpdatedAt = nowStr
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,19 +58,18 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
|
||||
// 3. Commit changes
|
||||
// 4. Create PR via GitHub API
|
||||
|
||||
var prId = $"gh-pr-{Guid.NewGuid():N}";
|
||||
var now = _timeProvider.GetUtcNow().ToString("O");
|
||||
var prId = $"gh-pr-{_guidFactory():N}";
|
||||
|
||||
return new PullRequestResult
|
||||
{
|
||||
PrId = prId,
|
||||
PrNumber = new Random().Next(1000, 9999), // Placeholder
|
||||
PrNumber = _randomFactory(1000, 9999), // Placeholder
|
||||
Url = $"https://github.com/{ExtractOwnerRepo(plan.Request.RepositoryUrl)}/pull/{prId}",
|
||||
BranchName = branchName,
|
||||
Status = PullRequestStatus.Creating,
|
||||
StatusMessage = "Pull request is being created",
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
CreatedAt = nowStr,
|
||||
UpdatedAt = nowStr
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,7 +78,7 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// In a real implementation, this would query GitHub API
|
||||
var now = _timeProvider.GetUtcNow().ToString("O");
|
||||
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
|
||||
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
@@ -104,7 +114,7 @@ public sealed class GitHubPullRequestGenerator : IPullRequestGenerator
|
||||
private string GenerateBranchName(RemediationPlan plan)
|
||||
{
|
||||
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
|
||||
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd");
|
||||
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
return $"stellaops/fix-{vulnId}-{timestamp}";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Remediation;
|
||||
|
||||
/// <summary>
|
||||
@@ -8,10 +10,17 @@ namespace StellaOps.AdvisoryAI.Remediation;
|
||||
public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
|
||||
{
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly Func<Guid> _guidFactory;
|
||||
private readonly Func<int, int, int> _randomFactory;
|
||||
|
||||
public GitLabMergeRequestGenerator(TimeProvider? timeProvider = null)
|
||||
public GitLabMergeRequestGenerator(
|
||||
TimeProvider? timeProvider = null,
|
||||
Func<Guid>? guidFactory = null,
|
||||
Func<int, int, int>? randomFactory = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_guidFactory = guidFactory ?? Guid.NewGuid;
|
||||
_randomFactory = randomFactory ?? Random.Shared.Next;
|
||||
}
|
||||
|
||||
public string ScmType => "gitlab";
|
||||
@@ -20,36 +29,36 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
|
||||
RemediationPlan plan,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var nowStr = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
|
||||
if (!plan.PrReady)
|
||||
{
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
PrId = $"mr-{Guid.NewGuid():N}",
|
||||
PrId = $"mr-{_guidFactory():N}",
|
||||
PrNumber = 0,
|
||||
Url = string.Empty,
|
||||
BranchName = string.Empty,
|
||||
Status = PullRequestStatus.Failed,
|
||||
StatusMessage = plan.NotReadyReason ?? "Plan is not MR-ready",
|
||||
CreatedAt = _timeProvider.GetUtcNow().ToString("O"),
|
||||
UpdatedAt = _timeProvider.GetUtcNow().ToString("O")
|
||||
CreatedAt = nowStr,
|
||||
UpdatedAt = nowStr
|
||||
});
|
||||
}
|
||||
|
||||
var branchName = GenerateBranchName(plan);
|
||||
var mrId = $"gl-mr-{Guid.NewGuid():N}";
|
||||
var now = _timeProvider.GetUtcNow().ToString("O");
|
||||
var mrId = $"gl-mr-{_guidFactory():N}";
|
||||
|
||||
// In a real implementation, this would use GitLab API
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
PrId = mrId,
|
||||
PrNumber = new Random().Next(1000, 9999),
|
||||
PrNumber = _randomFactory(1000, 9999),
|
||||
Url = $"https://gitlab.com/{ExtractProjectPath(plan.Request.RepositoryUrl)}/-/merge_requests/{mrId}",
|
||||
BranchName = branchName,
|
||||
Status = PullRequestStatus.Creating,
|
||||
StatusMessage = "Merge request is being created",
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
CreatedAt = nowStr,
|
||||
UpdatedAt = nowStr
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,7 +66,7 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
|
||||
string prId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = _timeProvider.GetUtcNow().ToString("O");
|
||||
var now = _timeProvider.GetUtcNow().ToString("O", CultureInfo.InvariantCulture);
|
||||
return Task.FromResult(new PullRequestResult
|
||||
{
|
||||
PrId = prId,
|
||||
@@ -90,7 +99,7 @@ public sealed class GitLabMergeRequestGenerator : IPullRequestGenerator
|
||||
private string GenerateBranchName(RemediationPlan plan)
|
||||
{
|
||||
var vulnId = plan.Request.VulnerabilityId.Replace(":", "-").ToLowerInvariant();
|
||||
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd");
|
||||
var timestamp = _timeProvider.GetUtcNow().ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
return $"stellaops/fix-{vulnId}-{timestamp}";
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class SemanticVersionTests
|
||||
[InlineData("1.0.0-")]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void Parse_InvalidInputs_Throws(string value)
|
||||
public void Parse_InvalidInputs_Throws(string? value)
|
||||
{
|
||||
var act = () => SemanticVersion.Parse(value!);
|
||||
act.Should().Throw<FormatException>();
|
||||
|
||||
@@ -12,12 +12,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user