search and ai stabilization work, localization stablized.

This commit is contained in:
master
2026-02-24 23:29:36 +02:00
parent 4f947a8b61
commit b07d27772e
766 changed files with 55299 additions and 3221 deletions

View File

@@ -4567,6 +4567,7 @@ spec:
public EntryTraceResponseModel? EntryTraceResponse { get; set; }
public Exception? EntryTraceException { get; set; }
public string? LastEntryTraceScanId { get; private set; }
public (string Tenant, string Locale)? LastLanguagePreferenceSet { get; private set; }
public List<(AdvisoryAiTaskType TaskType, AdvisoryPipelinePlanRequestModel Request)> AdvisoryPlanRequests { get; } = new();
public AdvisoryPipelinePlanResponseModel? AdvisoryPlanResponse { get; set; }
public Exception? AdvisoryPlanException { get; set; }
@@ -4947,6 +4948,20 @@ spec:
public Task<AnalyticsListResponse<AnalyticsComponentTrendPoint>> GetAnalyticsComponentTrendsAsync(string? environment, int? days, CancellationToken cancellationToken)
=> Task.FromResult(new AnalyticsListResponse<AnalyticsComponentTrendPoint>(Array.Empty<AnalyticsComponentTrendPoint>()));
public Task<PlatformAvailableLocalesResponse> GetAvailableLocalesAsync(string tenant, CancellationToken cancellationToken)
=> Task.FromResult(new PlatformAvailableLocalesResponse(
new[] { "en-US", "de-DE", "bg-BG", "ru-RU", "es-ES", "fr-FR", "uk-UA", "zh-TW", "zh-CN" },
9));
public Task<PlatformLanguagePreferenceResponse> GetLanguagePreferenceAsync(string tenant, CancellationToken cancellationToken)
=> Task.FromResult(new PlatformLanguagePreferenceResponse(tenant, "stub-actor", null, DateTimeOffset.UtcNow, "stub"));
public Task<PlatformLanguagePreferenceResponse> SetLanguagePreferenceAsync(string tenant, string locale, CancellationToken cancellationToken)
{
LastLanguagePreferenceSet = (tenant, locale);
return Task.FromResult(new PlatformLanguagePreferenceResponse(tenant, "stub-actor", locale, DateTimeOffset.UtcNow, "stub"));
}
public Task<WitnessListResponse> ListWitnessesAsync(WitnessListRequest request, CancellationToken cancellationToken)
=> Task.FromResult(new WitnessListResponse());
@@ -4958,6 +4973,49 @@ spec:
public Task<Stream> DownloadWitnessAsync(string witnessId, WitnessExportFormat format, CancellationToken cancellationToken)
=> Task.FromResult<Stream>(new MemoryStream(Encoding.UTF8.GetBytes("{}")));
// CLI-IDP-001: Identity provider management stubs
public Task<IReadOnlyList<IdentityProviderDto>> ListIdentityProvidersAsync(CancellationToken cancellationToken)
=> Task.FromResult<IReadOnlyList<IdentityProviderDto>>(Array.Empty<IdentityProviderDto>());
public Task<IdentityProviderDto?> GetIdentityProviderAsync(string name, CancellationToken cancellationToken)
=> Task.FromResult<IdentityProviderDto?>(null);
public Task<IdentityProviderDto> CreateIdentityProviderAsync(CreateIdentityProviderRequest request, CancellationToken cancellationToken)
=> Task.FromResult(new IdentityProviderDto
{
Id = Guid.NewGuid(),
Name = request.Name,
Type = request.Type,
Enabled = request.Enabled,
Configuration = request.Configuration,
Description = request.Description,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
public Task<IdentityProviderDto> UpdateIdentityProviderAsync(Guid id, UpdateIdentityProviderRequest request, CancellationToken cancellationToken)
=> Task.FromResult(new IdentityProviderDto
{
Id = id,
Name = "updated",
Type = "standard",
Enabled = request.Enabled ?? true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
public Task<bool> DeleteIdentityProviderAsync(Guid id, CancellationToken cancellationToken)
=> Task.FromResult(true);
public Task<TestConnectionResult> TestIdentityProviderConnectionAsync(TestConnectionRequest request, CancellationToken cancellationToken)
=> Task.FromResult(new TestConnectionResult { Success = true, Message = "Connection successful", LatencyMs = 42 });
public Task<bool> EnableIdentityProviderAsync(Guid id, CancellationToken cancellationToken)
=> Task.FromResult(true);
public Task<bool> DisableIdentityProviderAsync(Guid id, CancellationToken cancellationToken)
=> Task.FromResult(true);
}
private sealed class StubExecutor : IScannerExecutor
@@ -5177,5 +5235,83 @@ spec:
AnsiConsole.Console = originalConsole;
}
}
}
[Fact]
public async Task HandleTenantsLocaleListAsync_AsJsonIncludesUkrainianLocale()
{
var originalExit = Environment.ExitCode;
var options = new StellaOpsCliOptions
{
BackendUrl = "https://platform.local",
ResultsDirectory = Path.Combine(Path.GetTempPath(), $"stellaops-cli-results-{Guid.NewGuid():N}")
};
try
{
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
var provider = BuildServiceProvider(backend, options: options);
var output = await CaptureTestConsoleAsync(async _ =>
{
await CommandHandlers.HandleTenantsLocaleListAsync(
provider,
options,
tenant: "tenant-alpha",
json: true,
verbose: false,
cancellationToken: CancellationToken.None);
});
Assert.Equal(0, Environment.ExitCode);
using var document = JsonDocument.Parse(output.PlainBuffer);
var locales = document.RootElement.GetProperty("locales")
.EnumerateArray()
.Select(static locale => locale.GetString())
.Where(static locale => !string.IsNullOrWhiteSpace(locale))
.Select(static locale => locale!)
.ToArray();
Assert.Contains("uk-UA", locales, StringComparer.OrdinalIgnoreCase);
}
finally
{
Environment.ExitCode = originalExit;
}
}
[Fact]
public async Task HandleTenantsLocaleSetAsync_RejectsLocaleOutsideCatalog()
{
var originalExit = Environment.ExitCode;
var options = new StellaOpsCliOptions
{
BackendUrl = "https://platform.local",
ResultsDirectory = Path.Combine(Path.GetTempPath(), $"stellaops-cli-results-{Guid.NewGuid():N}")
};
try
{
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
var provider = BuildServiceProvider(backend, options: options);
var output = await CaptureTestConsoleAsync(async _ =>
{
await CommandHandlers.HandleTenantsLocaleSetAsync(
provider,
options,
locale: "xx-XX",
tenant: "tenant-alpha",
json: false,
verbose: false,
cancellationToken: CancellationToken.None);
});
Assert.Equal(1, Environment.ExitCode);
Assert.Null(backend.LastLanguagePreferenceSet);
Assert.Contains("not available", output.Combined, StringComparison.OrdinalIgnoreCase);
}
finally
{
Environment.ExitCode = originalExit;
}
}
}

View File

@@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Globalization;
using System.IO;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using StellaOps.Cli.Commands;
using StellaOps.Cli.Services;
using StellaOps.Cli.Services.Models;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Cli.Tests.Commands;
[Trait("Category", TestCategories.Unit)]
public sealed class IdentityProviderCommandGroupTests
{
[Fact]
public async Task ListCommand_JsonOutput_CallsListIdentityProvidersAsync()
{
var providers = new List<IdentityProviderDto>
{
new()
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"),
Name = "corp-ldap",
Type = "ldap",
Enabled = true,
Configuration = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["Host"] = "ldap.corp.example.com",
["Port"] = "636"
},
HealthStatus = "healthy",
CreatedAt = DateTimeOffset.Parse("2026-02-20T10:00:00Z", CultureInfo.InvariantCulture),
UpdatedAt = DateTimeOffset.Parse("2026-02-20T10:00:00Z", CultureInfo.InvariantCulture)
},
new()
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000002"),
Name = "okta-oidc",
Type = "oidc",
Enabled = false,
Configuration = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["Authority"] = "https://okta.example.com"
},
HealthStatus = "unknown",
CreatedAt = DateTimeOffset.Parse("2026-02-21T12:00:00Z", CultureInfo.InvariantCulture),
UpdatedAt = DateTimeOffset.Parse("2026-02-21T12:00:00Z", CultureInfo.InvariantCulture)
}
};
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.ListIdentityProvidersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(providers);
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(root, "identity-providers list --json");
Assert.Equal(0, invocation.ExitCode);
using var doc = JsonDocument.Parse(invocation.StdOut);
var arr = doc.RootElement;
Assert.Equal(2, arr.GetArrayLength());
Assert.Equal("corp-ldap", arr[0].GetProperty("name").GetString());
Assert.Equal("ldap", arr[0].GetProperty("type").GetString());
Assert.True(arr[0].GetProperty("enabled").GetBoolean());
Assert.Equal("okta-oidc", arr[1].GetProperty("name").GetString());
Assert.Equal("oidc", arr[1].GetProperty("type").GetString());
Assert.False(arr[1].GetProperty("enabled").GetBoolean());
backend.VerifyAll();
}
[Fact]
public async Task AddCommand_LdapType_CallsCreateWithCorrectConfig()
{
CreateIdentityProviderRequest? capturedRequest = null;
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.CreateIdentityProviderAsync(
It.IsAny<CreateIdentityProviderRequest>(),
It.IsAny<CancellationToken>()))
.Callback<CreateIdentityProviderRequest, CancellationToken>((req, _) => capturedRequest = req)
.ReturnsAsync(new IdentityProviderDto
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000003"),
Name = "test-ldap",
Type = "ldap",
Enabled = true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(
root,
"identity-providers add --name test-ldap --type ldap --host ldap.example.com --port 636 --bind-dn cn=admin,dc=example,dc=com --search-base ou=users,dc=example,dc=com --use-ssl true");
Assert.Equal(0, invocation.ExitCode);
Assert.NotNull(capturedRequest);
Assert.Equal("test-ldap", capturedRequest!.Name);
Assert.Equal("ldap", capturedRequest.Type);
Assert.True(capturedRequest.Enabled);
Assert.Equal("ldap.example.com", capturedRequest.Configuration["Host"]);
Assert.Equal("636", capturedRequest.Configuration["Port"]);
Assert.Equal("cn=admin,dc=example,dc=com", capturedRequest.Configuration["BindDn"]);
Assert.Equal("ou=users,dc=example,dc=com", capturedRequest.Configuration["SearchBase"]);
Assert.Equal("true", capturedRequest.Configuration["UseSsl"]);
Assert.Contains("created successfully", invocation.StdOut, StringComparison.OrdinalIgnoreCase);
backend.VerifyAll();
}
[Fact]
public async Task AddCommand_OidcType_CallsCreateWithCorrectConfig()
{
CreateIdentityProviderRequest? capturedRequest = null;
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.CreateIdentityProviderAsync(
It.IsAny<CreateIdentityProviderRequest>(),
It.IsAny<CancellationToken>()))
.Callback<CreateIdentityProviderRequest, CancellationToken>((req, _) => capturedRequest = req)
.ReturnsAsync(new IdentityProviderDto
{
Id = Guid.Parse("00000000-0000-0000-0000-000000000004"),
Name = "okta-prod",
Type = "oidc",
Enabled = true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(
root,
"identity-providers add --name okta-prod --type oidc --authority https://okta.example.com --client-id my-client --client-secret my-secret");
Assert.Equal(0, invocation.ExitCode);
Assert.NotNull(capturedRequest);
Assert.Equal("okta-prod", capturedRequest!.Name);
Assert.Equal("oidc", capturedRequest.Type);
Assert.Equal("https://okta.example.com", capturedRequest.Configuration["Authority"]);
Assert.Equal("my-client", capturedRequest.Configuration["ClientId"]);
Assert.Equal("my-secret", capturedRequest.Configuration["ClientSecret"]);
backend.VerifyAll();
}
[Fact]
public async Task RemoveCommand_CallsDeleteIdentityProviderAsync()
{
var providerId = Guid.Parse("00000000-0000-0000-0000-000000000005");
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.GetIdentityProviderAsync("corp-ldap", It.IsAny<CancellationToken>()))
.ReturnsAsync(new IdentityProviderDto
{
Id = providerId,
Name = "corp-ldap",
Type = "ldap",
Enabled = true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
backend
.Setup(c => c.DeleteIdentityProviderAsync(providerId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(root, "identity-providers remove corp-ldap");
Assert.Equal(0, invocation.ExitCode);
Assert.Contains("removed", invocation.StdOut, StringComparison.OrdinalIgnoreCase);
backend.VerifyAll();
}
[Fact]
public async Task RemoveCommand_NotFound_SetsExitCodeOne()
{
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.GetIdentityProviderAsync("missing", It.IsAny<CancellationToken>()))
.ReturnsAsync((IdentityProviderDto?)null);
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(root, "identity-providers remove missing");
Assert.Equal(1, invocation.ExitCode);
Assert.Contains("not found", invocation.StdErr, StringComparison.OrdinalIgnoreCase);
backend.VerifyAll();
}
[Fact]
public async Task EnableCommand_CallsEnableIdentityProviderAsync()
{
var providerId = Guid.Parse("00000000-0000-0000-0000-000000000006");
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.GetIdentityProviderAsync("my-saml", It.IsAny<CancellationToken>()))
.ReturnsAsync(new IdentityProviderDto
{
Id = providerId,
Name = "my-saml",
Type = "saml",
Enabled = false,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
backend
.Setup(c => c.EnableIdentityProviderAsync(providerId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(root, "identity-providers enable my-saml");
Assert.Equal(0, invocation.ExitCode);
Assert.Contains("enabled", invocation.StdOut, StringComparison.OrdinalIgnoreCase);
backend.VerifyAll();
}
[Fact]
public async Task DisableCommand_CallsDisableIdentityProviderAsync()
{
var providerId = Guid.Parse("00000000-0000-0000-0000-000000000007");
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(c => c.GetIdentityProviderAsync("my-oidc", It.IsAny<CancellationToken>()))
.ReturnsAsync(new IdentityProviderDto
{
Id = providerId,
Name = "my-oidc",
Type = "oidc",
Enabled = true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
backend
.Setup(c => c.DisableIdentityProviderAsync(providerId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(IdentityProviderCommandGroup.BuildIdentityProviderCommand(services, CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(root, "identity-providers disable my-oidc");
Assert.Equal(0, invocation.ExitCode);
Assert.Contains("disabled", invocation.StdOut, StringComparison.OrdinalIgnoreCase);
backend.VerifyAll();
}
private static async Task<CommandInvocationResult> InvokeWithCapturedConsoleAsync(
RootCommand root,
string commandLine)
{
var originalOut = Console.Out;
var originalError = Console.Error;
var originalExitCode = Environment.ExitCode;
Environment.ExitCode = 0;
var stdout = new StringWriter(CultureInfo.InvariantCulture);
var stderr = new StringWriter(CultureInfo.InvariantCulture);
try
{
Console.SetOut(stdout);
Console.SetError(stderr);
var exitCode = await root.Parse(commandLine).InvokeAsync();
var capturedExitCode = Environment.ExitCode != 0 ? Environment.ExitCode : exitCode;
return new CommandInvocationResult(capturedExitCode, stdout.ToString(), stderr.ToString());
}
finally
{
Console.SetOut(originalOut);
Console.SetError(originalError);
Environment.ExitCode = originalExitCode;
}
}
private sealed record CommandInvocationResult(int ExitCode, string StdOut, string StdErr);
}

View File

@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Cli.Services.Models;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Cli.Tests.Integration;
/// <summary>
/// CLI integration tests for identity provider commands against real IDP containers.
/// Requires: docker compose -f devops/compose/docker-compose.idp-testing.yml --profile idp up -d
/// Execute: dotnet test --filter "FullyQualifiedName~IdentityProviderIntegrationTests"
/// </summary>
[Trait("Category", TestCategories.Integration)]
[Collection("IdpContainerTests")]
public sealed class IdentityProviderIntegrationTests
{
private const string LdapHost = "localhost";
private const int LdapPort = 3389;
private const string KeycloakBaseUrl = "http://localhost:8280";
/// <summary>
/// Validates the CLI model DTOs can be constructed and their properties match API contract.
/// This is a local-only test that does not require containers.
/// </summary>
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IdentityProviderDto_PropertiesAreAccessible()
{
var dto = new IdentityProviderDto
{
Id = Guid.NewGuid(),
Name = "test-provider",
Type = "ldap",
Enabled = true,
Configuration = new Dictionary<string, string?>
{
["host"] = "ldap.test",
["port"] = "389"
},
Description = "Test",
HealthStatus = "healthy",
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
};
Assert.Equal("test-provider", dto.Name);
Assert.Equal("ldap", dto.Type);
Assert.True(dto.Enabled);
Assert.Equal("ldap.test", dto.Configuration["host"]);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateIdentityProviderRequest_CanBeConstructed()
{
var request = new CreateIdentityProviderRequest
{
Name = "my-ldap",
Type = "ldap",
Enabled = true,
Configuration = new Dictionary<string, string?>
{
["host"] = "ldap.example.com",
["port"] = "636",
["bindDn"] = "cn=admin,dc=example,dc=com",
["bindPassword"] = "secret",
["searchBase"] = "dc=example,dc=com"
},
Description = "Production LDAP"
};
Assert.Equal("my-ldap", request.Name);
Assert.Equal("ldap", request.Type);
Assert.Equal(5, request.Configuration.Count);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TestConnectionRequest_SamlType()
{
var request = new TestConnectionRequest
{
Type = "saml",
Configuration = new Dictionary<string, string?>
{
["spEntityId"] = "stellaops-sp",
["idpEntityId"] = "https://idp.example.com",
["idpMetadataUrl"] = "https://idp.example.com/metadata"
}
};
Assert.Equal("saml", request.Type);
Assert.Equal(3, request.Configuration.Count);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TestConnectionRequest_OidcType()
{
var request = new TestConnectionRequest
{
Type = "oidc",
Configuration = new Dictionary<string, string?>
{
["authority"] = "https://auth.example.com",
["clientId"] = "stellaops",
["clientSecret"] = "secret"
}
};
Assert.Equal("oidc", request.Type);
Assert.Equal(3, request.Configuration.Count);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TestConnectionResult_SuccessAndFailure()
{
var success = new TestConnectionResult
{
Success = true,
Message = "Connection successful",
LatencyMs = 42
};
Assert.True(success.Success);
Assert.Equal(42, success.LatencyMs);
var failure = new TestConnectionResult
{
Success = false,
Message = "Connection timed out",
LatencyMs = 10000
};
Assert.False(failure.Success);
}
// --- Container-dependent tests below ---
[Fact(Skip = "Requires docker compose idp containers")]
public async Task AddLdapProvider_ListShowsIt()
{
// This test would exercise the CLI backend client against the Platform API
// which connects to real OpenLDAP container
await Task.CompletedTask;
}
[Fact(Skip = "Requires docker compose idp containers")]
public async Task AddSamlProvider_WithKeycloakMetadata()
{
// Would test creating a SAML provider pointing to Keycloak's metadata URL
await Task.CompletedTask;
}
[Fact(Skip = "Requires docker compose idp containers")]
public async Task AddOidcProvider_WithKeycloakDiscovery()
{
// Would test creating an OIDC provider pointing to Keycloak's OIDC endpoint
await Task.CompletedTask;
}
[Fact(Skip = "Requires docker compose idp containers")]
public async Task TestConnection_LiveLdap_Succeeds()
{
// Would test the test-connection command against real OpenLDAP
await Task.CompletedTask;
}
[Fact(Skip = "Requires docker compose idp containers")]
public async Task DisableAndEnable_Provider()
{
// Would test the disable/enable commands
await Task.CompletedTask;
}
[Fact(Skip = "Requires docker compose idp containers")]
public async Task RemoveProvider_RemovesFromList()
{
// Would test the remove command
await Task.CompletedTask;
}
}

View File

@@ -50,3 +50,5 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| PAPI-005-TESTS | DONE | SPRINT_20260210_005 - DevPortal portable-v1 verifier matrix hardened with manifest/DSSE/Rekor/Parquet fail-closed tests; CLI suite passed (1182 passed) on 2026-02-10. |
| SPRINT_20260224_004-LOC-303-T | DONE | Sprint `docs/implplan/SPRINT_20260224_004_Platform_user_locale_expansion_and_cli_persistence.md`: updated `CommandHandlersTests` backend stubs for new locale preference client methods; full-suite execution reached `1196/1201` with unrelated pre-existing failures in migration/knowledge-search/risk-budget test lanes. |
| SPRINT_20260224_004-LOC-308-CLI-T | DONE | Sprint `docs/implplan/SPRINT_20260224_004_Platform_user_locale_expansion_and_cli_persistence.md`: added command-handler coverage for locale catalog listing (`tenants locale list`) and unsupported locale rejection before preference writes. |