/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using Opc.Ua.Tests; namespace Opc.Ua.Core.Tests { [TestFixture] [SetCulture("en-us")] [SetUICulture("en-us")] public class ApplicationConfigurationTests { private ITelemetryContext m_telemetry; [SetUp] public void SetUp() { m_telemetry = NUnitTelemetryContext.Create(); } [Test] /// /// Parameterless constructor creates a valid instance. /// public void ConstructorDefault() { var config = new ApplicationConfiguration(); Assert.That(config, Is.Not.Null); } [Test] /// /// Constructor with telemetry creates a valid instance. /// public void ConstructorWithTelemetry() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config, Is.Not.Null); } [Test] /// /// Copy constructor copies all supported properties. /// public void ConstructorCopy() { var original = new ApplicationConfiguration(m_telemetry) { ApplicationName = "TestApp", ApplicationUri = "urn:test:app", ApplicationType = ApplicationType.Server, DisableHiResClock = true }; var copy = new ApplicationConfiguration(original); Assert.That(copy.ApplicationName, Is.EqualTo("TestApp")); Assert.That(copy.ApplicationUri, Is.EqualTo("urn:test:app")); Assert.That(copy.ApplicationType, Is.EqualTo(ApplicationType.Server)); Assert.That(copy.DisableHiResClock, Is.True); } [Test] /// /// ApplicationName get/set round-trips correctly. /// public void ApplicationNameGetSet() { var config = new ApplicationConfiguration(m_telemetry) { ApplicationName = "MyApp" }; Assert.That(config.ApplicationName, Is.EqualTo("MyApp")); } [Test] /// /// ApplicationName defaults to null. /// public void ApplicationNameDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ApplicationName, Is.Null); } [Test] /// /// ApplicationUri get/set round-trips correctly. /// public void ApplicationUriGetSet() { var config = new ApplicationConfiguration(m_telemetry) { ApplicationUri = "urn:test:myapp" }; Assert.That(config.ApplicationUri, Is.EqualTo("urn:test:myapp")); } [Test] /// /// ApplicationUri defaults to null. /// public void ApplicationUriDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ApplicationUri, Is.Null); } [Test] /// /// ProductUri get/set round-trips correctly. /// public void ProductUriGetSet() { var config = new ApplicationConfiguration(m_telemetry) { ProductUri = "urn:test:product" }; Assert.That(config.ProductUri, Is.EqualTo("urn:test:product")); } [Test] /// /// ProductUri defaults to null. /// public void ProductUriDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ProductUri, Is.Null); } [Test] /// /// ApplicationType get/set round-trips correctly. /// public void ApplicationTypeGetSet() { var config = new ApplicationConfiguration(m_telemetry) { ApplicationType = ApplicationType.ClientAndServer }; Assert.That(config.ApplicationType, Is.EqualTo(ApplicationType.ClientAndServer)); } [Test] /// /// ApplicationType defaults to Server. /// public void ApplicationTypeDefaultIsServer() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ApplicationType, Is.EqualTo(ApplicationType.Server)); } [Test] /// /// ServerConfiguration get/set round-trips correctly. /// public void ServerConfigurationGetSet() { var config = new ApplicationConfiguration(m_telemetry); var serverConfig = new ServerConfiguration(); config.ServerConfiguration = serverConfig; Assert.That(config.ServerConfiguration, Is.SameAs(serverConfig)); } [Test] /// /// ServerConfiguration defaults to null. /// public void ServerConfigurationDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ServerConfiguration, Is.Null); } [Test] /// /// ClientConfiguration get/set round-trips correctly. /// public void ClientConfigurationGetSet() { var config = new ApplicationConfiguration(m_telemetry); var clientConfig = new ClientConfiguration(); config.ClientConfiguration = clientConfig; Assert.That(config.ClientConfiguration, Is.SameAs(clientConfig)); } [Test] /// /// ClientConfiguration defaults to null. /// public void ClientConfigurationDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ClientConfiguration, Is.Null); } [Test] /// /// SecurityConfiguration is created by default constructor. /// public void SecurityConfigurationDefaultIsNotNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.SecurityConfiguration, Is.Not.Null); } [Test] /// /// SecurityConfiguration set with null reverts to default. /// public void SecurityConfigurationSetNullReturnsDefault() { var config = new ApplicationConfiguration(m_telemetry) { SecurityConfiguration = null! }; Assert.That(config.SecurityConfiguration, Is.Not.Null); } [Test] /// /// SecurityConfiguration get/set round-trips correctly. /// public void SecurityConfigurationGetSet() { var config = new ApplicationConfiguration(m_telemetry); var secConfig = new SecurityConfiguration(); config.SecurityConfiguration = secConfig; Assert.That(config.SecurityConfiguration, Is.SameAs(secConfig)); } [Test] /// /// TransportQuotas get/set round-trips correctly. /// public void TransportQuotasGetSet() { var config = new ApplicationConfiguration(m_telemetry); var quotas = new TransportQuotas(); config.TransportQuotas = quotas; Assert.That(config.TransportQuotas, Is.SameAs(quotas)); } [Test] /// /// TransportQuotas defaults to null. /// public void TransportQuotasDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.TransportQuotas, Is.Null); } [Test] /// /// TransportConfigurations is created by default constructor. /// public void TransportConfigurationsDefaultIsNotNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.TransportConfigurations.IsNull, Is.False); } [Test] /// /// TransportConfigurations get/set round-trips correctly. /// public void TransportConfigurationsGetSet() { var config = new ApplicationConfiguration(m_telemetry) { TransportConfigurations = new List { new("opc.tcp", typeof(object)) }.ToArrayOf() }; Assert.That(config.TransportConfigurations.Count, Is.EqualTo(1)); } [Test] /// /// CertificateManager is null by default before validation. /// public void CertificateManagerDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.CertificateManager, Is.Null); } [Test] /// /// CertificateManager get/set round-trips correctly. /// public void CertificateManagerGetSet() { var config = new ApplicationConfiguration(m_telemetry); using var manager = new CertificateManager(m_telemetry); config.CertificateManager = manager; Assert.That(config.CertificateManager, Is.SameAs(manager)); } [Test] /// /// TraceConfiguration get/set round-trips correctly. /// public void TraceConfigurationGetSet() { var config = new ApplicationConfiguration(m_telemetry) { TraceConfiguration = new TraceConfiguration { OutputFilePath = "trace.log" } }; Assert.That(config.TraceConfiguration, Is.Not.Null); Assert.That(config.TraceConfiguration.OutputFilePath, Is.EqualTo("trace.log")); } [Test] /// /// TraceConfiguration defaults to null. /// public void TraceConfigurationDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.TraceConfiguration, Is.Null); } [Test] /// /// DisableHiResClock get/set round-trips correctly. /// public void DisableHiResClockGetSet() { var config = new ApplicationConfiguration(m_telemetry) { DisableHiResClock = true }; Assert.That(config.DisableHiResClock, Is.True); } [Test] /// /// DisableHiResClock defaults to false. /// public void DisableHiResClockDefaultIsFalse() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.DisableHiResClock, Is.False); } [Test] /// /// DiscoveryServerConfiguration get/set round-trips correctly. /// public void DiscoveryServerConfigurationGetSet() { var config = new ApplicationConfiguration(m_telemetry); var dsc = new DiscoveryServerConfiguration(); config.DiscoveryServerConfiguration = dsc; Assert.That(config.DiscoveryServerConfiguration, Is.SameAs(dsc)); } [Test] /// /// DiscoveryServerConfiguration defaults to null. /// public void DiscoveryServerConfigurationDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.DiscoveryServerConfiguration, Is.Null); } [Test] /// /// Properties dictionary is accessible and initially empty. /// public void PropertiesDictionaryIsAccessible() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.Properties, Is.Not.Null); Assert.That(config.Properties, Is.Empty); } [Test] /// /// GetOrAddProperty runs the factory once when the key is absent. /// public void GetOrAddPropertyAddsWhenKeyIsAbsent() { var config = new ApplicationConfiguration(m_telemetry); int calls = 0; string value = config.GetOrAddProperty("key", () => { calls++; return "made"; }); Assert.That(value, Is.EqualTo("made")); Assert.That(calls, Is.EqualTo(1)); Assert.That(config.Properties["key"], Is.EqualTo("made")); } [Test] /// /// GetOrAddProperty does not run the factory when a value is present. /// public void GetOrAddPropertyDoesNotRunFactoryWhenKeyIsPresent() { var config = new ApplicationConfiguration(m_telemetry); config.Properties["key"] = "existing"; string value = config.GetOrAddProperty( "key", () => throw new InvalidOperationException("factory must not run")); Assert.That(value, Is.EqualTo("existing")); } [Test] /// /// Concurrent callers all observe the same instance, which is what the /// removed PropertiesLock was taken for. /// public void GetOrAddPropertyPublishesOneInstanceToConcurrentCallers() { var config = new ApplicationConfiguration(m_telemetry); const int callers = 32; using var start = new ManualResetEventSlim(false); object[] results = new object[callers]; var threads = new Thread[callers]; for (int i = 0; i < callers; i++) { int index = i; threads[i] = new Thread(() => { start.Wait(); results[index] = config.GetOrAddProperty("key", () => new object()); }); threads[i].Start(); } start.Set(); foreach (Thread thread in threads) { thread.Join(); } Assert.That(results, Is.All.SameAs(results[0])); Assert.That(config.Properties["key"], Is.SameAs(results[0])); } [Test] /// /// GetOrAddProperty rejects a null key or factory. /// public void GetOrAddPropertyThrowsOnNullArguments() { var config = new ApplicationConfiguration(m_telemetry); Assert.That( () => config.GetOrAddProperty(null!, () => "x"), Throws.TypeOf()); Assert.That( () => config.GetOrAddProperty("key", null!), Throws.TypeOf()); } [Test] /// /// ExtensionObjects list is accessible. /// public void ExtensionObjectsIsAccessible() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.ExtensionObjects, Is.Not.Null); } [Test] /// /// CreateMessageContext returns a valid ServiceMessageContext. /// public void CreateMessageContextReturnsValidContext() { var config = new ApplicationConfiguration(m_telemetry); ServiceMessageContext context = config.CreateMessageContext(); Assert.That(context, Is.Not.Null); Assert.That(context.NamespaceUris, Is.Not.Null); } [Test] /// /// CreateMessageContext with factory returns context with that factory. /// public void CreateMessageContextWithFactory() { var config = new ApplicationConfiguration(m_telemetry); IEncodeableFactory factory = EncodeableFactory.Create(); ServiceMessageContext context = config.CreateMessageContext(factory); Assert.That(context, Is.Not.Null); Assert.That(context.Factory, Is.SameAs(factory)); } [Test] /// /// CreateMessageContext applies TransportQuotas when set. /// public void CreateMessageContextAppliesTransportQuotas() { var config = new ApplicationConfiguration(m_telemetry) { TransportQuotas = new TransportQuotas { MaxArrayLength = 42, MaxStringLength = 99, MaxByteStringLength = 101, MaxMessageSize = 5000, MaxEncodingNestingLevels = 7, MaxDecoderRecoveries = 3 } }; ServiceMessageContext context = config.CreateMessageContext(); Assert.That(context.MaxArrayLength, Is.EqualTo(42)); Assert.That(context.MaxStringLength, Is.EqualTo(99)); Assert.That(context.MaxByteStringLength, Is.EqualTo(101)); Assert.That(context.MaxMessageSize, Is.EqualTo(5000)); Assert.That(context.MaxEncodingNestingLevels, Is.EqualTo(7)); Assert.That(context.MaxDecoderRecoveries, Is.EqualTo(3)); } [Test] /// /// CreateMessageContext without TransportQuotas uses defaults. /// public void CreateMessageContextWithoutTransportQuotasUsesDefaults() { var config = new ApplicationConfiguration(m_telemetry); ServiceMessageContext context = config.CreateMessageContext(); Assert.That(context.MaxArrayLength, Is.EqualTo(DefaultEncodingLimits.MaxArrayLength)); Assert.That(context.MaxStringLength, Is.EqualTo(DefaultEncodingLimits.MaxStringLength)); } [Test] /// /// GetServerDomainNames returns empty when ServerConfiguration is null. /// public void GetServerDomainNamesReturnsEmptyWhenNoServerConfig() { var config = new ApplicationConfiguration(m_telemetry); ArrayOf domains = config.GetServerDomainNames(); Assert.That(domains.Count, Is.Zero); } [Test] /// /// GetServerDomainNames extracts domains from base addresses. /// public void GetServerDomainNamesExtractsFromBaseAddresses() { var config = new ApplicationConfiguration(m_telemetry) { ServerConfiguration = new ServerConfiguration() }; config.ServerConfiguration.BaseAddresses = new List { "opc.tcp://myserver:4840" }.ToArrayOf(); ArrayOf domains = config.GetServerDomainNames(); Assert.That(domains.Count, Is.GreaterThan(0)); } [Test] /// /// GetServerDomainNames deduplicates domain names. /// public void GetServerDomainNamesDeduplicates() { var config = new ApplicationConfiguration(m_telemetry) { ServerConfiguration = new ServerConfiguration() }; config.ServerConfiguration.BaseAddresses = new List { "opc.tcp://myserver:4840", "https://myserver:4843" }.ToArrayOf(); ArrayOf domains = config.GetServerDomainNames(); Assert.That(domains.Count, Is.EqualTo(1)); } [Test] /// /// GetServerDomainNames includes alternate base addresses. /// public void GetServerDomainNamesIncludesAlternateAddresses() { var config = new ApplicationConfiguration(m_telemetry) { ServerConfiguration = new ServerConfiguration() }; config.ServerConfiguration.BaseAddresses = new List { "opc.tcp://server1:4840" }.ToArrayOf(); config.ServerConfiguration.AlternateBaseAddresses = new List { "opc.tcp://server2:4840" }.ToArrayOf(); ArrayOf domains = config.GetServerDomainNames(); Assert.That(domains.Count, Is.EqualTo(2)); } [TestCase("https")] [TestCase("opc.https")] [TestCase("wss")] [TestCase("opc.wss")] [TestCase("HTTPS")] [TestCase("Opc.Https")] [TestCase("WSS")] [TestCase("Opc.Wss")] public void GetServerDomainNamesPreservesConfiguredLoopbackName(string scheme) { var config = new ApplicationConfiguration(m_telemetry) { ServerConfiguration = new ServerConfiguration { BaseAddresses = [$"{scheme}://localhost:4843", $"{scheme}://LOCALHOST:4844"] } }; var expected = new HashSet(StringComparer.OrdinalIgnoreCase) { "localhost", Utils.GetHostName() }; ArrayOf domains = config.GetServerDomainNames(); Assert.That(domains.ToArray(), Is.EquivalentTo(expected)); Assert.That(domains.Count, Is.EqualTo(expected.Count)); } [TestCase("https://127.0.0.1:4843", "127.0.0.1")] [TestCase("https://[::1]:4843", "::1")] public void GetServerDomainNamesPreservesIpAddressIdentity(string endpoint, string expected) { var config = new ApplicationConfiguration(m_telemetry) { ServerConfiguration = new ServerConfiguration { BaseAddresses = [endpoint] } }; ArrayOf domains = config.GetServerDomainNames(); Assert.That(domains.Count, Is.EqualTo(1)); Assert.That(domains[0], Is.EqualTo(expected)); } [Test] /// /// ValidateAsync throws when ApplicationName is empty. /// public void ValidateAsyncThrowsWhenApplicationNameEmpty() { var config = new ApplicationConfiguration(m_telemetry); Assert.ThrowsAsync( async () => await config.ValidateAsync(ApplicationType.Server).ConfigureAwait(false)); } [Test] /// /// ValidateAsync throws when SecurityConfiguration is null. /// public void ValidateAsyncThrowsWhenSecurityConfigurationNull() { var config = new ApplicationConfiguration(m_telemetry) { ApplicationName = "TestApp" }; // Force SecurityConfiguration to a new instance with no trust lists, // then null it out through reflection since the setter prevents null. // Instead, test with a valid name but rely on the SecurityConfiguration.Validate // to throw since there are no certificate stores configured. // The setter prevents null, so SecurityConfiguration is always non-null. // The validate call will proceed past the null check. Assert.ThrowsAsync( async () => await config.ValidateAsync(ApplicationType.Server).ConfigureAwait(false)); } [Test] /// /// ValidateAsync throws for Server type when ServerConfiguration is null. /// public async Task ValidateAsyncThrowsForServerWithNoServerConfiguration() { ApplicationConfiguration config = CreateMinimalValidatableConfig(); config.ApplicationType = ApplicationType.Server; ServiceResultException ex = Assert.ThrowsAsync( async () => await config.ValidateAsync(ApplicationType.Server).ConfigureAwait(false)); Assert.That(ex, Is.Not.Null); await Task.CompletedTask.ConfigureAwait(false); } [Test] /// /// ValidateAsync throws for Client type when ClientConfiguration is null. /// public async Task ValidateAsyncThrowsForClientWithNoClientConfiguration() { ApplicationConfiguration config = CreateMinimalValidatableConfig(); ServiceResultException ex = Assert.ThrowsAsync( async () => await config.ValidateAsync(ApplicationType.Client).ConfigureAwait(false)); Assert.That(ex, Is.Not.Null); await Task.CompletedTask.ConfigureAwait(false); } [Test] /// /// ValidateAsync throws for DiscoveryServer type when DiscoveryServerConfiguration is null. /// public async Task ValidateAsyncThrowsForDiscoveryServerWithNoConfig() { ApplicationConfiguration config = CreateMinimalValidatableConfig(); ServiceResultException ex = Assert.ThrowsAsync( async () => await config.ValidateAsync(ApplicationType.DiscoveryServer).ConfigureAwait(false)); Assert.That(ex, Is.Not.Null); await Task.CompletedTask.ConfigureAwait(false); } [Test] /// /// SourceFilePath is null by default. /// public void SourceFilePathDefaultIsNull() { var config = new ApplicationConfiguration(m_telemetry); Assert.That(config.SourceFilePath, Is.Null); } [Test] /// /// Extensions property get/set round-trips correctly. /// public void ExtensionsGetSet() { var config = new ApplicationConfiguration(m_telemetry) { Extensions = new List().ToArrayOf() }; Assert.That(config.Extensions.IsNull, Is.False); Assert.That(config.Extensions.Count, Is.Zero); } [Test] /// /// Properties dictionary supports adding and retrieving values. /// public void PropertiesCanStoreAndRetrieveValues() { var config = new ApplicationConfiguration(m_telemetry); config.Properties["key1"] = "value1"; Assert.That(config.Properties["key1"], Is.EqualTo("value1")); } [Test] /// /// Copy constructor copies ServerConfiguration reference. /// public void CopyConstructorCopiesServerConfiguration() { var serverConfig = new ServerConfiguration(); var original = new ApplicationConfiguration(m_telemetry) { ApplicationName = "CopyTest", ServerConfiguration = serverConfig }; var copy = new ApplicationConfiguration(original); Assert.That(copy.ServerConfiguration, Is.SameAs(serverConfig)); } [Test] /// /// Copy constructor copies ClientConfiguration reference. /// public void CopyConstructorCopiesClientConfiguration() { var clientConfig = new ClientConfiguration(); var original = new ApplicationConfiguration(m_telemetry) { ApplicationName = "CopyTest", ClientConfiguration = clientConfig }; var copy = new ApplicationConfiguration(original); Assert.That(copy.ClientConfiguration, Is.SameAs(clientConfig)); } [Test] /// /// Copy constructor copies CertificateManager by reference. /// public void CopyConstructorCopiesCertificateManager() { using var manager = new CertificateManager(m_telemetry); var original = new ApplicationConfiguration(m_telemetry) { ApplicationName = "CopyTest", CertificateManager = manager }; var copy = new ApplicationConfiguration(original); Assert.That(copy.CertificateManager, Is.SameAs(manager)); } [Test] /// /// Copy constructor copies TransportQuotas reference. /// public void CopyConstructorCopiesTransportQuotas() { var quotas = new TransportQuotas(); var original = new ApplicationConfiguration(m_telemetry) { ApplicationName = "CopyTest", TransportQuotas = quotas }; var copy = new ApplicationConfiguration(original); Assert.That(copy.TransportQuotas, Is.SameAs(quotas)); } [Test] /// /// TransportQuotas default values match DefaultEncodingLimits. /// public void TransportQuotasDefaultValues() { var quotas = new TransportQuotas(); Assert.That(quotas.MaxArrayLength, Is.EqualTo(DefaultEncodingLimits.MaxArrayLength)); Assert.That(quotas.MaxStringLength, Is.EqualTo(DefaultEncodingLimits.MaxStringLength)); Assert.That(quotas.MaxByteStringLength, Is.EqualTo(DefaultEncodingLimits.MaxByteStringLength)); Assert.That(quotas.MaxMessageSize, Is.EqualTo(DefaultEncodingLimits.MaxMessageSize)); Assert.That(quotas.MaxEncodingNestingLevels, Is.EqualTo(DefaultEncodingLimits.MaxEncodingNestingLevels)); Assert.That(quotas.MaxDecoderRecoveries, Is.EqualTo(DefaultEncodingLimits.MaxDecoderRecoveries)); } [Test] /// /// GetFilePathFromAppConfig returns a non-null path. /// public void GetFilePathFromAppConfigReturnsPath() { string path = ApplicationConfiguration.GetFilePathFromAppConfig( "TestSection", m_telemetry.CreateLogger()); Assert.That(path, Is.Not.Null); Assert.That(path, Does.Contain("TestSection")); } /// /// ValidateAsync defaults TransportQuotas when it is null so the transport /// layer does not fail with a NullReferenceException on server start. /// [Test] public async Task ValidateAsyncDefaultsTransportQuotasWhenNull() { string pkiPath = Path.Combine( Path.GetTempPath(), "OpcUaTestTransportQuotas_" + Guid.NewGuid().ToString("N")); try { ApplicationConfiguration config = CreateValidatableServerConfig(pkiPath); config.TransportQuotas = null; await config.ValidateAsync(ApplicationType.Server).ConfigureAwait(false); Assert.That(config.TransportQuotas, Is.Not.Null); Assert.That( config.TransportQuotas!.MaxMessageSize, Is.EqualTo(DefaultEncodingLimits.MaxMessageSize)); } finally { TryDeleteDirectory(pkiPath); } } /// /// ValidateAsync preserves an explicitly configured TransportQuotas instance. /// [Test] public async Task ValidateAsyncKeepsExplicitTransportQuotas() { string pkiPath = Path.Combine( Path.GetTempPath(), "OpcUaTestTransportQuotas_" + Guid.NewGuid().ToString("N")); try { ApplicationConfiguration config = CreateValidatableServerConfig(pkiPath); var quotas = new TransportQuotas { MaxMessageSize = 1234567 }; config.TransportQuotas = quotas; await config.ValidateAsync(ApplicationType.Server).ConfigureAwait(false); Assert.That(config.TransportQuotas, Is.SameAs(quotas)); } finally { TryDeleteDirectory(pkiPath); } } /// /// ValidateAsync defaults TransportQuotas when it is null on the client path, /// ensuring the behavior is consistent regardless of ApplicationType. /// [Test] public async Task ValidateAsyncDefaultsTransportQuotasWhenNullForClient() { string pkiPath = Path.Combine( Path.GetTempPath(), "OpcUaTestTransportQuotasClient_" + Guid.NewGuid().ToString("N")); try { ApplicationConfiguration config = CreateValidatableClientConfig(pkiPath); config.TransportQuotas = null; await config.ValidateAsync(ApplicationType.Client).ConfigureAwait(false); Assert.That(config.TransportQuotas, Is.Not.Null); Assert.That( config.TransportQuotas!.MaxMessageSize, Is.EqualTo(DefaultEncodingLimits.MaxMessageSize)); } finally { TryDeleteDirectory(pkiPath); } } private ApplicationConfiguration CreateValidatableClientConfig(string pkiPath) { const string applicationName = "TransportQuotasTestClient"; return new ApplicationConfiguration(m_telemetry) { ApplicationName = applicationName, ApplicationUri = "urn:test:" + applicationName, ApplicationType = ApplicationType.Client, ClientConfiguration = new ClientConfiguration(), SecurityConfiguration = new SecurityConfiguration { ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "own"), SubjectName = "CN=" + applicationName }, TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "trusted") }, TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "issuers") }, RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "rejected") }, AutoAcceptUntrustedCertificates = true } }; } private ApplicationConfiguration CreateValidatableServerConfig(string pkiPath) { const string applicationName = "TransportQuotasTestServer"; return new ApplicationConfiguration(m_telemetry) { ApplicationName = applicationName, ApplicationUri = "urn:test:" + applicationName, ApplicationType = ApplicationType.Server, ServerConfiguration = new ServerConfiguration(), SecurityConfiguration = new SecurityConfiguration { ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "own"), SubjectName = "CN=" + applicationName }, TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "trusted") }, TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "issuers") }, RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pkiPath, "rejected") }, AutoAcceptUntrustedCertificates = true } }; } private static void TryDeleteDirectory(string path) { try { if (Directory.Exists(path)) { Directory.Delete(path, true); } } catch { // best effort cleanup } } private ApplicationConfiguration CreateMinimalValidatableConfig() { return new ApplicationConfiguration(m_telemetry) { ApplicationName = "TestApp", ApplicationUri = "urn:test:app" }; } } }