136 lines
5.5 KiB
C#
136 lines
5.5 KiB
C#
using Microsoft.Data.SqlClient;
|
|
|
|
namespace ScadaLink.CentralUI.PlaywrightTests.SiteCalls;
|
|
|
|
/// <summary>
|
|
/// Direct-SQL seeding helper for the Site Calls page Playwright E2E tests
|
|
/// (Site Call Audit #22, follow-ups Task 6).
|
|
///
|
|
/// <para>
|
|
/// The Site Calls page reads the central <c>SiteCalls</c> table through the
|
|
/// <c>SiteCallAuditActor</c>, which is a pure read-from-table mirror — so a row
|
|
/// INSERTed directly into <c>SiteCalls</c> surfaces on the page exactly as a
|
|
/// telemetry-ingested row would. Mirrors <see cref="Audit.AuditDataSeeder"/>:
|
|
/// each test inserts its own rows at setup and best-effort deletes them at
|
|
/// teardown, keeping the suite self-contained without touching
|
|
/// <c>infra/mssql/seed-config.sql</c>.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Rows are tagged with a unique <c>Target</c> prefix derived from the test name
|
|
/// + a GUID so the teardown <c>DELETE</c> never touches rows the cluster itself
|
|
/// produced. <c>CreatedAtUtc</c>/<c>UpdatedAtUtc</c> are pinned to "now" so the
|
|
/// page's default (unconstrained) query window sees the row.
|
|
/// </para>
|
|
/// </summary>
|
|
internal static class SiteCallDataSeeder
|
|
{
|
|
private const string DefaultConnectionString =
|
|
"Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true;Encrypt=false;Connect Timeout=5";
|
|
|
|
private const string EnvVar = "SCADALINK_PLAYWRIGHT_DB";
|
|
|
|
/// <summary>
|
|
/// Connection string for the running cluster's configuration DB. Resolved
|
|
/// from <c>SCADALINK_PLAYWRIGHT_DB</c> when set, otherwise the local docker
|
|
/// dev defaults.
|
|
/// </summary>
|
|
public static string ConnectionString
|
|
{
|
|
get
|
|
{
|
|
var fromEnv = Environment.GetEnvironmentVariable(EnvVar);
|
|
return string.IsNullOrWhiteSpace(fromEnv) ? DefaultConnectionString : fromEnv;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a single row into the central <c>SiteCalls</c> table. Optional
|
|
/// fields are nullable so a test can shape the row to the status/channel it
|
|
/// needs for its grid assertions. <c>TrackedOperationId</c> is stored as the
|
|
/// 36-character GUID string the entity mapping expects.
|
|
/// </summary>
|
|
public static async Task InsertSiteCallAsync(
|
|
Guid trackedOperationId,
|
|
string channel,
|
|
string target,
|
|
string sourceSite,
|
|
string status,
|
|
int retryCount,
|
|
DateTime createdAtUtc,
|
|
DateTime updatedAtUtc,
|
|
string? lastError = null,
|
|
int? httpStatus = null,
|
|
DateTime? terminalAtUtc = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
const string sql = @"
|
|
INSERT INTO [SiteCalls]
|
|
([TrackedOperationId], [Channel], [Target], [SourceSite], [Status], [RetryCount],
|
|
[LastError], [HttpStatus], [CreatedAtUtc], [UpdatedAtUtc], [TerminalAtUtc], [IngestedAtUtc])
|
|
VALUES
|
|
(@id, @channel, @target, @sourceSite, @status, @retryCount,
|
|
@lastError, @httpStatus, @createdAtUtc, @updatedAtUtc, @terminalAtUtc, SYSUTCDATETIME());";
|
|
|
|
await using var connection = new SqlConnection(ConnectionString);
|
|
await connection.OpenAsync(ct);
|
|
await using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
cmd.Parameters.AddWithValue("@id", trackedOperationId.ToString());
|
|
cmd.Parameters.AddWithValue("@channel", channel);
|
|
cmd.Parameters.AddWithValue("@target", target);
|
|
cmd.Parameters.AddWithValue("@sourceSite", sourceSite);
|
|
cmd.Parameters.AddWithValue("@status", status);
|
|
cmd.Parameters.AddWithValue("@retryCount", retryCount);
|
|
cmd.Parameters.AddWithValue("@lastError", (object?)lastError ?? DBNull.Value);
|
|
cmd.Parameters.AddWithValue("@httpStatus", (object?)httpStatus ?? DBNull.Value);
|
|
cmd.Parameters.AddWithValue("@createdAtUtc", createdAtUtc);
|
|
cmd.Parameters.AddWithValue("@updatedAtUtc", updatedAtUtc);
|
|
cmd.Parameters.AddWithValue("@terminalAtUtc", (object?)terminalAtUtc ?? DBNull.Value);
|
|
|
|
await cmd.ExecuteNonQueryAsync(ct);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Best-effort cleanup. Deletes every <c>SiteCalls</c> row whose <c>Target</c>
|
|
/// starts with <paramref name="targetPrefix"/>. Swallows all errors — the
|
|
/// prefix carries a per-run GUID so the rows are unique to this test run.
|
|
/// </summary>
|
|
public static async Task DeleteByTargetPrefixAsync(string targetPrefix, CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
await using var connection = new SqlConnection(ConnectionString);
|
|
await connection.OpenAsync(ct);
|
|
await using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = "DELETE FROM [SiteCalls] WHERE [Target] LIKE @prefix";
|
|
cmd.Parameters.AddWithValue("@prefix", targetPrefix + "%");
|
|
await cmd.ExecuteNonQueryAsync(ct);
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort — the prefix carries a GUID so the rows are unique to
|
|
// this test run and won't collide on the next pass.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Probe whether the configuration DB is reachable. Tests gate their per-test
|
|
/// setup on this so a downed cluster surfaces a clear message rather than an
|
|
/// opaque <see cref="SqlException"/>.
|
|
/// </summary>
|
|
public static async Task<bool> IsAvailableAsync(CancellationToken ct = default)
|
|
{
|
|
try
|
|
{
|
|
await using var connection = new SqlConnection(ConnectionString);
|
|
await connection.OpenAsync(ct);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|