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 type = n.Type.ToString();
|
||||||
var status = n.Status.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),
|
// FormattableString interpolation parameterises every value (no concatenation),
|
||||||
// so this is safe against injection even for the string columns.
|
// so this is safe against injection even for the string columns.
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ public class ScadaBridgeWebApplicationFactory : WebApplicationFactory<Program>
|
|||||||
["ScadaBridge__Database__MachineDataDb"] = "Server=localhost;Database=ScadaBridge_MachineData_Test;TrustServerCertificate=True",
|
["ScadaBridge__Database__MachineDataDb"] = "Server=localhost;Database=ScadaBridge_MachineData_Test;TrustServerCertificate=True",
|
||||||
["ScadaBridge__Database__SkipMigrations"] = "true",
|
["ScadaBridge__Database__SkipMigrations"] = "true",
|
||||||
["ScadaBridge__Security__JwtSigningKey"] = "integration-test-signing-key-must-be-at-least-32-chars-long",
|
["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
|
// Task 1.4: LDAP settings nest under Security:Ldap (shared LdapOptions) and use
|
||||||
// the renamed keys (Transport replaces LdapUseTls; None == plaintext for the
|
// the renamed keys (Transport replaces LdapUseTls; None == plaintext for the
|
||||||
// GLAuth dev directory, paired with AllowInsecure=true).
|
// GLAuth dev directory, paired with AllowInsecure=true).
|
||||||
|
|||||||
Reference in New Issue
Block a user