fix(notification-outbox+test): provider-aware InsertIfNotExists for SQLite + supply ApiKeyPepper in IntegrationTests host config (#286)

This commit is contained in:
Joseph Doherty
2026-06-19 01:03:48 -04:00
parent 649e45b5c0
commit 6a4c9a85b8
2 changed files with 37 additions and 0 deletions
@@ -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