b211911f98
v2-ci / unit-tests (push) Failing after 13m22s
Akka.TestKit.Xunit2 -> Akka.TestKit.Xunit 1.5.62 (xunit.v3 TestKit; needs xunit.v3 3.2.2, set via VersionOverride on the four projects). Removes the xunit 2.9.3 pin. IAsyncLifetime returns ValueTask, SqlServerFactAttribute gains the source-info ctor, and xUnit1051 call sites pass TestContext.Current.CancellationToken. Test counts unchanged. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
134 lines
5.8 KiB
C#
134 lines
5.8 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
|
using ZB.MOM.WW.OtOpcUa.Security.Auth;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw;
|
|
|
|
/// <summary>
|
|
/// R04 P6: the CSV import grid renders through Virtualize from a precomputed view, and the export is
|
|
/// downloaded from an endpoint (bounded preview in the modal) instead of being embedded in the circuit.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class RawCsvRenderingTests
|
|
{
|
|
private static RawCsvParseResult Result(params (int Row, bool Ok)[] rows) =>
|
|
new(["Name"], rows.Select(r => new RawCsvReviewRow
|
|
{
|
|
RowNumber = r.Row, Name = $"t{r.Row}", Error = r.Ok ? null : "bad",
|
|
ImportRow = r.Ok ? new RawTagImportRow(null, new RawTagInput($"t{r.Row}", "Int32", Configuration.Enums.TagAccessLevel.Read, false, null, "{}")) : null,
|
|
}).ToList(), null);
|
|
|
|
[Fact]
|
|
public void Review_view_counts_and_filters_once_per_change()
|
|
{
|
|
var view = new RawCsvReviewView();
|
|
view.Set(Result((1, true), (2, false), (3, true)));
|
|
|
|
view.OkCount.ShouldBe(2);
|
|
view.ErrorCount.ShouldBe(1);
|
|
view.RemainingCount.ShouldBe(3);
|
|
|
|
view.Remove(2);
|
|
view.ErrorCount.ShouldBe(0);
|
|
view.VisibleRows.Select(r => r.RowNumber).ShouldBe([1, 3]);
|
|
view.CommitRows().Select(r => r.Tag.Name).ShouldBe(["t1", "t3"]);
|
|
|
|
view.Set(null);
|
|
view.RemainingCount.ShouldBe(0);
|
|
// Positive control: a fresh set after a clear shows every row again (removals are forgotten).
|
|
view.Set(Result((1, true), (2, false)));
|
|
view.RemainingCount.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public void Preview_is_bounded_and_flags_truncation()
|
|
{
|
|
var csv = string.Concat(Enumerable.Range(0, 200).Select(i => $"row{i}\n"));
|
|
|
|
var (preview, truncated) = RawTagCsvExport.Preview(csv, maxLines: 50);
|
|
truncated.ShouldBeTrue();
|
|
preview.Split('\n', StringSplitOptions.RemoveEmptyEntries).Length.ShouldBe(50);
|
|
|
|
var (all, cut) = RawTagCsvExport.Preview("a\nb\n", maxLines: 50);
|
|
cut.ShouldBeFalse();
|
|
all.ShouldBe("a\nb\n");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Preview_build_maps_only_the_first_rows_but_counts_all()
|
|
{
|
|
// Review r0 M8: the modal maps only its preview's rows; the full count still comes back.
|
|
var dbName = Uns.RawTreeTestDb.SeedFresh();
|
|
var reader = new RawTagCsvExportReader(Uns.RawTreeTestDb.Factory(dbName));
|
|
|
|
var full = await RawTagCsvExport.BuildAsync(reader, Uns.RawTreeTestDb.FolderedDeviceId, null, "Modbus", TestContext.Current.CancellationToken);
|
|
var preview = await RawTagCsvExport.BuildAsync(reader, Uns.RawTreeTestDb.FolderedDeviceId, null, "Modbus", ct: TestContext.Current.CancellationToken, maxRows: 1);
|
|
|
|
full!.RowCount.ShouldBe(2);
|
|
Commons.Csv.CsvParser.Parse(full.Csv).Count.ShouldBe(3); // header + 2 (positive control)
|
|
preview!.RowCount.ShouldBe(2);
|
|
Commons.Csv.CsvParser.Parse(preview.Csv).Count.ShouldBe(2); // header + 1
|
|
}
|
|
|
|
[Fact]
|
|
public void Attachment_header_carries_an_rfc5987_name_for_non_ascii_ids()
|
|
{
|
|
var header = RawTagCsvExport.AttachmentHeader(RawTagCsvExport.BuildFileName("Prüfstand"));
|
|
|
|
header.ShouldStartWith("attachment");
|
|
header.ShouldContain("filename*=UTF-8''tags-Pr%C3%BCfstand.csv");
|
|
// Positive control: an ASCII name is still a plain filename.
|
|
RawTagCsvExport.AttachmentHeader("tags-dev1.csv").ShouldContain("filename=tags-dev1.csv");
|
|
}
|
|
|
|
[Fact]
|
|
public void Download_url_escapes_its_query_values()
|
|
{
|
|
RawTagCsvExport.DownloadUrl("dev 1&x", null, "Modbus")
|
|
.ShouldBe("/api/raw/tags.csv?deviceId=dev%201%26x&driverType=Modbus");
|
|
RawTagCsvExport.DownloadUrl("", "TG/1", null).ShouldBe("/api/raw/tags.csv?tagGroupId=TG%2F1");
|
|
}
|
|
|
|
[Fact]
|
|
public void Download_endpoint_requires_the_same_ConfigEditor_policy_as_the_raw_page()
|
|
{
|
|
var builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddDbContextFactory<OtOpcUaConfigDbContext>(o => o.UseInMemoryDatabase("csv-export-metadata"));
|
|
builder.Services.AddScoped<RawTagCsvExportReader>();
|
|
var app = builder.Build();
|
|
app.MapRawTagCsvExport();
|
|
|
|
var endpoint = ((IEndpointRouteBuilder)app).DataSources.SelectMany(d => d.Endpoints).OfType<RouteEndpoint>()
|
|
.Single(e => e.RoutePattern.RawText == RawTagCsvExport.DownloadPath);
|
|
|
|
endpoint.Metadata.GetMetadata<IAllowAnonymous>().ShouldBeNull();
|
|
endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>().Select(a => a.Policy)
|
|
.ShouldContain(AdminUiPolicies.ConfigEditor);
|
|
// Parity with the page that links to it.
|
|
typeof(ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Raw.GlobalRaw)
|
|
.GetCustomAttributes(typeof(AuthorizeAttribute), false).Cast<AuthorizeAttribute>()
|
|
.Single().Policy.ShouldBe(AdminUiPolicies.ConfigEditor);
|
|
}
|
|
|
|
[Fact]
|
|
public void Modals_no_longer_embed_the_csv_or_render_every_row()
|
|
{
|
|
var export = AdminUiSource.Read("Components", "Shared", "Raw", "RawCsvExportModal.razor");
|
|
export.ShouldNotContain("data:text/csv");
|
|
export.ShouldContain("RawTagCsvExport.Preview(");
|
|
|
|
var import = AdminUiSource.Read("Components", "Shared", "Raw", "RawCsvImportModal.razor");
|
|
import.ShouldContain("<Virtualize Items=\"@_view.VisibleRows\"");
|
|
import.ShouldNotContain("@foreach (var row in _result.Rows)");
|
|
|
|
AdminUiSource.Read("EndpointRouteBuilderExtensions.cs").ShouldContain("app.MapRawTagCsvExport();");
|
|
}
|
|
}
|