7b0b9c7365
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
86 lines
3.7 KiB
C#
86 lines
3.7 KiB
C#
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
|
|
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests;
|
|
|
|
/// <summary>
|
|
/// Regression guard for ConfigurationDatabase-013: a <see cref="ScadaBridgeDbContext"/>
|
|
/// constructed without an explicit Data Protection provider (the single-argument
|
|
/// constructor) must NOT silently encrypt secret columns with a throwaway ephemeral
|
|
/// key — that would persist ciphertext that becomes permanently undecryptable on the
|
|
/// next process restart, with no error. Writing a secret column on such a context
|
|
/// must fail fast with a clear <see cref="InvalidOperationException"/> instead.
|
|
/// </summary>
|
|
public class EphemeralEncryptionFallbackTests
|
|
{
|
|
private static DbContextOptions<ScadaBridgeDbContext> SqliteOptions() =>
|
|
new DbContextOptionsBuilder<ScadaBridgeDbContext>()
|
|
.UseSqlite("DataSource=:memory:")
|
|
.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))
|
|
.Options;
|
|
|
|
[Fact]
|
|
public async Task SingleArgConstructor_WritingSecretColumn_FailsFast_DoesNotPersistThrowawayCiphertext()
|
|
{
|
|
// Single-argument constructor: no Data Protection provider supplied (the
|
|
// design-time / schema-only path). Schema creation must still succeed.
|
|
using var context = new ScadaBridgeDbContext(SqliteOptions());
|
|
context.Database.OpenConnection();
|
|
context.Database.EnsureCreated();
|
|
|
|
// AuthConfiguration is an encrypted secret column. Persisting it without a real
|
|
// key ring would produce undecryptable ciphertext; it must throw instead.
|
|
var ext = new ExternalSystemDefinition("Erp", "https://erp.example.com", "ApiKey")
|
|
{
|
|
AuthConfiguration = "{\"apiKey\":\"live-secret\"}"
|
|
};
|
|
context.ExternalSystemDefinitions.Add(ext);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => context.SaveChangesAsync());
|
|
Assert.Contains("Data Protection", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SingleArgConstructor_WritingNonSecretColumn_Succeeds()
|
|
{
|
|
// The schema-only / no-provider context must remain fully usable for entities
|
|
// that have no encrypted secret columns — only secret writes are gated.
|
|
using var context = new ScadaBridgeDbContext(SqliteOptions());
|
|
context.Database.OpenConnection();
|
|
context.Database.EnsureCreated();
|
|
|
|
var ext = new ExternalSystemDefinition("Erp", "https://erp.example.com", "None");
|
|
context.ExternalSystemDefinitions.Add(ext);
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
Assert.True(ext.Id > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProviderConstructor_WritingSecretColumn_StillSucceeds()
|
|
{
|
|
// Sanity check: the gating must not regress the supported runtime path where a
|
|
// real Data Protection provider is supplied.
|
|
using var context = new ScadaBridgeDbContext(SqliteOptions(), new EphemeralDataProtectionProvider());
|
|
context.Database.OpenConnection();
|
|
context.Database.EnsureCreated();
|
|
|
|
var ext = new ExternalSystemDefinition("Erp", "https://erp.example.com", "ApiKey")
|
|
{
|
|
AuthConfiguration = "{\"apiKey\":\"live-secret\"}"
|
|
};
|
|
context.ExternalSystemDefinitions.Add(ext);
|
|
|
|
await context.SaveChangesAsync();
|
|
context.ChangeTracker.Clear();
|
|
|
|
var loaded = await context.ExternalSystemDefinitions.SingleAsync(e => e.Id == ext.Id);
|
|
Assert.Equal("{\"apiKey\":\"live-secret\"}", loaded.AuthConfiguration);
|
|
}
|
|
}
|