up
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.ExportCenter.Client.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.ExportCenter.Client.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Smoke tests for ExportCenterClient with mock HTTP responses.
|
||||
/// </summary>
|
||||
public sealed class ExportCenterClientTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
[Fact]
|
||||
public async Task GetDiscoveryMetadataAsync_ReturnsMetadata()
|
||||
{
|
||||
var expectedMetadata = new OpenApiDiscoveryMetadata(
|
||||
Service: "export-center",
|
||||
Version: "1.0.0",
|
||||
SpecVersion: "3.0.3",
|
||||
Format: "application/yaml",
|
||||
Url: "/openapi/export-center.yaml",
|
||||
JsonUrl: "/openapi/export-center.json",
|
||||
ErrorEnvelopeSchema: "#/components/schemas/ErrorEnvelope",
|
||||
GeneratedAt: new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
ProfilesSupported: new[] { "attestation", "mirror" },
|
||||
ChecksumSha256: null);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
Assert.Equal("/.well-known/openapi", request.RequestUri!.AbsolutePath);
|
||||
return CreateJsonResponse(expectedMetadata);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var result = await client.GetDiscoveryMetadataAsync();
|
||||
|
||||
Assert.Equal("export-center", result.Service);
|
||||
Assert.Equal("1.0.0", result.Version);
|
||||
Assert.Equal("3.0.3", result.SpecVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListProfilesAsync_ReturnsProfiles()
|
||||
{
|
||||
var expectedResponse = new ExportProfileListResponse(
|
||||
Profiles: new[]
|
||||
{
|
||||
new ExportProfile(
|
||||
ProfileId: "profile-1",
|
||||
Name: "Test Profile",
|
||||
Description: "Test",
|
||||
Adapter: "evidence",
|
||||
Selectors: new Dictionary<string, string> { ["org"] = "test" },
|
||||
OutputFormat: "tar.gz",
|
||||
SigningEnabled: true,
|
||||
CreatedAt: DateTimeOffset.UtcNow,
|
||||
UpdatedAt: null)
|
||||
},
|
||||
ContinuationToken: null,
|
||||
HasMore: false);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
Assert.Equal("/v1/exports/profiles", request.RequestUri!.AbsolutePath);
|
||||
return CreateJsonResponse(expectedResponse);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var result = await client.ListProfilesAsync();
|
||||
|
||||
Assert.Single(result.Profiles);
|
||||
Assert.Equal("profile-1", result.Profiles[0].ProfileId);
|
||||
Assert.False(result.HasMore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListProfilesAsync_WithPagination_IncludesParameters()
|
||||
{
|
||||
var expectedResponse = new ExportProfileListResponse([], null, false);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
var query = request.RequestUri!.Query;
|
||||
Assert.Contains("limit=10", query);
|
||||
Assert.Contains("continuationToken=abc123", query);
|
||||
return CreateJsonResponse(expectedResponse);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
await client.ListProfilesAsync(continuationToken: "abc123", limit: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProfileAsync_WhenNotFound_ReturnsNull()
|
||||
{
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var result = await client.GetProfileAsync("nonexistent");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateEvidenceExportAsync_ReturnsResponse()
|
||||
{
|
||||
var expectedResponse = new CreateEvidenceExportResponse(
|
||||
RunId: "run-123",
|
||||
Status: "pending",
|
||||
StatusUrl: "/v1/exports/evidence/run-123/status",
|
||||
EstimatedCompletionSeconds: 60);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
Assert.Equal("/v1/exports/evidence", request.RequestUri!.AbsolutePath);
|
||||
return CreateJsonResponse(expectedResponse, HttpStatusCode.Accepted);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var request = new CreateEvidenceExportRequest("profile-1");
|
||||
var result = await client.CreateEvidenceExportAsync(request);
|
||||
|
||||
Assert.Equal("run-123", result.RunId);
|
||||
Assert.Equal("pending", result.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEvidenceExportStatusAsync_ReturnsStatus()
|
||||
{
|
||||
var expectedStatus = new EvidenceExportStatus(
|
||||
RunId: "run-123",
|
||||
ProfileId: "profile-1",
|
||||
Status: "completed",
|
||||
Progress: 100,
|
||||
StartedAt: DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
CompletedAt: DateTimeOffset.UtcNow,
|
||||
BundleHash: "sha256:abc123",
|
||||
DownloadUrl: "/v1/exports/evidence/run-123/download",
|
||||
ErrorCode: null,
|
||||
ErrorMessage: null);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
Assert.Equal("/v1/exports/evidence/run-123/status", request.RequestUri!.AbsolutePath);
|
||||
return CreateJsonResponse(expectedStatus);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var result = await client.GetEvidenceExportStatusAsync("run-123");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("completed", result.Status);
|
||||
Assert.Equal(100, result.Progress);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadEvidenceExportAsync_ReturnsStream()
|
||||
{
|
||||
var bundleContent = "test bundle content"u8.ToArray();
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
Assert.Equal("/v1/exports/evidence/run-123/download", request.RequestUri!.AbsolutePath);
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new ByteArrayContent(bundleContent)
|
||||
};
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var stream = await client.DownloadEvidenceExportAsync("run-123");
|
||||
|
||||
Assert.NotNull(stream);
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
Assert.Equal(bundleContent, ms.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadEvidenceExportAsync_WhenNotReady_ReturnsNull()
|
||||
{
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.Conflict);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var result = await client.DownloadEvidenceExportAsync("run-123");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAttestationExportAsync_ReturnsResponse()
|
||||
{
|
||||
var expectedResponse = new CreateAttestationExportResponse(
|
||||
RunId: "att-run-123",
|
||||
Status: "pending",
|
||||
StatusUrl: "/v1/exports/attestations/att-run-123/status",
|
||||
EstimatedCompletionSeconds: 30);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
Assert.Equal("/v1/exports/attestations", request.RequestUri!.AbsolutePath);
|
||||
return CreateJsonResponse(expectedResponse, HttpStatusCode.Accepted);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var request = new CreateAttestationExportRequest("profile-1", IncludeTransparencyLog: true);
|
||||
var result = await client.CreateAttestationExportAsync(request);
|
||||
|
||||
Assert.Equal("att-run-123", result.RunId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAttestationExportStatusAsync_IncludesTransparencyLogField()
|
||||
{
|
||||
var expectedStatus = new AttestationExportStatus(
|
||||
RunId: "att-run-123",
|
||||
ProfileId: "profile-1",
|
||||
Status: "completed",
|
||||
Progress: 100,
|
||||
StartedAt: DateTimeOffset.UtcNow.AddMinutes(-2),
|
||||
CompletedAt: DateTimeOffset.UtcNow,
|
||||
BundleHash: "sha256:def456",
|
||||
DownloadUrl: "/v1/exports/attestations/att-run-123/download",
|
||||
TransparencyLogIncluded: true,
|
||||
ErrorCode: null,
|
||||
ErrorMessage: null);
|
||||
|
||||
var handler = new MockHttpMessageHandler(request =>
|
||||
{
|
||||
return CreateJsonResponse(expectedStatus);
|
||||
});
|
||||
|
||||
var client = CreateClient(handler);
|
||||
|
||||
var result = await client.GetAttestationExportStatusAsync("att-run-123");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.TransparencyLogIncluded);
|
||||
}
|
||||
|
||||
private static ExportCenterClient CreateClient(MockHttpMessageHandler handler)
|
||||
{
|
||||
var httpClient = new HttpClient(handler)
|
||||
{
|
||||
BaseAddress = new Uri("https://localhost:5001")
|
||||
};
|
||||
return new ExportCenterClient(httpClient);
|
||||
}
|
||||
|
||||
private static HttpResponseMessage CreateJsonResponse<T>(T content, HttpStatusCode statusCode = HttpStatusCode.OK)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(content, JsonOptions);
|
||||
return new HttpResponseMessage(statusCode)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock HTTP message handler for testing.
|
||||
/// </summary>
|
||||
internal sealed class MockHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Func<HttpRequestMessage, HttpResponseMessage> _handler;
|
||||
|
||||
public MockHttpMessageHandler(Func<HttpRequestMessage, HttpResponseMessage> handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(_handler(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
using StellaOps.ExportCenter.Client.Streaming;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.ExportCenter.Client.Tests;
|
||||
|
||||
public sealed class ExportDownloadHelperTests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
|
||||
public ExportDownloadHelperTests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), $"export-download-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_tempDir))
|
||||
{
|
||||
Directory.Delete(_tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadToFileAsync_WritesContentToFile()
|
||||
{
|
||||
var content = "test content"u8.ToArray();
|
||||
using var stream = new MemoryStream(content);
|
||||
var outputPath = Path.Combine(_tempDir, "output.bin");
|
||||
|
||||
var bytesWritten = await ExportDownloadHelper.DownloadToFileAsync(stream, outputPath);
|
||||
|
||||
Assert.Equal(content.Length, bytesWritten);
|
||||
Assert.True(File.Exists(outputPath));
|
||||
Assert.Equal(content, await File.ReadAllBytesAsync(outputPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadToFileAsync_ReportsProgress()
|
||||
{
|
||||
var content = new byte[10000];
|
||||
Random.Shared.NextBytes(content);
|
||||
using var stream = new MemoryStream(content);
|
||||
var outputPath = Path.Combine(_tempDir, "progress.bin");
|
||||
var progressReports = new List<(long bytes, long? total)>();
|
||||
|
||||
await ExportDownloadHelper.DownloadToFileAsync(
|
||||
stream, outputPath, content.Length, (b, t) => progressReports.Add((b, t)));
|
||||
|
||||
Assert.NotEmpty(progressReports);
|
||||
Assert.Equal(content.Length, progressReports[^1].bytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ComputeSha256Async_ReturnsCorrectHash()
|
||||
{
|
||||
var content = "test content for hashing"u8.ToArray();
|
||||
using var stream = new MemoryStream(content);
|
||||
|
||||
var hash = await ExportDownloadHelper.ComputeSha256Async(stream);
|
||||
|
||||
// Verify it's a valid hex string
|
||||
Assert.Equal(64, hash.Length); // SHA-256 produces 32 bytes = 64 hex chars
|
||||
Assert.All(hash, c => Assert.True(char.IsLetterOrDigit(c)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadAndVerifyAsync_SucceedsWithCorrectHash()
|
||||
{
|
||||
var content = "deterministic content"u8.ToArray();
|
||||
using var hashStream = new MemoryStream(content);
|
||||
var expectedHash = await ExportDownloadHelper.ComputeSha256Async(hashStream);
|
||||
|
||||
using var downloadStream = new MemoryStream(content);
|
||||
var outputPath = Path.Combine(_tempDir, "verified.bin");
|
||||
|
||||
var actualHash = await ExportDownloadHelper.DownloadAndVerifyAsync(
|
||||
downloadStream, outputPath, expectedHash);
|
||||
|
||||
Assert.Equal(expectedHash, actualHash);
|
||||
Assert.True(File.Exists(outputPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadAndVerifyAsync_ThrowsOnHashMismatch()
|
||||
{
|
||||
var content = "actual content"u8.ToArray();
|
||||
using var stream = new MemoryStream(content);
|
||||
var outputPath = Path.Combine(_tempDir, "mismatch.bin");
|
||||
var wrongHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
ExportDownloadHelper.DownloadAndVerifyAsync(stream, outputPath, wrongHash));
|
||||
|
||||
// Verify file was deleted
|
||||
Assert.False(File.Exists(outputPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadAndVerifyAsync_HandlesSha256Prefix()
|
||||
{
|
||||
var content = "prefixed hash test"u8.ToArray();
|
||||
using var hashStream = new MemoryStream(content);
|
||||
var hash = await ExportDownloadHelper.ComputeSha256Async(hashStream);
|
||||
var prefixedHash = "sha256:" + hash;
|
||||
|
||||
using var downloadStream = new MemoryStream(content);
|
||||
var outputPath = Path.Combine(_tempDir, "prefixed.bin");
|
||||
|
||||
var actualHash = await ExportDownloadHelper.DownloadAndVerifyAsync(
|
||||
downloadStream, outputPath, prefixedHash);
|
||||
|
||||
Assert.Equal(hash, actualHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CopyWithProgressAsync_CopiesCorrectly()
|
||||
{
|
||||
var content = new byte[5000];
|
||||
Random.Shared.NextBytes(content);
|
||||
using var source = new MemoryStream(content);
|
||||
using var destination = new MemoryStream();
|
||||
|
||||
var bytesCopied = await ExportDownloadHelper.CopyWithProgressAsync(source, destination);
|
||||
|
||||
Assert.Equal(content.Length, bytesCopied);
|
||||
Assert.Equal(content, destination.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProgressLogger_ReturnsWorkingCallback()
|
||||
{
|
||||
var messages = new List<string>();
|
||||
var callback = ExportDownloadHelper.CreateProgressLogger(msg => messages.Add(msg), 100);
|
||||
|
||||
// Simulate progress
|
||||
callback(50, 1000); // Should not log (below threshold)
|
||||
callback(150, 1000); // Should log
|
||||
callback(200, 1000); // Should not log (too close to last)
|
||||
callback(300, 1000); // Should log
|
||||
|
||||
Assert.Equal(2, messages.Count);
|
||||
Assert.Contains("150", messages[0]);
|
||||
Assert.Contains("300", messages[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProgressLogger_FormatsWithoutTotalBytes()
|
||||
{
|
||||
var messages = new List<string>();
|
||||
var callback = ExportDownloadHelper.CreateProgressLogger(msg => messages.Add(msg), 100);
|
||||
|
||||
callback(200, null);
|
||||
|
||||
Assert.Single(messages);
|
||||
Assert.DoesNotContain("%", messages[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProgressLogger_FormatsWithTotalBytes()
|
||||
{
|
||||
var messages = new List<string>();
|
||||
var callback = ExportDownloadHelper.CreateProgressLogger(msg => messages.Add(msg), 100);
|
||||
|
||||
callback(500, 1000);
|
||||
|
||||
Assert.Single(messages);
|
||||
Assert.Contains("%", messages[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using StellaOps.ExportCenter.Client.Lifecycle;
|
||||
using StellaOps.ExportCenter.Client.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.ExportCenter.Client.Tests;
|
||||
|
||||
public sealed class ExportJobLifecycleHelperTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("completed", true)]
|
||||
[InlineData("failed", true)]
|
||||
[InlineData("cancelled", true)]
|
||||
[InlineData("pending", false)]
|
||||
[InlineData("running", false)]
|
||||
[InlineData("COMPLETED", true)]
|
||||
public void IsTerminalStatus_ReturnsCorrectValue(string status, bool expected)
|
||||
{
|
||||
var result = ExportJobLifecycleHelper.IsTerminalStatus(status);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WaitForEvidenceExportCompletionAsync_ReturnsOnTerminalStatus()
|
||||
{
|
||||
var callCount = 0;
|
||||
var mockClient = new MockExportCenterClient
|
||||
{
|
||||
GetEvidenceExportStatusHandler = runId =>
|
||||
{
|
||||
callCount++;
|
||||
var status = callCount < 3 ? "running" : "completed";
|
||||
return new EvidenceExportStatus(
|
||||
RunId: runId,
|
||||
ProfileId: "profile-1",
|
||||
Status: status,
|
||||
Progress: callCount < 3 ? 50 : 100,
|
||||
StartedAt: DateTimeOffset.UtcNow,
|
||||
CompletedAt: callCount >= 3 ? DateTimeOffset.UtcNow : null,
|
||||
BundleHash: callCount >= 3 ? "sha256:abc" : null,
|
||||
DownloadUrl: null,
|
||||
ErrorCode: null,
|
||||
ErrorMessage: null);
|
||||
}
|
||||
};
|
||||
|
||||
var result = await ExportJobLifecycleHelper.WaitForEvidenceExportCompletionAsync(
|
||||
mockClient, "run-1", TimeSpan.FromMilliseconds(10), TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.Equal("completed", result.Status);
|
||||
Assert.Equal(100, result.Progress);
|
||||
Assert.Equal(3, callCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WaitForEvidenceExportCompletionAsync_ThrowsOnNotFound()
|
||||
{
|
||||
var mockClient = new MockExportCenterClient
|
||||
{
|
||||
GetEvidenceExportStatusHandler = _ => null
|
||||
};
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
ExportJobLifecycleHelper.WaitForEvidenceExportCompletionAsync(
|
||||
mockClient, "nonexistent", TimeSpan.FromMilliseconds(10), TimeSpan.FromSeconds(1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WaitForAttestationExportCompletionAsync_ReturnsOnTerminalStatus()
|
||||
{
|
||||
var callCount = 0;
|
||||
var mockClient = new MockExportCenterClient
|
||||
{
|
||||
GetAttestationExportStatusHandler = runId =>
|
||||
{
|
||||
callCount++;
|
||||
var status = callCount < 2 ? "running" : "completed";
|
||||
return new AttestationExportStatus(
|
||||
RunId: runId,
|
||||
ProfileId: "profile-1",
|
||||
Status: status,
|
||||
Progress: callCount < 2 ? 50 : 100,
|
||||
StartedAt: DateTimeOffset.UtcNow,
|
||||
CompletedAt: callCount >= 2 ? DateTimeOffset.UtcNow : null,
|
||||
BundleHash: callCount >= 2 ? "sha256:abc" : null,
|
||||
DownloadUrl: null,
|
||||
TransparencyLogIncluded: true,
|
||||
ErrorCode: null,
|
||||
ErrorMessage: null);
|
||||
}
|
||||
};
|
||||
|
||||
var result = await ExportJobLifecycleHelper.WaitForAttestationExportCompletionAsync(
|
||||
mockClient, "run-1", TimeSpan.FromMilliseconds(10), TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.Equal("completed", result.Status);
|
||||
Assert.True(result.TransparencyLogIncluded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateEvidenceExportAndWaitAsync_CreatesAndWaits()
|
||||
{
|
||||
var createCalled = false;
|
||||
var mockClient = new MockExportCenterClient
|
||||
{
|
||||
CreateEvidenceExportHandler = request =>
|
||||
{
|
||||
createCalled = true;
|
||||
return new CreateEvidenceExportResponse("run-1", "pending", "/status", 10);
|
||||
},
|
||||
GetEvidenceExportStatusHandler = runId =>
|
||||
{
|
||||
return new EvidenceExportStatus(
|
||||
runId, "profile-1", "completed", 100,
|
||||
DateTimeOffset.UtcNow, DateTimeOffset.UtcNow,
|
||||
"sha256:abc", "/download", null, null);
|
||||
}
|
||||
};
|
||||
|
||||
var result = await ExportJobLifecycleHelper.CreateEvidenceExportAndWaitAsync(
|
||||
mockClient,
|
||||
new CreateEvidenceExportRequest("profile-1"),
|
||||
TimeSpan.FromMilliseconds(10),
|
||||
TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.True(createCalled);
|
||||
Assert.Equal("completed", result.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TerminalStatuses_ContainsExpectedValues()
|
||||
{
|
||||
Assert.Contains("completed", ExportJobLifecycleHelper.TerminalStatuses);
|
||||
Assert.Contains("failed", ExportJobLifecycleHelper.TerminalStatuses);
|
||||
Assert.Contains("cancelled", ExportJobLifecycleHelper.TerminalStatuses);
|
||||
Assert.DoesNotContain("pending", ExportJobLifecycleHelper.TerminalStatuses);
|
||||
Assert.DoesNotContain("running", ExportJobLifecycleHelper.TerminalStatuses);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IExportCenterClient for testing.
|
||||
/// </summary>
|
||||
internal sealed class MockExportCenterClient : IExportCenterClient
|
||||
{
|
||||
public Func<string, EvidenceExportStatus?>? GetEvidenceExportStatusHandler { get; set; }
|
||||
public Func<string, AttestationExportStatus?>? GetAttestationExportStatusHandler { get; set; }
|
||||
public Func<CreateEvidenceExportRequest, CreateEvidenceExportResponse>? CreateEvidenceExportHandler { get; set; }
|
||||
public Func<CreateAttestationExportRequest, CreateAttestationExportResponse>? CreateAttestationExportHandler { get; set; }
|
||||
|
||||
public Task<OpenApiDiscoveryMetadata> GetDiscoveryMetadataAsync(CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<ExportProfileListResponse> ListProfilesAsync(string? continuationToken = null, int? limit = null, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<ExportProfile?> GetProfileAsync(string profileId, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<ExportRunListResponse> ListRunsAsync(string? profileId = null, string? continuationToken = null, int? limit = null, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<ExportRun?> GetRunAsync(string runId, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<CreateEvidenceExportResponse> CreateEvidenceExportAsync(CreateEvidenceExportRequest request, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(CreateEvidenceExportHandler?.Invoke(request) ?? throw new NotImplementedException());
|
||||
|
||||
public Task<EvidenceExportStatus?> GetEvidenceExportStatusAsync(string runId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(GetEvidenceExportStatusHandler?.Invoke(runId));
|
||||
|
||||
public Task<Stream?> DownloadEvidenceExportAsync(string runId, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<CreateAttestationExportResponse> CreateAttestationExportAsync(CreateAttestationExportRequest request, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(CreateAttestationExportHandler?.Invoke(request) ?? throw new NotImplementedException());
|
||||
|
||||
public Task<AttestationExportStatus?> GetAttestationExportStatusAsync(string runId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(GetAttestationExportStatusHandler?.Invoke(runId));
|
||||
|
||||
public Task<Stream?> DownloadAttestationExportAsync(string runId, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<OutputType>Exe</OutputType>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit.v3" Version="3.0.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Client\StellaOps.ExportCenter.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
|
||||
"methodDisplay": "classAndMethod"
|
||||
}
|
||||
Reference in New Issue
Block a user