fix(notification-outbox+test): provider-aware InsertIfNotExists for SQLite + supply ApiKeyPepper in IntegrationTests host config (#286)
This commit is contained in:
+33
@@ -61,6 +61,39 @@ public class NotificationOutboxRepository : INotificationOutboxRepository
|
||||
var type = n.Type.ToString();
|
||||
var status = n.Status.ToString();
|
||||
|
||||
// Provider-aware idempotent insert. The production path is SQL Server; the
|
||||
// NotificationOutbox integration tests run over an in-memory SQLite database,
|
||||
// and SQLite does not understand the T-SQL `IF NOT EXISTS … INSERT` form
|
||||
// (it raises 'near "IF": syntax error'). Detect SQLite by provider name so
|
||||
// ConfigurationDatabase needs no Sqlite package reference; the SQL Server
|
||||
// path below stays byte-identical.
|
||||
if (_context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
|
||||
{
|
||||
// SQLite's idempotent insert: INSERT OR IGNORE skips the row when the
|
||||
// NotificationId primary key already exists (no schema prefix in SQLite).
|
||||
// Same parameterised columns/values as the SQL Server branch — the
|
||||
// FormattableString interpolation still parameterises every value
|
||||
// (no concatenation), so this is injection-safe.
|
||||
var sqliteRowsAffected = await _context.Database.ExecuteSqlInterpolatedAsync(
|
||||
$@"INSERT OR IGNORE INTO Notifications
|
||||
(NotificationId, Type, ListName, Subject, Body, TypeData, Status, RetryCount, LastError,
|
||||
ResolvedTargets, SourceSiteId, SourceNode, SourceInstanceId, SourceScript,
|
||||
OriginExecutionId, OriginParentExecutionId,
|
||||
SiteEnqueuedAt, CreatedAt, LastAttemptAt, NextAttemptAt, DeliveredAt)
|
||||
VALUES
|
||||
({n.NotificationId}, {type}, {n.ListName}, {n.Subject}, {n.Body}, {n.TypeData}, {status}, {n.RetryCount}, {n.LastError},
|
||||
{n.ResolvedTargets}, {n.SourceSiteId}, {n.SourceNode}, {n.SourceInstanceId}, {n.SourceScript},
|
||||
{n.OriginExecutionId}, {n.OriginParentExecutionId},
|
||||
{n.SiteEnqueuedAt}, {n.CreatedAt}, {n.LastAttemptAt}, {n.NextAttemptAt}, {n.DeliveredAt});",
|
||||
cancellationToken);
|
||||
|
||||
// rowsAffected == 1 -> we inserted; 0 -> the row was IGNOREd because the
|
||||
// NotificationId already existed. Preserves the true=inserted / false=already-existed
|
||||
// contract. No SqlException catch needed — INSERT OR IGNORE never raises a
|
||||
// unique-constraint violation.
|
||||
return sqliteRowsAffected == 1;
|
||||
}
|
||||
|
||||
// FormattableString interpolation parameterises every value (no concatenation),
|
||||
// so this is safe against injection even for the string columns.
|
||||
try
|
||||
|
||||
@@ -38,6 +38,10 @@ public class ScadaBridgeWebApplicationFactory : WebApplicationFactory<Program>
|
||||
["ScadaBridge__Database__MachineDataDb"] = "Server=localhost;Database=ScadaBridge_MachineData_Test;TrustServerCertificate=True",
|
||||
["ScadaBridge__Database__SkipMigrations"] = "true",
|
||||
["ScadaBridge__Security__JwtSigningKey"] = "integration-test-signing-key-must-be-at-least-32-chars-long",
|
||||
// The inbound API-key pepper is a REQUIRED Central config value (StartupValidator
|
||||
// enforces a >=16-char floor; it backs the peppered-HMAC verifier). Supply a fixed
|
||||
// test pepper so host boot passes validation in the test environment.
|
||||
["ScadaBridge__InboundApi__ApiKeyPepper"] = "integration-test-api-key-pepper-0123456789",
|
||||
// Task 1.4: LDAP settings nest under Security:Ldap (shared LdapOptions) and use
|
||||
// the renamed keys (Transport replaces LdapUseTls; None == plaintext for the
|
||||
// GLAuth dev directory, paired with AllowInsecure=true).
|
||||
|
||||
Reference in New Issue
Block a user