feat(sms): complete SmsConfig bundle export/import wiring + GetSmsConfigurationByIdAsync (S10b)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -52,6 +53,16 @@ public sealed class BundleExporterTests : IDisposable
|
||||
services.AddDbContext<ScadaBridgeDbContext>(opts =>
|
||||
opts.UseInMemoryDatabase(dbName));
|
||||
|
||||
// S10b: secret-bearing columns (SmsConfiguration.AuthToken, SmtpConfiguration
|
||||
// .Credentials, …) require the context's encrypting two-arg ctor with a Data
|
||||
// Protection key ring. Register an ephemeral provider and override the
|
||||
// AddDbContext registration to construct the encrypting context — mirrors the
|
||||
// production AddConfigurationDatabase wiring (and BundleImporterRollbackFailureTests).
|
||||
services.AddSingleton<IDataProtectionProvider>(new EphemeralDataProtectionProvider());
|
||||
services.AddScoped(sp => new ScadaBridgeDbContext(
|
||||
sp.GetRequiredService<DbContextOptions<ScadaBridgeDbContext>>(),
|
||||
sp.GetRequiredService<IDataProtectionProvider>()));
|
||||
|
||||
// Repositories the resolver pulls from. M8 (B4): the resolver now injects
|
||||
// ISiteRepository to walk the site/data-connection/instance closure, so it
|
||||
// must be registered or the BuildServiceProvider-time graph resolution for
|
||||
@@ -201,6 +212,78 @@ public sealed class BundleExporterTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportAsync_emits_selected_sms_config_with_secret_and_summary_count()
|
||||
{
|
||||
// Arrange: seed one SMS provider config with a secret auth token.
|
||||
int smsId;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
var sms = new SmsConfiguration("AC_export_sid", "+15557654321")
|
||||
{
|
||||
AuthToken = "super-secret-token",
|
||||
MessagingServiceSid = "MG_svc",
|
||||
ApiBaseUrl = "https://api.twilio.example",
|
||||
};
|
||||
ctx.Set<SmsConfiguration>().Add(sms);
|
||||
await ctx.SaveChangesAsync();
|
||||
smsId = sms.Id;
|
||||
}
|
||||
|
||||
// Act: export selecting only the SMS config.
|
||||
Stream bundleStream;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var exporter = scope.ServiceProvider.GetRequiredService<IBundleExporter>();
|
||||
var selection = new ExportSelection(
|
||||
TemplateIds: Array.Empty<int>(),
|
||||
SharedScriptIds: Array.Empty<int>(),
|
||||
ExternalSystemIds: Array.Empty<int>(),
|
||||
DatabaseConnectionIds: Array.Empty<int>(),
|
||||
NotificationListIds: Array.Empty<int>(),
|
||||
SmtpConfigurationIds: Array.Empty<int>(),
|
||||
ApiMethodIds: Array.Empty<int>(),
|
||||
IncludeDependencies: false,
|
||||
SmsConfigurationIds: new[] { smsId });
|
||||
|
||||
bundleStream = await exporter.ExportAsync(
|
||||
selection, user: "alice", sourceEnvironment: "dev",
|
||||
passphrase: null, cancellationToken: CancellationToken.None);
|
||||
}
|
||||
|
||||
byte[] bundleBytes;
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
await bundleStream.CopyToAsync(ms);
|
||||
bundleBytes = ms.ToArray();
|
||||
}
|
||||
|
||||
// Assert: manifest summary counts the SMS config; content carries it with
|
||||
// the auth token preserved inside the SecretsBlock.
|
||||
var serializer = _provider.GetRequiredService<BundleSerializer>();
|
||||
BundleManifest manifest;
|
||||
using (var ms = new MemoryStream(bundleBytes, writable: false))
|
||||
{
|
||||
manifest = serializer.ReadManifest(ms);
|
||||
}
|
||||
Assert.Equal(1, manifest.Summary.SmsConfigs);
|
||||
|
||||
byte[] rawContent;
|
||||
using (var ms = new MemoryStream(bundleBytes, writable: false))
|
||||
{
|
||||
rawContent = serializer.ReadContentBytes(ms, manifest);
|
||||
}
|
||||
var content = serializer.UnpackContent(rawContent, manifest, passphrase: null, encryptor: null);
|
||||
Assert.Single(content.SmsConfigs);
|
||||
var dto = content.SmsConfigs[0];
|
||||
Assert.Equal("AC_export_sid", dto.AccountSid);
|
||||
Assert.Equal("+15557654321", dto.FromNumber);
|
||||
Assert.NotNull(dto.Secrets);
|
||||
Assert.True(dto.Secrets!.Values.TryGetValue("AuthToken", out var token));
|
||||
Assert.Equal("super-secret-token", token);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportAsync_with_passphrase_produces_encrypted_bundle()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user