143 lines
5.8 KiB
C#
143 lines
5.8 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.TaskRunner.Core.Execution;
|
|
using StellaOps.TaskRunner.Core.Planning;
|
|
using StellaOps.TaskRunner.Infrastructure.Execution;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.TaskRunner.Tests;
|
|
|
|
public sealed class FilesystemPackRunArtifactUploaderTests : IDisposable
|
|
{
|
|
private readonly string artifactsRoot;
|
|
|
|
public FilesystemPackRunArtifactUploaderTests()
|
|
{
|
|
artifactsRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task CopiesFileOutputs()
|
|
{
|
|
var sourceFile = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():n}.txt");
|
|
await File.WriteAllTextAsync(sourceFile, "artifact-content", CancellationToken.None);
|
|
|
|
var uploader = CreateUploader();
|
|
var output = CreateFileOutput("bundle", sourceFile);
|
|
var context = CreateContext();
|
|
var state = CreateState(context);
|
|
|
|
await uploader.UploadAsync(context, state, new[] { output }, CancellationToken.None);
|
|
|
|
var runPath = Path.Combine(artifactsRoot, context.RunId);
|
|
var filesDirectory = Path.Combine(runPath, "files");
|
|
var copiedFiles = Directory.GetFiles(filesDirectory);
|
|
Assert.Single(copiedFiles);
|
|
Assert.Equal("bundle.txt", Path.GetFileName(copiedFiles[0]));
|
|
Assert.Equal("artifact-content", await File.ReadAllTextAsync(copiedFiles[0], CancellationToken.None));
|
|
|
|
var manifest = await ReadManifestAsync(runPath);
|
|
Assert.Single(manifest.Outputs);
|
|
Assert.Equal("copied", manifest.Outputs[0].Status);
|
|
Assert.Equal("files/bundle.txt", manifest.Outputs[0].StoredPath);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task RecordsMissingFilesWithoutThrowing()
|
|
{
|
|
var uploader = CreateUploader();
|
|
var output = CreateFileOutput("missing", Path.Combine(Path.GetTempPath(), "does-not-exist.txt"));
|
|
var context = CreateContext();
|
|
var state = CreateState(context);
|
|
|
|
await uploader.UploadAsync(context, state, new[] { output }, CancellationToken.None);
|
|
|
|
var manifest = await ReadManifestAsync(Path.Combine(artifactsRoot, context.RunId));
|
|
Assert.Equal("missing", manifest.Outputs[0].Status);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task WritesExpressionOutputsAsJson()
|
|
{
|
|
var uploader = CreateUploader();
|
|
var output = CreateExpressionOutput("metadata", JsonNode.Parse("""{"foo":"bar"}""")!);
|
|
var context = CreateContext();
|
|
var state = CreateState(context);
|
|
|
|
await uploader.UploadAsync(context, state, new[] { output }, CancellationToken.None);
|
|
|
|
var expressionPath = Path.Combine(artifactsRoot, context.RunId, "expressions", "metadata.json");
|
|
Assert.True(File.Exists(expressionPath));
|
|
|
|
var manifest = await ReadManifestAsync(Path.Combine(artifactsRoot, context.RunId));
|
|
Assert.Equal("materialized", manifest.Outputs[0].Status);
|
|
Assert.Equal("expressions/metadata.json", manifest.Outputs[0].StoredPath);
|
|
}
|
|
|
|
private FilesystemPackRunArtifactUploader CreateUploader()
|
|
=> new(artifactsRoot, TimeProvider.System, NullLogger<FilesystemPackRunArtifactUploader>.Instance);
|
|
|
|
private static TaskPackPlanOutput CreateFileOutput(string name, string path)
|
|
=> new(
|
|
name,
|
|
Type: "file",
|
|
Path: new TaskPackPlanParameterValue(JsonValue.Create(path), null, null, false),
|
|
Expression: null);
|
|
|
|
private static TaskPackPlanOutput CreateExpressionOutput(string name, JsonNode expression)
|
|
=> new(
|
|
name,
|
|
Type: "object",
|
|
Path: null,
|
|
Expression: new TaskPackPlanParameterValue(expression, null, null, false));
|
|
|
|
private static PackRunExecutionContext CreateContext()
|
|
=> new("run-" + Guid.NewGuid().ToString("n"), CreatePlan(), DateTimeOffset.UtcNow);
|
|
|
|
private static PackRunState CreateState(PackRunExecutionContext context)
|
|
=> PackRunState.Create(
|
|
runId: context.RunId,
|
|
planHash: context.Plan.Hash,
|
|
context.Plan,
|
|
failurePolicy: new TaskPackPlanFailurePolicy(1, 1, false),
|
|
requestedAt: DateTimeOffset.UtcNow,
|
|
steps: new Dictionary<string, PackRunStepStateRecord>(StringComparer.Ordinal),
|
|
timestamp: DateTimeOffset.UtcNow);
|
|
|
|
private static TaskPackPlan CreatePlan()
|
|
{
|
|
return new TaskPackPlan(
|
|
new TaskPackPlanMetadata("sample-pack", "1.0.0", null, Array.Empty<string>()),
|
|
new Dictionary<string, JsonNode?>(StringComparer.Ordinal),
|
|
Array.Empty<TaskPackPlanStep>(),
|
|
hash: "hash",
|
|
approvals: Array.Empty<TaskPackPlanApproval>(),
|
|
secrets: Array.Empty<TaskPackPlanSecret>(),
|
|
outputs: Array.Empty<TaskPackPlanOutput>(),
|
|
failurePolicy: new TaskPackPlanFailurePolicy(1, 1, false));
|
|
}
|
|
|
|
private static async Task<ArtifactManifestModel> ReadManifestAsync(string runPath)
|
|
{
|
|
var json = await File.ReadAllTextAsync(Path.Combine(runPath, "artifact-manifest.json"), CancellationToken.None);
|
|
return JsonSerializer.Deserialize<ArtifactManifestModel>(json, new JsonSerializerOptions(JsonSerializerDefaults.Web))!;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(artifactsRoot))
|
|
{
|
|
Directory.Delete(artifactsRoot, recursive: true);
|
|
}
|
|
}
|
|
|
|
private sealed record ArtifactManifestModel(string RunId, DateTimeOffset UploadedAt, List<ArtifactRecordModel> Outputs);
|
|
|
|
private sealed record ArtifactRecordModel(string Name, string Type, string? SourcePath, string? StoredPath, string Status, string? Notes);
|
|
}
|