V13-FE-011: finalize search list layout slice

This commit is contained in:
2026-08-09 02:57:26 +09:00
parent 9efd202e76
commit 6422cb2b13
984 changed files with 120811 additions and 1498 deletions
@@ -0,0 +1,24 @@
using System.Security.Cryptography;
namespace Kbx.Tests.Scenarios;
public sealed record KbxScenarioArtifact(string Type, string Path, string? Sha256 = null);
public sealed record KbxScenarioEvidence(
string ScenarioId,
string RunId,
DateTimeOffset StartedAt,
DateTimeOffset? FinishedAt,
string Result,
IReadOnlyDictionary<string,string> ContractVersions,
IReadOnlyList<string> CorrelationIds,
IReadOnlyList<KbxScenarioArtifact> Artifacts);
public static class KbxScenarioHash
{
public static string Sha256File(string path)
{
using var stream = File.OpenRead(path);
return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant();
}
}
@@ -0,0 +1,39 @@
using Xunit;
using Npgsql;
using Testcontainers.PostgreSql;
namespace Kbx.Tests.Scenarios;
/// <summary>
/// Host repository reference fixture. Requires Testcontainers.PostgreSql and Npgsql.
/// The container is disposable; never point this fixture at a production connection string.
/// </summary>
public sealed class KbxScenarioPostgresFixture : IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:17-alpine")
.WithDatabase("kbx_scenario_test")
.WithUsername("kbx")
.WithPassword("kbx-test-only")
.Build();
public string ConnectionString => _postgres.GetConnectionString();
public async Task InitializeAsync()
{
await _postgres.StartAsync();
// Host: run DbUp migrations in filename order, then bootstrap/reset/seed SQL.
}
public Task DisposeAsync() => _postgres.DisposeAsync().AsTask();
public static async Task AssertTestGuardAsync(string connectionString, CancellationToken ct = default)
{
await using var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync(ct);
await using var command = new NpgsqlCommand(
"select exists(select 1 from kbx_test.environment_guard where marker='KBX_SCENARIO_TEST_ONLY')", connection);
if (await command.ExecuteScalarAsync(ct) is not true)
throw new InvalidOperationException("KBX scenario database guard is missing.");
}
}
@@ -0,0 +1,38 @@
using System.Text.Json;
using ClosedXML.Excel;
namespace Kbx.Tests.Scenarios;
/// <summary>Builds an XLSX from the checked-in synthetic JSON fixture. No production spreadsheet is stored in source control.</summary>
public static class KbxSyntheticImportWorkbook
{
private sealed record Fixture(string Sheet, string[] Columns, JsonElement[][] Rows);
public static void Build(string jsonPath, string xlsxPath)
{
var fixture = JsonSerializer.Deserialize<Fixture>(File.ReadAllText(jsonPath),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
?? throw new InvalidOperationException("Invalid synthetic import fixture.");
using var workbook = new XLWorkbook();
var sheet = workbook.Worksheets.Add(fixture.Sheet);
for (var c = 0; c < fixture.Columns.Length; c++)
sheet.Cell(1, c + 1).Value = fixture.Columns[c];
for (var r = 0; r < fixture.Rows.Length; r++)
for (var c = 0; c < fixture.Rows[r].Length; c++)
{
var cell = sheet.Cell(r + 2, c + 1);
var value = fixture.Rows[r][c];
cell.Value = value.ValueKind switch
{
JsonValueKind.Number when value.TryGetDecimal(out var number) => number,
JsonValueKind.String => value.GetString() ?? string.Empty,
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => value.ToString()
};
}
workbook.SaveAs(xlsxPath);
}
}