Fix release health multi-scope evidence contracts
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Contracts;
|
||||
|
||||
public sealed record EvidencePackProjection(
|
||||
string CapsuleId,
|
||||
string RunId,
|
||||
string ReleaseId,
|
||||
string? Environment,
|
||||
string? Region,
|
||||
string Status,
|
||||
DateTimeOffset UpdatedAt,
|
||||
string CapsuleRoute);
|
||||
@@ -0,0 +1,83 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.Platform.WebService.Constants;
|
||||
using StellaOps.Platform.WebService.Contracts;
|
||||
using StellaOps.Platform.WebService.Services;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Endpoints;
|
||||
|
||||
public static class EvidenceReadModelEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapEvidenceReadModelEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var evidence = app.MapGroup("/api/v2/evidence")
|
||||
.WithTags("Evidence V2")
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlRead)
|
||||
.RequireTenant();
|
||||
|
||||
evidence.MapGet("/packs", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
ReleaseReadModelService service,
|
||||
TimeProvider timeProvider,
|
||||
[AsParameters] EvidencePackListQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var page = await service.ListEvidencePacksAsync(
|
||||
requestContext!,
|
||||
query.Region,
|
||||
query.Environment,
|
||||
query.Limit,
|
||||
query.Offset,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<EvidencePackProjection>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
timeProvider.GetUtcNow(),
|
||||
Cached: false,
|
||||
CacheTtlSeconds: 0,
|
||||
page.Items,
|
||||
page.Total,
|
||||
page.Limit,
|
||||
page.Offset));
|
||||
})
|
||||
.WithName("ListEvidencePacksV2")
|
||||
.WithSummary("List Pack-22 evidence pack projections")
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static bool TryResolveContext(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
out PlatformRequestContext? requestContext,
|
||||
out IResult? failure)
|
||||
{
|
||||
if (resolver.TryResolve(context, out requestContext, out var error))
|
||||
{
|
||||
failure = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
failure = Results.BadRequest(new { error = error ?? "tenant_missing" });
|
||||
return false;
|
||||
}
|
||||
|
||||
public sealed record EvidencePackListQuery(
|
||||
string? Region,
|
||||
string? Environment,
|
||||
int? Limit,
|
||||
int? Offset);
|
||||
}
|
||||
@@ -321,6 +321,7 @@ app.MapReleaseControlEndpoints();
|
||||
app.MapReleaseReadModelEndpoints();
|
||||
app.MapTopologyReadModelEndpoints();
|
||||
app.MapSecurityReadModelEndpoints();
|
||||
app.MapEvidenceReadModelEndpoints();
|
||||
app.MapIntegrationReadModelEndpoints();
|
||||
app.MapLegacyAliasEndpoints();
|
||||
app.MapPackAdapterEndpoints();
|
||||
|
||||
@@ -37,8 +37,8 @@ public sealed class ReleaseReadModelService
|
||||
{
|
||||
var normalizedLimit = NormalizeLimit(limit);
|
||||
var normalizedOffset = NormalizeOffset(offset);
|
||||
var normalizedRegion = NormalizeFilter(region);
|
||||
var normalizedEnvironment = NormalizeFilter(environment);
|
||||
var regionFilter = ParseFilterSet(region);
|
||||
var environmentFilter = ParseFilterSet(environment);
|
||||
var normalizedType = NormalizeFilter(releaseType);
|
||||
var normalizedStatus = NormalizeFilter(status);
|
||||
|
||||
@@ -66,8 +66,8 @@ public sealed class ReleaseReadModelService
|
||||
var projected = bundles
|
||||
.Select(bundle => BuildReleaseProjection(bundle, runsByBundle.GetValueOrDefault(bundle.Id, Array.Empty<ReleaseControlBundleMaterializationRun>())))
|
||||
.Where(release =>
|
||||
(string.IsNullOrEmpty(normalizedRegion) || string.Equals(release.TargetRegion, normalizedRegion, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedEnvironment) || string.Equals(release.TargetEnvironment, normalizedEnvironment, StringComparison.Ordinal))
|
||||
MatchesFilters(release.TargetRegion, regionFilter)
|
||||
&& MatchesFilters(release.TargetEnvironment, environmentFilter)
|
||||
&& (string.IsNullOrEmpty(normalizedType) || string.Equals(release.ReleaseType, normalizedType, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedStatus) || string.Equals(release.Status, normalizedStatus, StringComparison.Ordinal)))
|
||||
.OrderByDescending(release => release.UpdatedAt)
|
||||
@@ -132,8 +132,8 @@ public sealed class ReleaseReadModelService
|
||||
{
|
||||
var normalizedLimit = NormalizeLimit(limit);
|
||||
var normalizedOffset = NormalizeOffset(offset);
|
||||
var normalizedRegion = NormalizeFilter(region);
|
||||
var normalizedEnvironment = NormalizeFilter(environment);
|
||||
var regionFilter = ParseFilterSet(region);
|
||||
var environmentFilter = ParseFilterSet(environment);
|
||||
|
||||
var bundles = await store.ListBundlesAsync(
|
||||
context.TenantId,
|
||||
@@ -184,8 +184,8 @@ public sealed class ReleaseReadModelService
|
||||
|
||||
var filtered = activity
|
||||
.Where(item =>
|
||||
(string.IsNullOrEmpty(normalizedRegion) || string.Equals(item.TargetRegion, normalizedRegion, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedEnvironment) || string.Equals(item.TargetEnvironment, normalizedEnvironment, StringComparison.Ordinal)))
|
||||
MatchesFilters(item.TargetRegion, regionFilter)
|
||||
&& MatchesFilters(item.TargetEnvironment, environmentFilter))
|
||||
.OrderByDescending(item => item.OccurredAt)
|
||||
.ThenBy(item => item.ActivityId, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
@@ -210,8 +210,8 @@ public sealed class ReleaseReadModelService
|
||||
var normalizedLimit = NormalizeLimit(limit);
|
||||
var normalizedOffset = NormalizeOffset(offset);
|
||||
var normalizedStatus = NormalizeFilter(status);
|
||||
var normalizedRegion = NormalizeFilter(region);
|
||||
var normalizedEnvironment = NormalizeFilter(environment);
|
||||
var regionFilter = ParseFilterSet(region);
|
||||
var environmentFilter = ParseFilterSet(environment);
|
||||
|
||||
var bundles = await store.ListBundlesAsync(
|
||||
context.TenantId,
|
||||
@@ -241,8 +241,8 @@ public sealed class ReleaseReadModelService
|
||||
var filtered = approvals
|
||||
.Where(item =>
|
||||
string.Equals(item.Status, effectiveStatus, StringComparison.Ordinal)
|
||||
&& (string.IsNullOrEmpty(normalizedRegion) || string.Equals(item.TargetRegion, normalizedRegion, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedEnvironment) || string.Equals(item.TargetEnvironment, normalizedEnvironment, StringComparison.Ordinal)))
|
||||
&& MatchesFilters(item.TargetRegion, regionFilter)
|
||||
&& MatchesFilters(item.TargetEnvironment, environmentFilter))
|
||||
.OrderByDescending(item => item.RequestedAt)
|
||||
.ThenBy(item => item.ApprovalId, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
@@ -270,8 +270,8 @@ public sealed class ReleaseReadModelService
|
||||
{
|
||||
var normalizedStatus = NormalizeFilter(status);
|
||||
var normalizedLane = NormalizeFilter(lane);
|
||||
var normalizedEnvironment = NormalizeFilter(environment);
|
||||
var normalizedRegion = NormalizeFilter(region);
|
||||
var environmentFilter = ParseFilterSet(environment);
|
||||
var regionFilter = ParseFilterSet(region);
|
||||
var normalizedOutcome = NormalizeFilter(outcome);
|
||||
var normalizedLimit = NormalizeLimit(limit);
|
||||
var normalizedOffset = NormalizeOffset(offset);
|
||||
@@ -281,8 +281,8 @@ public sealed class ReleaseReadModelService
|
||||
.Where(run =>
|
||||
(string.IsNullOrEmpty(normalizedStatus) || string.Equals(run.Status, normalizedStatus, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedLane) || string.Equals(run.Lane, normalizedLane, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedEnvironment) || string.Equals(run.TargetEnvironment, normalizedEnvironment, StringComparison.Ordinal))
|
||||
&& (string.IsNullOrEmpty(normalizedRegion) || string.Equals(run.TargetRegion, normalizedRegion, StringComparison.Ordinal))
|
||||
&& MatchesFilters(run.TargetEnvironment, environmentFilter)
|
||||
&& MatchesFilters(run.TargetRegion, regionFilter)
|
||||
&& (string.IsNullOrEmpty(normalizedOutcome) || string.Equals(run.Outcome, normalizedOutcome, StringComparison.Ordinal))
|
||||
&& (needsApproval is null || run.NeedsApproval == needsApproval.Value)
|
||||
&& (blockedByDataIntegrity is null || run.BlockedByDataIntegrity == blockedByDataIntegrity.Value))
|
||||
@@ -298,6 +298,53 @@ public sealed class ReleaseReadModelService
|
||||
return new ReleasePageResult<ReleaseRunProjection>(paged, projections.Length, normalizedLimit, normalizedOffset);
|
||||
}
|
||||
|
||||
public async Task<ReleasePageResult<EvidencePackProjection>> ListEvidencePacksAsync(
|
||||
PlatformRequestContext context,
|
||||
string? region,
|
||||
string? environment,
|
||||
int? limit,
|
||||
int? offset,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var regionFilter = ParseFilterSet(region);
|
||||
var environmentFilter = ParseFilterSet(environment);
|
||||
var normalizedLimit = NormalizeLimit(limit);
|
||||
var normalizedOffset = NormalizeOffset(offset);
|
||||
|
||||
var projections = (await LoadRunSeedsAsync(context, cancellationToken).ConfigureAwait(false))
|
||||
.Select(seed =>
|
||||
{
|
||||
var run = BuildRunProjection(seed);
|
||||
if (!MatchesFilters(run.TargetRegion, regionFilter) || !MatchesFilters(run.TargetEnvironment, environmentFilter))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var evidence = BuildRunEvidence(seed);
|
||||
return new EvidencePackProjection(
|
||||
CapsuleId: evidence.DecisionCapsuleId,
|
||||
RunId: run.RunId,
|
||||
ReleaseId: run.ReleaseId,
|
||||
Environment: run.TargetEnvironment,
|
||||
Region: run.TargetRegion,
|
||||
Status: ResolveEvidencePackStatus(run, evidence),
|
||||
UpdatedAt: run.UpdatedAt,
|
||||
CapsuleRoute: evidence.CapsuleRoute);
|
||||
})
|
||||
.Where(static item => item is not null)
|
||||
.Select(static item => item!)
|
||||
.OrderByDescending(item => item.UpdatedAt)
|
||||
.ThenBy(item => item.CapsuleId, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
var paged = projections
|
||||
.Skip(normalizedOffset)
|
||||
.Take(normalizedLimit)
|
||||
.ToArray();
|
||||
|
||||
return new ReleasePageResult<EvidencePackProjection>(paged, projections.Length, normalizedLimit, normalizedOffset);
|
||||
}
|
||||
|
||||
public async Task<ReleaseRunDetailProjection?> GetRunDetailAsync(
|
||||
PlatformRequestContext context,
|
||||
Guid runId,
|
||||
@@ -1469,6 +1516,59 @@ public sealed class ReleaseReadModelService
|
||||
return value.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static HashSet<string> ParseFilterSet(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
return value
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(static item => NormalizeFilter(item))
|
||||
.Where(static item => !string.IsNullOrWhiteSpace(item))
|
||||
.Select(static item => item!)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
private static bool MatchesFilters(string? value, HashSet<string> filter)
|
||||
{
|
||||
if (filter.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var normalized = NormalizeFilter(value);
|
||||
return normalized is not null && filter.Contains(normalized);
|
||||
}
|
||||
|
||||
private static string ResolveEvidencePackStatus(
|
||||
ReleaseRunProjection run,
|
||||
ReleaseRunEvidenceProjection evidence)
|
||||
{
|
||||
if (evidence.ReplayMismatch)
|
||||
{
|
||||
return "stale";
|
||||
}
|
||||
|
||||
if (run.Status is "queued" or "pending")
|
||||
{
|
||||
return "pending";
|
||||
}
|
||||
|
||||
if (string.Equals(evidence.SignatureStatus, "unsigned", StringComparison.Ordinal))
|
||||
{
|
||||
return "unsigned";
|
||||
}
|
||||
|
||||
if (string.Equals(evidence.ChainCompleteness, "partial", StringComparison.Ordinal))
|
||||
{
|
||||
return "stale";
|
||||
}
|
||||
|
||||
return "current";
|
||||
}
|
||||
|
||||
private static int NormalizeLimit(int? value)
|
||||
{
|
||||
return value switch
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Platform.WebService.Constants;
|
||||
using StellaOps.Platform.WebService.Contracts;
|
||||
using StellaOps.TestKit;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Tests;
|
||||
|
||||
public sealed class EvidenceReadModelEndpointsTests : IClassFixture<PlatformWebApplicationFactory>
|
||||
{
|
||||
private readonly PlatformWebApplicationFactory _factory;
|
||||
|
||||
public EvidenceReadModelEndpointsTests(PlatformWebApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidencePacksEndpoint_ReturnsDeterministicFilteredProjections()
|
||||
{
|
||||
var tenantId = Guid.NewGuid().ToString("D");
|
||||
using var client = CreateTenantClient(tenantId);
|
||||
|
||||
var search = await SeedReleaseAsync(client, "search-evidence", "Search Evidence", "us-prod", "critical-fix");
|
||||
var catalog = await SeedReleaseAsync(client, "catalog-evidence", "Catalog Evidence", "eu-prod", "policy-review");
|
||||
|
||||
var first = await client.GetFromJsonAsync<PlatformListResponse<EvidencePackProjection>>(
|
||||
"/api/v2/evidence/packs?region=us-east,eu-west&environment=us-prod,eu-prod&limit=50&offset=0",
|
||||
TestContext.Current.CancellationToken);
|
||||
var second = await client.GetFromJsonAsync<PlatformListResponse<EvidencePackProjection>>(
|
||||
"/api/v2/evidence/packs?region=us-east,eu-west&environment=us-prod,eu-prod&limit=50&offset=0",
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(first);
|
||||
Assert.NotNull(second);
|
||||
Assert.NotEmpty(first!.Items);
|
||||
Assert.Equal(
|
||||
first.Items.Select(item => item.CapsuleId).ToArray(),
|
||||
second!.Items.Select(item => item.CapsuleId).ToArray());
|
||||
|
||||
Assert.Contains(first.Items, item => item.ReleaseId == search.Bundle.Id.ToString("D"));
|
||||
Assert.Contains(first.Items, item => item.ReleaseId == catalog.Bundle.Id.ToString("D"));
|
||||
Assert.All(first.Items, item =>
|
||||
{
|
||||
Assert.Contains(item.Region, new[] { "us-east", "eu-west" });
|
||||
Assert.Contains(item.Environment, new[] { "us-prod", "eu-prod" });
|
||||
Assert.False(string.IsNullOrWhiteSpace(item.CapsuleRoute));
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EvidencePacksEndpoint_WithoutTenantHeader_ReturnsBadRequest()
|
||||
{
|
||||
using var client = _factory.CreateClient();
|
||||
var response = await client.GetAsync("/api/v2/evidence/packs", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidencePacksEndpoint_RequiresReleaseControlReadPolicy()
|
||||
{
|
||||
var endpoints = _factory.Services
|
||||
.GetRequiredService<EndpointDataSource>()
|
||||
.Endpoints
|
||||
.OfType<RouteEndpoint>()
|
||||
.ToArray();
|
||||
|
||||
var endpoint = endpoints.Single(candidate =>
|
||||
string.Equals(candidate.RoutePattern.RawText, "/api/v2/evidence/packs", StringComparison.Ordinal)
|
||||
&& candidate.Metadata.GetMetadata<HttpMethodMetadata>()?.HttpMethods.Contains("GET", StringComparer.OrdinalIgnoreCase) == true);
|
||||
|
||||
var policies = endpoint.Metadata
|
||||
.GetOrderedMetadata<IAuthorizeData>()
|
||||
.Select(metadata => metadata.Policy)
|
||||
.Where(static policy => !string.IsNullOrWhiteSpace(policy))
|
||||
.ToArray();
|
||||
|
||||
Assert.Contains(PlatformPolicies.ReleaseControlRead, policies);
|
||||
}
|
||||
|
||||
private static async Task<SeededRelease> SeedReleaseAsync(
|
||||
HttpClient client,
|
||||
string slug,
|
||||
string name,
|
||||
string targetEnvironment,
|
||||
string reason)
|
||||
{
|
||||
var createResponse = await client.PostAsJsonAsync(
|
||||
"/api/v1/release-control/bundles",
|
||||
new CreateReleaseControlBundleRequest(slug, name, $"{name} description"),
|
||||
TestContext.Current.CancellationToken);
|
||||
createResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var bundle = await createResponse.Content.ReadFromJsonAsync<ReleaseControlBundleDetail>(
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(bundle);
|
||||
|
||||
var publishResponse = await client.PostAsJsonAsync(
|
||||
$"/api/v1/release-control/bundles/{bundle!.Id:D}/versions",
|
||||
new PublishReleaseControlBundleVersionRequest(
|
||||
Changelog: "baseline",
|
||||
Components:
|
||||
[
|
||||
new ReleaseControlBundleComponentInput(
|
||||
ComponentVersionId: $"{slug}@1.0.0",
|
||||
ComponentName: slug,
|
||||
ImageDigest: "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
DeployOrder: 10,
|
||||
MetadataJson: "{\"track\":\"stable\"}")
|
||||
]),
|
||||
TestContext.Current.CancellationToken);
|
||||
publishResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var version = await publishResponse.Content.ReadFromJsonAsync<ReleaseControlBundleVersionDetail>(
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(version);
|
||||
|
||||
var materializeResponse = await client.PostAsJsonAsync(
|
||||
$"/api/v1/release-control/bundles/{bundle.Id:D}/versions/{version!.Id:D}/materialize",
|
||||
new MaterializeReleaseControlBundleVersionRequest(targetEnvironment, reason, Guid.NewGuid().ToString("N")),
|
||||
TestContext.Current.CancellationToken);
|
||||
materializeResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var run = await materializeResponse.Content.ReadFromJsonAsync<ReleaseControlBundleMaterializationRun>(
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(run);
|
||||
|
||||
return new SeededRelease(bundle, version, run!);
|
||||
}
|
||||
|
||||
private HttpClient CreateTenantClient(string tenantId)
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
|
||||
client.DefaultRequestHeaders.Add("X-StellaOps-Actor", "evidence-v2-tests");
|
||||
return client;
|
||||
}
|
||||
|
||||
private sealed record SeededRelease(
|
||||
ReleaseControlBundleDetail Bundle,
|
||||
ReleaseControlBundleVersionDetail Version,
|
||||
ReleaseControlBundleMaterializationRun Run);
|
||||
}
|
||||
@@ -133,6 +133,27 @@ public sealed class ReleaseReadModelEndpointsTests : IClassFixture<PlatformWebAp
|
||||
Assert.All(euApprovals.Items, item => Assert.Equal("eu-west", item.TargetRegion));
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReleasesEndpoints_AcceptCommaDelimitedRegionAndEnvironmentFilters()
|
||||
{
|
||||
var tenantId = Guid.NewGuid().ToString("D");
|
||||
using var client = CreateTenantClient(tenantId);
|
||||
|
||||
await SeedReleaseAsync(client, "search-hotfix", "Search Hotfix", "us-prod", "critical-fix");
|
||||
await SeedReleaseAsync(client, "catalog-release", "Catalog Release", "eu-prod", "policy-review");
|
||||
|
||||
var activity = await client.GetFromJsonAsync<PlatformListResponse<ReleaseActivityProjection>>(
|
||||
"/api/v2/releases/activity?region=us-east,eu-west&environment=us-prod,eu-prod&limit=50&offset=0",
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.NotEmpty(activity!.Items);
|
||||
Assert.Contains(activity.Items, item => item.TargetRegion == "us-east");
|
||||
Assert.Contains(activity.Items, item => item.TargetRegion == "eu-west");
|
||||
Assert.All(activity.Items, item => Assert.Contains(item.TargetEnvironment, new[] { "us-prod", "eu-prod" }));
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReleasesEndpoints_WithoutTenantHeader_ReturnBadRequest()
|
||||
|
||||
Reference in New Issue
Block a user