docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
+24
-6
@@ -23,7 +23,7 @@ using TransportExportPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Des
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages.Design;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit + logic tests for the TransportExport wizard (Component #24, Task T21).
|
||||
/// bUnit + logic tests for the TransportExport wizard (Transport component).
|
||||
///
|
||||
/// <para>
|
||||
/// Covers the four contract points the design plan calls out:
|
||||
@@ -54,6 +54,7 @@ public class TransportExportPageTests : BunitContext
|
||||
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
|
||||
private readonly IBundleExporter _exporter = Substitute.For<IBundleExporter>();
|
||||
|
||||
/// <summary>Initializes a new instance of <c>TransportExportPageTests</c>.</summary>
|
||||
public TransportExportPageTests()
|
||||
{
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
@@ -76,7 +77,7 @@ public class TransportExportPageTests : BunitContext
|
||||
.Returns(Task.FromResult<IReadOnlyList<SmtpConfiguration>>(new List<SmtpConfiguration>()));
|
||||
_inboundApiRepo.GetAllApiMethodsAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<ApiMethod>>(new List<ApiMethod>()));
|
||||
// Empty site/instance defaults — the M8 site/instance picker calls these on load
|
||||
// Empty site/instance defaults — the site/instance picker calls these on load
|
||||
// and the DependencyResolver fans out over sites on resolve.
|
||||
_siteRepo.GetAllSitesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<Site>>(new List<Site>()));
|
||||
@@ -92,7 +93,7 @@ public class TransportExportPageTests : BunitContext
|
||||
Services.AddSingleton(_siteRepo);
|
||||
Services.AddSingleton(_exporter);
|
||||
// DependencyResolver is sealed but its dependencies are the repositories above
|
||||
// (template/external/notification/inbound + ISiteRepository for M8 site/instance
|
||||
// (template/external/notification/inbound + ISiteRepository for site/instance
|
||||
// export) — registering the concrete type is enough.
|
||||
Services.AddSingleton<DependencyResolver>();
|
||||
Services.AddSingleton<IOptions<TransportOptions>>(
|
||||
@@ -117,6 +118,7 @@ public class TransportExportPageTests : BunitContext
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 1: Step 1 renders the template tree and every flat artifact group.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that renders step 1 with template tree and artifact checkboxes.</summary>
|
||||
[Fact]
|
||||
public void Renders_step1_with_template_tree_and_artifact_checkboxes()
|
||||
{
|
||||
@@ -201,6 +203,8 @@ public class TransportExportPageTests : BunitContext
|
||||
// Test 2: Step 2 shows resolved dependencies — auto-included templates pulled
|
||||
// in because a seed template composes them.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that step 2 shows resolved dependencies after clicking next.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Step2_shows_resolved_dependencies_after_clicking_next()
|
||||
{
|
||||
@@ -252,6 +256,8 @@ public class TransportExportPageTests : BunitContext
|
||||
// Test 3: Walks the wizard end-to-end and verifies BundleExporter.ExportAsync
|
||||
// is invoked with the user-selected ids and the authenticated identity.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that step 4 triggers export async with selected artifacts and user identity.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Step4_triggers_ExportAsync_with_selected_artifacts_and_user_identity()
|
||||
{
|
||||
@@ -318,6 +324,8 @@ public class TransportExportPageTests : BunitContext
|
||||
// doesn't model routing, so we verify the policy itself denies the
|
||||
// principal (the same gate the router consults).
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that page returns unauthorized for user without design role.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Page_returns_unauthorized_for_user_without_Design_role()
|
||||
{
|
||||
@@ -336,9 +344,10 @@ public class TransportExportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 5 (M8 E1): Step 1 renders the Sites & Instances group, listing each
|
||||
// Test 5: Step 1 renders the Sites & Instances group, listing each
|
||||
// site and (when expanded) its instances.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that renders step 1 sites group with sites and instances.</summary>
|
||||
[Fact]
|
||||
public void Renders_step1_sites_group_with_sites_and_instances()
|
||||
{
|
||||
@@ -372,10 +381,12 @@ public class TransportExportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 6 (M8 E1): Selecting a site flows its id into ExportSelection.SiteIds;
|
||||
// Test 6: Selecting a site flows its id into ExportSelection.SiteIds;
|
||||
// selecting an individual instance flows its id into InstanceIds. Verified at
|
||||
// the resolver/exporter boundary (the same contract DependencyResolver reads).
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that step 4 export carries selected site and instance ids.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Step4_export_carries_selected_site_and_instance_ids()
|
||||
{
|
||||
@@ -441,6 +452,8 @@ public class TransportExportPageTests : BunitContext
|
||||
// AccountSid (never AuthToken), and ticking one flows its entity id into
|
||||
// ExportSelection.SmsConfigurationIds — mirroring the SMTP-by-Host contract.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that step 4 export carries selected sms configuration ids.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Step4_export_carries_selected_sms_configuration_ids()
|
||||
{
|
||||
@@ -494,6 +507,7 @@ public class TransportExportPageTests : BunitContext
|
||||
// Test 9 (S10c): CountSecrets counts a populated SMS AuthToken, mirroring
|
||||
// the SMTP Credentials contribution to the Step 3 secrets banner.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that count secrets includes sms configuration auth token.</summary>
|
||||
[Fact]
|
||||
public void CountSecrets_includes_sms_configuration_auth_token()
|
||||
{
|
||||
@@ -523,6 +537,7 @@ public class TransportExportPageTests : BunitContext
|
||||
// Static helpers — exercised directly so the file-naming + secret-count
|
||||
// contract is unit-pinned independently of the rendering surface.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that build filename produces pattern and sanitises source environment.</summary>
|
||||
[Fact]
|
||||
public void BuildFilename_produces_pattern_and_sanitises_source_environment()
|
||||
{
|
||||
@@ -532,9 +547,10 @@ public class TransportExportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 7 (M8 C2-SECRET-1): CountSecrets includes site data-connection
|
||||
// Test 7: CountSecrets includes site data-connection
|
||||
// PrimaryConfiguration and BackupConfiguration fields in the banner total.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that count secrets includes data connection primary and backup configurations.</summary>
|
||||
[Fact]
|
||||
public void CountSecrets_includes_data_connection_primary_and_backup_configurations()
|
||||
{
|
||||
@@ -560,6 +576,7 @@ public class TransportExportPageTests : BunitContext
|
||||
Assert.Equal(2, TransportExportPage.CountSecrets(resolved));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that count secrets counts only non empty data connection configurations.</summary>
|
||||
[Fact]
|
||||
public void CountSecrets_counts_only_non_empty_data_connection_configurations()
|
||||
{
|
||||
@@ -589,6 +606,7 @@ public class TransportExportPageTests : BunitContext
|
||||
Assert.Equal(1, TransportExportPage.CountSecrets(resolved));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that count secrets returns zero when no data connections.</summary>
|
||||
[Fact]
|
||||
public void CountSecrets_returns_zero_when_no_data_connections()
|
||||
{
|
||||
|
||||
+54
-10
@@ -24,7 +24,7 @@ using TransportImportPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Des
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages.Design;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit + logic tests for the TransportImport wizard (Component #24, Task T22).
|
||||
/// bUnit + logic tests for the TransportImport wizard.
|
||||
///
|
||||
/// <para>
|
||||
/// The wizard has five steps (Upload / Passphrase / Diff / Confirm / Result).
|
||||
@@ -41,7 +41,7 @@ public class TransportImportPageTests : BunitContext
|
||||
{
|
||||
private readonly IBundleImporter _importer = Substitute.For<IBundleImporter>();
|
||||
private readonly IAuditService _auditService = Substitute.For<IAuditService>();
|
||||
// M8 E2: the Map step injects ISiteRepository to populate target-site +
|
||||
// The Map step injects ISiteRepository to populate target-site +
|
||||
// target-connection dropdowns. Register a substitute so every test (not just
|
||||
// the Map tests) can render the wizard without a missing-service failure.
|
||||
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
|
||||
@@ -49,6 +49,7 @@ public class TransportImportPageTests : BunitContext
|
||||
/// <summary>Client address the wizard reports for the per-IP unlock throttle (register row 44).</summary>
|
||||
private const string TestClientIp = "203.0.113.7";
|
||||
|
||||
/// <summary>Initializes the test context with stubbed importer, audit, repository and authentication services.</summary>
|
||||
public TransportImportPageTests()
|
||||
{
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
@@ -124,6 +125,7 @@ public class TransportImportPageTests : BunitContext
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 1: Step 1 renders the InputFile upload control.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that the wizard renders the step 1 upload input.</summary>
|
||||
[Fact]
|
||||
public void Renders_step1_upload_input()
|
||||
{
|
||||
@@ -138,6 +140,8 @@ public class TransportImportPageTests : BunitContext
|
||||
// Test 2: Wrong passphrase increments the failure counter without
|
||||
// advancing past Step 2.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that a decryption failure increments the passphrase attempt counter.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Decryption_failure_increments_attempt_counter()
|
||||
{
|
||||
@@ -169,6 +173,8 @@ public class TransportImportPageTests : BunitContext
|
||||
// Test 3: After MaxUnlockAttemptsPerSession failures the wizard returns
|
||||
// to Step 1 with an explanatory error.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that three failed unlock attempts force the user to upload the bundle again.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Three_failed_unlocks_force_reupload()
|
||||
{
|
||||
@@ -202,6 +208,8 @@ public class TransportImportPageTests : BunitContext
|
||||
// Test 4: Confirm step requires an exact match (case-sensitive) on the
|
||||
// source environment name before Apply is enabled.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that the confirm step requires an exact environment name match.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Confirm_step_requires_exact_environment_name_match()
|
||||
{
|
||||
@@ -250,6 +258,8 @@ public class TransportImportPageTests : BunitContext
|
||||
// Test 5: ApplyAsync is invoked with the chosen resolutions and the
|
||||
// authenticated user identity.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that the apply step calls the bundle importer's <c>ApplyAsync</c> with the chosen resolutions.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Apply_step_invokes_BundleImporter_ApplyAsync_with_resolutions()
|
||||
{
|
||||
@@ -314,6 +324,8 @@ public class TransportImportPageTests : BunitContext
|
||||
// doesn't model routing, so we verify the policy itself denies the
|
||||
// principal.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that the page is unauthorized for a user without the Admin role.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Page_returns_unauthorized_for_user_without_Admin_role()
|
||||
{
|
||||
@@ -335,6 +347,7 @@ public class TransportImportPageTests : BunitContext
|
||||
// Test 7 (helper coverage): BuildDefaultResolutions maps each kind to the
|
||||
// expected default ResolutionAction.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that default resolutions map each conflict kind to the expected action.</summary>
|
||||
[Fact]
|
||||
public void BuildDefaultResolutions_maps_kinds_to_actions()
|
||||
{
|
||||
@@ -355,10 +368,12 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 8 (M8 E2): the Map sub-section renders one row per required site /
|
||||
// Test 8: the Map sub-section renders one row per required site /
|
||||
// connection mapping with the auto-match defaults pre-selected, and the
|
||||
// section is hidden entirely when the preview carries no required mappings.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that the map section renders the required rows with auto-match defaults selected.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Map_section_renders_required_rows_with_automatch_defaults()
|
||||
{
|
||||
@@ -398,6 +413,8 @@ public class TransportImportPageTests : BunitContext
|
||||
Assert.Equal("opc-main", selectedConnOpt.GetAttribute("value"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the map section is hidden when no mappings are required.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Map_section_hidden_when_no_required_mappings()
|
||||
{
|
||||
@@ -418,10 +435,12 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 9 (M8 E2): the operator's Map choices fold into the BundleNameMap
|
||||
// Test 9: the operator's Map choices fold into the BundleNameMap
|
||||
// passed to ApplyAsync — a chosen target → MapToExisting, "Create new" →
|
||||
// CreateNew. Captured via the substituted IBundleImporter.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that apply passes a <c>BundleNameMap</c> built from the operator's map choices.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Apply_passes_BundleNameMap_built_from_map_choices()
|
||||
{
|
||||
@@ -479,9 +498,11 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 10 (M8 E2): a Modified row whose FieldDiffJson carries a code
|
||||
// Test 10: a Modified row whose FieldDiffJson carries a code
|
||||
// lineDiff renders +/- lines via LineDiffView.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that a modified row with a code line diff renders added and removed lines.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Modified_row_with_code_lineDiff_renders_add_and_remove_lines()
|
||||
{
|
||||
@@ -533,8 +554,10 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 11 (M8 E2): truncation marker shows when the lineDiff is truncated.
|
||||
// Test 11: truncation marker shows when the lineDiff is truncated.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that a modified row with a truncated line diff shows the truncation marker.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Modified_row_with_truncated_lineDiff_shows_truncation_marker()
|
||||
{
|
||||
@@ -579,11 +602,13 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 12 (M8 E2 Fix 1): changing a source site's dropdown to a target
|
||||
// Test 12: changing a source site's dropdown to a target
|
||||
// that lacks the previously auto-matched connection name resets that
|
||||
// connection choice to CreateNew (""), and BuildNameMap therefore emits
|
||||
// a CreateNew entry — not a MapToExisting for an absent connection.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that changing a site choice resets connection choices the new target site lacks.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task OnSiteChoiceChanged_resets_stale_connection_choices_when_new_target_lacks_connection()
|
||||
{
|
||||
@@ -639,9 +664,11 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 13 (M8 E2 Fix 2): BackToUpload clears session/preview/Map state
|
||||
// Test 13: BackToUpload clears session/preview/Map state
|
||||
// so a subsequent re-upload flow cannot inherit stale data.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
/// <summary>Verifies that going back to upload clears the session, preview and map state.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task BackToUpload_clears_session_and_preview_state()
|
||||
{
|
||||
@@ -715,12 +742,17 @@ public class TransportImportPageTests : BunitContext
|
||||
/// </summary>
|
||||
private sealed class StoreBackedImporter : IBundleImporter
|
||||
{
|
||||
/// <summary>Gets the backing bundle session store.</summary>
|
||||
public BundleSessionStore Store { get; } = new(
|
||||
Microsoft.Extensions.Options.Options.Create(new TransportOptions()),
|
||||
TimeProvider.System);
|
||||
|
||||
/// <summary>Gets the number of times <c>Abandon</c> has been called.</summary>
|
||||
public int AbandonCalls { get; private set; }
|
||||
|
||||
/// <summary>Seeds an encrypted session directly into the store.</summary>
|
||||
/// <param name="sourceEnv">The source environment name recorded on the session manifest.</param>
|
||||
/// <returns>The opened session.</returns>
|
||||
public BundleSession OpenSession(string sourceEnv = "prod-cluster")
|
||||
{
|
||||
var seed = BuildEncryptedSession(sourceEnv);
|
||||
@@ -735,13 +767,16 @@ public class TransportImportPageTests : BunitContext
|
||||
return Store.Open(session);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<BundleSession> LoadAsync(
|
||||
Stream bundleStream, string? passphrase, CancellationToken ct = default, string? clientKey = null)
|
||||
=> throw new NotSupportedException("These tests seed sessions via OpenSession().");
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ImportPreview> PreviewAsync(Guid sessionId, CancellationToken ct = default)
|
||||
=> Task.FromResult(new ImportPreview(sessionId, new List<ImportPreviewItem>()));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ImportResult> ApplyAsync(
|
||||
Guid sessionId,
|
||||
IReadOnlyList<ImportResolution> resolutions,
|
||||
@@ -760,6 +795,7 @@ public class TransportImportPageTests : BunitContext
|
||||
AuditEventCorrelation: Guid.NewGuid().ToString()));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Abandon(Guid sessionId)
|
||||
{
|
||||
AbandonCalls++;
|
||||
@@ -777,6 +813,8 @@ public class TransportImportPageTests : BunitContext
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disposing the wizard releases its bundle session.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Disposing_the_wizard_releases_its_bundle_session()
|
||||
{
|
||||
@@ -798,6 +836,8 @@ public class TransportImportPageTests : BunitContext
|
||||
Assert.Null(GetField<object?>(cut.Instance, "_session"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that going back to upload releases the bundle session.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task BackToUpload_releases_the_bundle_session()
|
||||
{
|
||||
@@ -827,6 +867,8 @@ public class TransportImportPageTests : BunitContext
|
||||
Assert.All(content, b => Assert.Equal(0, b));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that applying then disposing releases the session exactly once without throwing.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Apply_then_dispose_releases_the_session_once_without_throwing()
|
||||
{
|
||||
@@ -862,6 +904,8 @@ public class TransportImportPageTests : BunitContext
|
||||
Assert.Null(importer.Store.Get(session.SessionId));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disposing one wizard leaves a concurrent circuit's session open.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Disposing_one_wizard_leaves_a_concurrent_circuits_session_open()
|
||||
{
|
||||
@@ -942,8 +986,8 @@ public class TransportImportPageTests : BunitContext
|
||||
/// <summary>
|
||||
/// Seeds the wizard at Step 2 (Passphrase) with a staged bundle file — the
|
||||
/// shape after an encrypted-bundle upload completed Step 1's peek and
|
||||
/// surfaced an ArgumentException ("passphrase required"). CentralUI-031:
|
||||
/// the wizard now stages the upload to a temp file and only retains the
|
||||
/// surfaced an ArgumentException ("passphrase required"). The
|
||||
/// wizard now stages the upload to a temp file and only retains the
|
||||
/// path on the component, so the test helper writes the bytes to a per-
|
||||
/// test temp file and sets the path field instead of the byte[] field.
|
||||
/// </summary>
|
||||
|
||||
+13
-2
@@ -22,7 +22,7 @@ using TransportImportPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Des
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages.Design;
|
||||
|
||||
/// <summary>
|
||||
/// Regression tests for register row 103 (the P6-6 lesser instance in
|
||||
/// Regression tests for register row 103 (the lesser instance in
|
||||
/// <c>TransportImport</c>). <c>TryLoadAsync</c> awaits
|
||||
/// <see cref="IBundleImporter.LoadAsync"/>, then used to publish the result into
|
||||
/// <c>_session</c> unconditionally. If the circuit was torn down inside that await
|
||||
@@ -31,7 +31,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages.Design;
|
||||
/// <c>BundleSession</c> stayed registered, stranding one of
|
||||
/// <c>TransportOptions.MaxConcurrentImportSessions</c> (register row 43) for the full
|
||||
/// session TTL. The fix publishes the session and re-checks disposal under one lock
|
||||
/// (<c>_sessionLock</c>), mirroring <c>DebugView</c>'s P6-6 fix, so exactly one side
|
||||
/// (<c>_sessionLock</c>), mirroring <c>DebugView</c>'s fix, so exactly one side
|
||||
/// (this method or <c>Dispose</c>) abandons it.
|
||||
/// </summary>
|
||||
public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
@@ -48,6 +48,7 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
private readonly TaskCompletionSource _entered = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly List<Guid> _abandoned = new();
|
||||
|
||||
/// <summary>Gets the session store used by the test.</summary>
|
||||
public BundleSessionStore Store { get; } = new(
|
||||
Options.Create(new TransportOptions()), TimeProvider.System);
|
||||
|
||||
@@ -60,8 +61,10 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
get { lock (_abandoned) { return _abandoned.ToList(); } }
|
||||
}
|
||||
|
||||
/// <summary>Releases the load that is blocked waiting for the test to let it finish.</summary>
|
||||
public void ReleaseLoad() => _gate.TrySetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<BundleSession> LoadAsync(
|
||||
Stream bundleStream, string? passphrase, CancellationToken ct = default, string? clientKey = null)
|
||||
{
|
||||
@@ -89,9 +92,11 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
return Store.Open(session);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ImportPreview> PreviewAsync(Guid sessionId, CancellationToken ct = default)
|
||||
=> throw new NotSupportedException("Not exercised by these tests.");
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ImportResult> ApplyAsync(
|
||||
Guid sessionId,
|
||||
IReadOnlyList<ImportResolution> resolutions,
|
||||
@@ -100,6 +105,7 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
BundleNameMap? nameMap = null)
|
||||
=> throw new NotSupportedException("Not exercised by these tests.");
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Abandon(Guid sessionId)
|
||||
{
|
||||
lock (_abandoned) { _abandoned.Add(sessionId); }
|
||||
@@ -116,6 +122,7 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
|
||||
private const string TestClientIp = "203.0.113.7";
|
||||
|
||||
/// <summary>Initializes the test class and its fake session service.</summary>
|
||||
public TransportImportSessionDisposeRaceTests()
|
||||
{
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
@@ -181,6 +188,8 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
return task!;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a component disposed mid-load abandons the session that was just opened.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TryLoadAsync_DisposedMidLoad_AbandonsTheJustOpenedSession()
|
||||
{
|
||||
@@ -203,6 +212,8 @@ public class TransportImportSessionDisposeRaceTests : BunitContext
|
||||
Assert.Null(_importer.Store.Get(_importer.Abandoned[0]));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that loading and then disposing abandons the session exactly once.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TryLoadAsync_ThenDispose_AbandonsTheSessionExactlyOnce()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user