refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
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.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
|
||||
using ZB.MOM.WW.ScadaBridge.Transport.Encryption;
|
||||
using ZB.MOM.WW.ScadaBridge.Transport.Serialization;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Serialization;
|
||||
|
||||
public sealed class BundleSerializerTests
|
||||
{
|
||||
// Commons-015 sets the documented PBKDF2 floor to 100_000 (OWASP minimum).
|
||||
// Using the floor keeps the suite fast while passing EncryptionMetadata
|
||||
// validation.
|
||||
private const int TestIterations = EncryptionMetadata.MinPbkdf2Iterations;
|
||||
|
||||
private static BundleContentDto SampleContent() => new(
|
||||
TemplateFolders: new[] { new TemplateFolderDto("Root", ParentName: null, SortOrder: 0) },
|
||||
Templates: Array.Empty<TemplateDto>(),
|
||||
SharedScripts: new[] { new SharedScriptDto("util", "return 42;", ParameterDefinitions: null, ReturnDefinition: null) },
|
||||
ExternalSystems: Array.Empty<ExternalSystemDto>(),
|
||||
DatabaseConnections: Array.Empty<DatabaseConnectionDto>(),
|
||||
NotificationLists: Array.Empty<NotificationListDto>(),
|
||||
SmtpConfigs: Array.Empty<SmtpConfigDto>(),
|
||||
ApiKeys: Array.Empty<ApiKeyDto>(),
|
||||
ApiMethods: Array.Empty<ApiMethodDto>());
|
||||
|
||||
private static BundleManifest BuildManifestFor(byte[] contentBytes, EncryptionMetadata? encryption = null) =>
|
||||
new ManifestBuilder().Build(
|
||||
sourceEnvironment: "test-env",
|
||||
exportedBy: "tester",
|
||||
scadaLinkVersion: "1.0.0",
|
||||
encryption: encryption,
|
||||
summary: new BundleSummary(0, 1, 1, 0, 0, 0, 0, 0, 0),
|
||||
contents: Array.Empty<ManifestContentEntry>(),
|
||||
contentBytes: contentBytes);
|
||||
|
||||
[Fact]
|
||||
public void Pack_emits_manifest_and_content_json_when_no_passphrase()
|
||||
{
|
||||
var sut = new BundleSerializer();
|
||||
var content = SampleContent();
|
||||
var contentJson = sut.SerializeContentBytes(content);
|
||||
var manifest = BuildManifestFor(contentJson);
|
||||
|
||||
using var stream = sut.Pack(content, manifest, passphrase: null, encryptor: null);
|
||||
|
||||
using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
|
||||
Assert.NotNull(archive.GetEntry("manifest.json"));
|
||||
Assert.NotNull(archive.GetEntry("content.json"));
|
||||
Assert.Null(archive.GetEntry("content.enc"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pack_emits_manifest_and_content_enc_when_passphrase_supplied()
|
||||
{
|
||||
var sut = new BundleSerializer();
|
||||
var encryptor = new BundleSecretEncryptor();
|
||||
var content = SampleContent();
|
||||
var contentJson = sut.SerializeContentBytes(content);
|
||||
var (cipher, meta) = encryptor.Encrypt(contentJson, "pass", TestIterations);
|
||||
var manifest = BuildManifestFor(cipher, encryption: meta);
|
||||
|
||||
using var stream = sut.Pack(content, manifest, passphrase: "pass", encryptor: encryptor);
|
||||
|
||||
using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
|
||||
Assert.NotNull(archive.GetEntry("manifest.json"));
|
||||
Assert.Null(archive.GetEntry("content.json"));
|
||||
Assert.NotNull(archive.GetEntry("content.enc"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roundtrip_through_temp_stream_recovers_identical_content()
|
||||
{
|
||||
var sut = new BundleSerializer();
|
||||
var content = SampleContent();
|
||||
var contentJson = sut.SerializeContentBytes(content);
|
||||
var manifest = BuildManifestFor(contentJson);
|
||||
|
||||
using var stream = sut.Pack(content, manifest, passphrase: null, encryptor: null);
|
||||
|
||||
stream.Position = 0;
|
||||
var readManifest = sut.ReadManifest(stream);
|
||||
stream.Position = 0;
|
||||
var contentBytes = sut.ReadContentBytes(stream, readManifest);
|
||||
var unpacked = sut.UnpackContent(contentBytes, readManifest, passphrase: null, encryptor: null);
|
||||
|
||||
Assert.Equal(manifest.ContentHash, readManifest.ContentHash);
|
||||
Assert.Single(unpacked.TemplateFolders);
|
||||
Assert.Equal("Root", unpacked.TemplateFolders[0].Name);
|
||||
var shared = Assert.Single(unpacked.SharedScripts);
|
||||
Assert.Equal("util", shared.Name);
|
||||
Assert.Equal("return 42;", shared.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadManifest_throws_when_zip_missing_manifest_json()
|
||||
{
|
||||
var sut = new BundleSerializer();
|
||||
using var ms = new MemoryStream();
|
||||
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
|
||||
{
|
||||
var entry = archive.CreateEntry("garbage.txt");
|
||||
using var es = entry.Open();
|
||||
es.Write(new byte[] { 1, 2, 3 });
|
||||
}
|
||||
ms.Position = 0;
|
||||
|
||||
Assert.Throws<InvalidDataException>(() => sut.ReadManifest(ms));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnpackContent_throws_CryptographicException_on_wrong_passphrase()
|
||||
{
|
||||
var sut = new BundleSerializer();
|
||||
var encryptor = new BundleSecretEncryptor();
|
||||
var content = SampleContent();
|
||||
var contentJson = sut.SerializeContentBytes(content);
|
||||
var (cipher, meta) = encryptor.Encrypt(contentJson, "right", TestIterations);
|
||||
var manifest = BuildManifestFor(cipher, encryption: meta);
|
||||
|
||||
using var stream = sut.Pack(content, manifest, passphrase: "right", encryptor: encryptor);
|
||||
stream.Position = 0;
|
||||
var readManifest = sut.ReadManifest(stream);
|
||||
stream.Position = 0;
|
||||
var contentBytes = sut.ReadContentBytes(stream, readManifest);
|
||||
|
||||
Assert.ThrowsAny<CryptographicException>(() => sut.UnpackContent(contentBytes, readManifest, "wrong", encryptor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.Transport.Serialization;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Serialization;
|
||||
|
||||
public sealed class EntitySerializerTests
|
||||
{
|
||||
private static EntityAggregate MakeEmptyAggregate() => new(
|
||||
TemplateFolders: Array.Empty<TemplateFolder>(),
|
||||
Templates: Array.Empty<Template>(),
|
||||
SharedScripts: Array.Empty<SharedScript>(),
|
||||
ExternalSystems: Array.Empty<ExternalSystemDefinition>(),
|
||||
ExternalSystemMethods: Array.Empty<ExternalSystemMethod>(),
|
||||
DatabaseConnections: Array.Empty<DatabaseConnectionDefinition>(),
|
||||
NotificationLists: Array.Empty<NotificationList>(),
|
||||
SmtpConfigurations: Array.Empty<SmtpConfiguration>(),
|
||||
ApiKeys: Array.Empty<ApiKey>(),
|
||||
ApiMethods: Array.Empty<ApiMethod>());
|
||||
|
||||
[Fact]
|
||||
public void ToDto_carves_external_system_credentials_into_secrets_block()
|
||||
{
|
||||
var sys = new ExternalSystemDefinition("erp", "https://erp/api", "ApiKey")
|
||||
{
|
||||
Id = 1,
|
||||
AuthConfiguration = "{\"apiKey\":\"super-secret\"}",
|
||||
};
|
||||
var aggregate = MakeEmptyAggregate() with { ExternalSystems = new[] { sys } };
|
||||
|
||||
var sut = new EntitySerializer();
|
||||
var dto = sut.ToBundleContent(aggregate);
|
||||
|
||||
var dtoSys = Assert.Single(dto.ExternalSystems);
|
||||
Assert.NotNull(dtoSys.Secrets);
|
||||
Assert.True(dtoSys.Secrets!.Values.ContainsKey("AuthConfiguration"));
|
||||
Assert.Equal("{\"apiKey\":\"super-secret\"}", dtoSys.Secrets.Values["AuthConfiguration"]);
|
||||
// Public part does not carry the secret.
|
||||
Assert.Equal("erp", dtoSys.Name);
|
||||
Assert.Equal("https://erp/api", dtoSys.BaseUrl);
|
||||
Assert.Equal("ApiKey", dtoSys.AuthType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToDto_carves_smtp_password_into_secrets_block()
|
||||
{
|
||||
var smtp = new SmtpConfiguration("smtp.example.com", "Basic", "noreply@example.com")
|
||||
{
|
||||
Id = 1,
|
||||
Port = 587,
|
||||
Credentials = "user:p@ssw0rd",
|
||||
};
|
||||
var aggregate = MakeEmptyAggregate() with { SmtpConfigurations = new[] { smtp } };
|
||||
|
||||
var dto = new EntitySerializer().ToBundleContent(aggregate);
|
||||
|
||||
var dtoSmtp = Assert.Single(dto.SmtpConfigs);
|
||||
Assert.NotNull(dtoSmtp.Secrets);
|
||||
Assert.Equal("user:p@ssw0rd", dtoSmtp.Secrets!.Values["Credentials"]);
|
||||
Assert.Equal("smtp.example.com", dtoSmtp.Host);
|
||||
Assert.Equal(587, dtoSmtp.Port);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roundtrip_template_preserves_attributes_alarms_scripts_composition()
|
||||
{
|
||||
var folder = new TemplateFolder("root") { Id = 1, SortOrder = 0 };
|
||||
var basic = new Template("Basic") { Id = 1, FolderId = 1, Description = "base" };
|
||||
basic.Attributes.Add(new TemplateAttribute("Pressure")
|
||||
{
|
||||
Id = 1,
|
||||
TemplateId = 1,
|
||||
DataType = DataType.Double,
|
||||
Value = "0",
|
||||
IsLocked = true,
|
||||
Description = "PSI",
|
||||
});
|
||||
basic.Scripts.Add(new TemplateScript("OnUpdate", "return 1;")
|
||||
{
|
||||
Id = 1,
|
||||
TemplateId = 1,
|
||||
TriggerType = "Periodic",
|
||||
ParameterDefinitions = "[]",
|
||||
ReturnDefinition = "void",
|
||||
IsLocked = false,
|
||||
MinTimeBetweenRuns = TimeSpan.FromSeconds(30),
|
||||
});
|
||||
basic.Alarms.Add(new TemplateAlarm("High")
|
||||
{
|
||||
Id = 1,
|
||||
TemplateId = 1,
|
||||
PriorityLevel = 2,
|
||||
TriggerType = AlarmTriggerType.RangeViolation,
|
||||
TriggerConfiguration = "{\"threshold\":100}",
|
||||
IsLocked = false,
|
||||
// FU-B / #37 — alarm fires "OnUpdate" script when triggered. The FK is
|
||||
// an in-aggregate script id; the DTO carries the name and the
|
||||
// importer resolves the FK on the way back in.
|
||||
OnTriggerScriptId = 1,
|
||||
});
|
||||
|
||||
var assembly = new Template("Assembly") { Id = 2, FolderId = 1 };
|
||||
assembly.Compositions.Add(new TemplateComposition("MotorA")
|
||||
{
|
||||
Id = 1,
|
||||
TemplateId = 2,
|
||||
ComposedTemplateId = 1, // refers to "Basic".
|
||||
});
|
||||
|
||||
var aggregate = MakeEmptyAggregate() with
|
||||
{
|
||||
TemplateFolders = new[] { folder },
|
||||
Templates = new[] { basic, assembly },
|
||||
};
|
||||
|
||||
var sut = new EntitySerializer();
|
||||
var dto = sut.ToBundleContent(aggregate);
|
||||
|
||||
// FU-B / #37 — verify the DTO carries OnTriggerScriptName by NAME (not id).
|
||||
// The bundle is portable across environments so a script-id FK can't
|
||||
// survive a round-trip; resolution back to a script id is the importer's
|
||||
// job once the parent template's scripts have been re-persisted.
|
||||
var dtoBasic = Assert.Single(dto.Templates, t => t.Name == "Basic");
|
||||
var dtoAlarm = Assert.Single(dtoBasic.Alarms);
|
||||
Assert.Equal("OnUpdate", dtoAlarm.OnTriggerScriptName);
|
||||
|
||||
var roundTripped = sut.FromBundleContent(dto);
|
||||
|
||||
var rtBasic = Assert.Single(roundTripped.Templates, t => t.Name == "Basic");
|
||||
var rtAttr = Assert.Single(rtBasic.Attributes);
|
||||
Assert.Equal("Pressure", rtAttr.Name);
|
||||
Assert.Equal(DataType.Double, rtAttr.DataType);
|
||||
Assert.Equal("0", rtAttr.Value);
|
||||
Assert.True(rtAttr.IsLocked);
|
||||
|
||||
var rtAlarm = Assert.Single(rtBasic.Alarms);
|
||||
Assert.Equal("High", rtAlarm.Name);
|
||||
Assert.Equal(AlarmTriggerType.RangeViolation, rtAlarm.TriggerType);
|
||||
Assert.Equal("{\"threshold\":100}", rtAlarm.TriggerConfiguration);
|
||||
Assert.Equal(2, rtAlarm.PriorityLevel);
|
||||
// FromBundleContent leaves OnTriggerScriptId null — the importer
|
||||
// resolves it post-persist when target-DB script ids are known.
|
||||
Assert.Null(rtAlarm.OnTriggerScriptId);
|
||||
|
||||
var rtScript = Assert.Single(rtBasic.Scripts);
|
||||
Assert.Equal("OnUpdate", rtScript.Name);
|
||||
Assert.Equal("return 1;", rtScript.Code);
|
||||
Assert.Equal("Periodic", rtScript.TriggerType);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), rtScript.MinTimeBetweenRuns);
|
||||
|
||||
var rtAssembly = Assert.Single(roundTripped.Templates, t => t.Name == "Assembly");
|
||||
var rtComp = Assert.Single(rtAssembly.Compositions);
|
||||
Assert.Equal("MotorA", rtComp.InstanceName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roundtrip_template_folder_preserves_hierarchy()
|
||||
{
|
||||
var root = new TemplateFolder("Root") { Id = 1, SortOrder = 0 };
|
||||
var child = new TemplateFolder("Pumps") { Id = 2, ParentFolderId = 1, SortOrder = 1 };
|
||||
|
||||
var aggregate = MakeEmptyAggregate() with
|
||||
{
|
||||
TemplateFolders = new[] { root, child },
|
||||
};
|
||||
|
||||
var sut = new EntitySerializer();
|
||||
var dto = sut.ToBundleContent(aggregate);
|
||||
var rt = sut.FromBundleContent(dto);
|
||||
|
||||
Assert.Equal(2, rt.TemplateFolders.Count);
|
||||
var rtRoot = Assert.Single(rt.TemplateFolders, f => f.Name == "Root");
|
||||
var rtChild = Assert.Single(rt.TemplateFolders, f => f.Name == "Pumps");
|
||||
Assert.Null(rtRoot.ParentFolderId);
|
||||
// Hierarchy is preserved by name reference; new local ids get assigned but
|
||||
// the child's parent must still point at the row whose name is "Root".
|
||||
Assert.NotNull(rtChild.ParentFolderId);
|
||||
Assert.Equal(rtRoot.Id, rtChild.ParentFolderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Roundtrip_external_system_preserves_retry_config()
|
||||
{
|
||||
var sys = new ExternalSystemDefinition("billing", "https://billing/api", "Basic")
|
||||
{
|
||||
Id = 1,
|
||||
MaxRetries = 5,
|
||||
RetryDelay = TimeSpan.FromSeconds(15),
|
||||
};
|
||||
var aggregate = MakeEmptyAggregate() with { ExternalSystems = new[] { sys } };
|
||||
|
||||
var sut = new EntitySerializer();
|
||||
var dto = sut.ToBundleContent(aggregate);
|
||||
var roundTripped = sut.FromBundleContent(dto);
|
||||
|
||||
var rtSys = Assert.Single(roundTripped.ExternalSystems);
|
||||
Assert.Equal("billing", rtSys.Name);
|
||||
Assert.Equal(5, rtSys.MaxRetries);
|
||||
Assert.Equal(TimeSpan.FromSeconds(15), rtSys.RetryDelay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromDto_with_null_SecretsBlock_yields_entity_with_default_empty_secrets()
|
||||
{
|
||||
var dto = new BundleContentDto(
|
||||
TemplateFolders: Array.Empty<TemplateFolderDto>(),
|
||||
Templates: Array.Empty<TemplateDto>(),
|
||||
SharedScripts: Array.Empty<SharedScriptDto>(),
|
||||
ExternalSystems: new[]
|
||||
{
|
||||
new ExternalSystemDto("erp", "https://x", "None", MaxRetries: 3, RetryDelay: TimeSpan.FromSeconds(5), Array.Empty<ExternalSystemMethodDto>(), Secrets: null),
|
||||
},
|
||||
DatabaseConnections: Array.Empty<DatabaseConnectionDto>(),
|
||||
NotificationLists: Array.Empty<NotificationListDto>(),
|
||||
SmtpConfigs: Array.Empty<SmtpConfigDto>(),
|
||||
ApiKeys: Array.Empty<ApiKeyDto>(),
|
||||
ApiMethods: Array.Empty<ApiMethodDto>());
|
||||
|
||||
var aggregate = new EntitySerializer().FromBundleContent(dto);
|
||||
|
||||
var sys = Assert.Single(aggregate.ExternalSystems);
|
||||
Assert.Null(sys.AuthConfiguration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
|
||||
using ZB.MOM.WW.ScadaBridge.Transport.Serialization;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests.Serialization;
|
||||
|
||||
public sealed class ManifestBuilderTests
|
||||
{
|
||||
private static BundleSummary EmptySummary => new(0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
private static IReadOnlyList<ManifestContentEntry> NoContents => Array.Empty<ManifestContentEntry>();
|
||||
|
||||
[Fact]
|
||||
public void Build_populates_summary_from_contents()
|
||||
{
|
||||
var sut = new ManifestBuilder();
|
||||
var summary = new BundleSummary(2, 1, 0, 0, 0, 0, 0, 0, 0);
|
||||
var contents = new[]
|
||||
{
|
||||
new ManifestContentEntry("Template", "T1", 1, Array.Empty<string>()),
|
||||
new ManifestContentEntry("Template", "T2", 1, new[] { "T1" }),
|
||||
new ManifestContentEntry("TemplateFolder", "F1", 1, Array.Empty<string>()),
|
||||
};
|
||||
|
||||
var manifest = sut.Build("env", "user", "1.0.0", encryption: null, summary, contents, contentBytes: new byte[] { 1, 2, 3 });
|
||||
|
||||
Assert.Equal(summary, manifest.Summary);
|
||||
Assert.Equal(contents, manifest.Contents);
|
||||
Assert.Equal("env", manifest.SourceEnvironment);
|
||||
Assert.Equal("user", manifest.ExportedBy);
|
||||
Assert.Equal("1.0.0", manifest.ScadaBridgeVersion);
|
||||
Assert.Equal(1, manifest.BundleFormatVersion);
|
||||
Assert.Equal("1.0", manifest.SchemaVersion);
|
||||
Assert.Null(manifest.Encryption);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_computes_content_hash_with_sha256_prefix()
|
||||
{
|
||||
var sut = new ManifestBuilder();
|
||||
var bytes = Encoding.UTF8.GetBytes("known-content");
|
||||
var expectedHashHex = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
||||
|
||||
var manifest = sut.Build("env", "user", "v", encryption: null, EmptySummary, NoContents, bytes);
|
||||
|
||||
Assert.Equal("sha256:" + expectedHashHex, manifest.ContentHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_rejects_unsupported_bundleFormatVersion()
|
||||
{
|
||||
var bytes = new byte[] { 1, 2, 3 };
|
||||
var hash = "sha256:" + Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
||||
var manifest = new BundleManifest(
|
||||
BundleFormatVersion: 999,
|
||||
SchemaVersion: "1.0",
|
||||
CreatedAtUtc: DateTimeOffset.UtcNow,
|
||||
SourceEnvironment: "env",
|
||||
ExportedBy: "u",
|
||||
ScadaBridgeVersion: "v",
|
||||
ContentHash: hash,
|
||||
Encryption: null,
|
||||
Summary: EmptySummary,
|
||||
Contents: NoContents);
|
||||
|
||||
var result = new ManifestValidator().Validate(manifest, bytes);
|
||||
|
||||
Assert.Equal(ManifestValidationResult.UnsupportedFormatVersion, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_rejects_when_contentHash_mismatch()
|
||||
{
|
||||
var bytes = new byte[] { 1, 2, 3 };
|
||||
var manifest = new BundleManifest(
|
||||
BundleFormatVersion: 1,
|
||||
SchemaVersion: "1.0",
|
||||
CreatedAtUtc: DateTimeOffset.UtcNow,
|
||||
SourceEnvironment: "env",
|
||||
ExportedBy: "u",
|
||||
ScadaBridgeVersion: "v",
|
||||
ContentHash: "sha256:deadbeef",
|
||||
Encryption: null,
|
||||
Summary: EmptySummary,
|
||||
Contents: NoContents);
|
||||
|
||||
var result = new ManifestValidator().Validate(manifest, bytes);
|
||||
|
||||
Assert.Equal(ManifestValidationResult.ContentHashMismatch, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_accepts_well_formed_v1_manifest()
|
||||
{
|
||||
var sut = new ManifestBuilder();
|
||||
var bytes = Encoding.UTF8.GetBytes("hello");
|
||||
var manifest = sut.Build("env", "u", "v", encryption: null, EmptySummary, NoContents, bytes);
|
||||
|
||||
var result = new ManifestValidator().Validate(manifest, bytes);
|
||||
|
||||
Assert.Equal(ManifestValidationResult.Ok, result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user