513fa1aa16
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.
131 lines
5.8 KiB
C#
131 lines
5.8 KiB
C#
using Microsoft.Data.Sqlite;
|
|
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Persistence;
|
|
|
|
/// <summary>
|
|
/// Pins <see cref="SiteStorageSchema"/> as the single owner of the site config DDL.
|
|
/// The Host applies this to a LocalDb-managed connection before
|
|
/// <c>RegisterReplicated</c> installs the capture triggers, so the shape it produces
|
|
/// is the shape that replicates.
|
|
/// </summary>
|
|
public class SiteStorageSchemaTests
|
|
{
|
|
/// <summary>Verifies that applying the schema is idempotent and creates every table.</summary>
|
|
[Fact]
|
|
public void Apply_IsIdempotent_AndCreatesEveryTable()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"site-schema-{Guid.NewGuid():N}.db");
|
|
try
|
|
{
|
|
using var connection = new SqliteConnection($"Data Source={path}");
|
|
connection.Open();
|
|
|
|
SiteStorageSchema.Apply(connection);
|
|
SiteStorageSchema.Apply(connection); // second run must not throw
|
|
|
|
// Every table is named explicitly on purpose — a loop asserting "12 tables"
|
|
// would still pass if one were renamed.
|
|
foreach (var table in new[]
|
|
{
|
|
"deployed_configurations", "static_attribute_overrides", "shared_scripts",
|
|
"external_systems", "database_connections", "notification_lists",
|
|
"data_connection_definitions", "smtp_configurations", "native_alarm_state",
|
|
"deployed_secrets", "mirrored_secrets", "secret_mirror_status",
|
|
})
|
|
{
|
|
Assert.True(TableExists(connection, table), $"missing table: {table}");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The migration columns exist to upgrade a database created by an older build in
|
|
/// place. Asserting only against a freshly-created table would pass even if every
|
|
/// <c>ALTER</c> were deleted, because <c>CREATE TABLE</c> already lists them — so
|
|
/// this starts from the pre-migration shape and proves the migration path runs.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Apply_AddsMigrationColumns_ToALegacyDatabase()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"site-schema-legacy-{Guid.NewGuid():N}.db");
|
|
try
|
|
{
|
|
using var connection = new SqliteConnection($"Data Source={path}");
|
|
connection.Open();
|
|
|
|
// The shapes as they existed before backup_configuration /
|
|
// failover_retry_count / metadata_json / timeout_seconds were added.
|
|
using (var legacy = connection.CreateCommand())
|
|
{
|
|
legacy.CommandText = """
|
|
CREATE TABLE data_connection_definitions (
|
|
name TEXT PRIMARY KEY,
|
|
protocol TEXT NOT NULL,
|
|
configuration TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE native_alarm_state (
|
|
instance_unique_name TEXT NOT NULL,
|
|
source_canonical_name TEXT NOT NULL,
|
|
source_reference TEXT NOT NULL,
|
|
condition_json TEXT NOT NULL,
|
|
last_transition_at TEXT NOT NULL,
|
|
PRIMARY KEY (instance_unique_name, source_canonical_name, source_reference)
|
|
);
|
|
CREATE TABLE external_systems (
|
|
name TEXT PRIMARY KEY,
|
|
endpoint_url TEXT NOT NULL,
|
|
auth_type TEXT NOT NULL,
|
|
auth_configuration TEXT,
|
|
method_definitions TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
INSERT INTO external_systems (name, endpoint_url, auth_type, updated_at)
|
|
VALUES ('legacy-system', 'http://example.invalid', 'None', '2026-01-01T00:00:00Z');
|
|
""";
|
|
legacy.ExecuteNonQuery();
|
|
}
|
|
|
|
SiteStorageSchema.Apply(connection);
|
|
|
|
Assert.True(ColumnExists(connection, "data_connection_definitions", "backup_configuration"));
|
|
Assert.True(ColumnExists(connection, "data_connection_definitions", "failover_retry_count"));
|
|
Assert.True(ColumnExists(connection, "native_alarm_state", "metadata_json"));
|
|
Assert.True(ColumnExists(connection, "external_systems", "timeout_seconds"));
|
|
|
|
// The pre-existing row survives the upgrade and reads back the NOT NULL
|
|
// column's default rather than failing.
|
|
using var check = connection.CreateCommand();
|
|
check.CommandText = "SELECT timeout_seconds FROM external_systems WHERE name = 'legacy-system'";
|
|
Assert.Equal(0, Convert.ToInt32(check.ExecuteScalar()));
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
private static bool TableExists(SqliteConnection connection, string table)
|
|
{
|
|
using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = $name";
|
|
cmd.Parameters.AddWithValue("$name", table);
|
|
return Convert.ToInt32(cmd.ExecuteScalar()) > 0;
|
|
}
|
|
|
|
private static bool ColumnExists(SqliteConnection connection, string table, string column)
|
|
{
|
|
using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = $"SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name = $name";
|
|
cmd.Parameters.AddWithValue("$name", column);
|
|
return Convert.ToInt32(cmd.ExecuteScalar()) > 0;
|
|
}
|
|
}
|