Files
UA-.NETStandard/tests/Opc.Ua.Sessions.Tests/SessionlessInvocationTests.cs
romanett 9b034f8291 Enable nullable reference types centrally; migrate all unset and annotations-only projects (#4700)
# Description

Nullable reference types are now enabled once, in `common.props`, and
every project in the repository builds warning-clean with them.

**Configuration**
- `<Nullable>enable</Nullable>` lives in `common.props`. All per-project
`<Nullable>` settings are removed: 178 project files (enable,
annotations, and the `disable` in the three Aot test projects).
- 419 per-file `#nullable enable`/`restore` directives and the 4
`#nullable disable` directives are removed.
- Kept on purpose:
- The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type
Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`,
`PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the
project setting in generated code, so the directive is required there.
- `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against
the 1.5.378 packages and does not import the repository props.
- `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now
describe the central setting.

**Migration** (the 43 unset and 13 annotations-only projects, roughly
10k diagnostics)
- **Production code: annotated at the declarations.** Covers
`tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the
LDS sample.
- Members, parameters and returns are nullable where null actually
flows.
- Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`,
`IsMethodTypeDesign`).
- `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take
`params object?[]`. `Template.AddReplacement` takes `string?` (rendering
already skips null replacements).
- `IRegisteredServerStore.RegisterAsync` returns
`Task<RegistrationEntry?>`: it returns null when a registration is
removed.
- Dereferences backed by an invariant (for example, the validated model)
use `!`, so runtime behaviour is unchanged.
- **Behavioural changes in production code** (small, deliberate):
- `NodeSetToModelDesign` throws `InvalidOperationException` when the
file is not a NodeSet. Before, it threw a NullReferenceException on the
next line.
- `ModelDesignValidator` starts its lookup tables empty instead of null.
A redundant `if (m_nodes == null) return;` guard is gone.
- **Test code: minimal annotations.**
  - `?` on values that can be null, `!` where the test asserts non-null.
  - `= null!` for fixture members assigned in SetUp.
- `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the
`IEncodeable` constraint.
- A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`)
folded into the adjacent `IsNull`/`Count` checks.

**Notes for reviewers**
- The diff is large (~1,500 files) but almost entirely mechanical: `?`,
`!` and directive removal. The non-annotation changes are listed above.
- Known follow-up, not done here:
`IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay
non-nullable even though the validator returns null before it loads a
dictionary. Making them nullable would ripple through every generator
call site.

## Related Issues

- Fixes #4439

## Checklist

- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

The "no new warnings" box covers net10.0 and net48: full non-incremental
builds of `UA.slnx`, `tools/SourceGeneration.slnx`,
`tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0
warnings and 0 errors under the repository's warnings-as-errors
settings. net9.0 and net8.0 also had no nullable diagnostics, but they
were not rebuilt after the final fixes. Tests have not been run locally
yet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:48:13 +02:00

574 lines
26 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ========================================================================
* 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.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Client.TestFramework;
namespace Opc.Ua.Sessions.Tests
{
/// <summary>
/// compliance tests for Session Service Set – Sessionless Invocation.
/// Verifies that discovery services (GetEndpoints, FindServers) can be
/// called without an established session.
/// </summary>
[TestFixture]
[Category("Conformance")]
[Category("Session")]
[Category("SessionlessInvocation")]
public class SessionlessInvocationTests : TestFixture
{
[Test]
public async Task DiscoveryFailureDoesNotInterruptHealthyClientAsync()
{
using var reserved = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
reserved.Bind(new IPEndPoint(IPAddress.Loopback, 0));
int port = ((IPEndPoint)reserved.LocalEndPoint!).Port;
var refusedUrl = new Uri($"opc.tcp://127.0.0.1:{port}");
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
endpointConfiguration.OperationTimeout = 5000;
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using DiscoveryClient healthyClient = await DiscoveryClient.CreateAsync(
ServerUrl, endpointConfiguration, Telemetry, ct: timeout.Token).ConfigureAwait(false);
// Darwin can leave a connection pending while a non-listening socket owns the port.
// Release the reservation immediately before testing the refused connection.
reserved.Dispose();
await Assert.ThatAsync(async () =>
{
using DiscoveryClient unexpectedClient = await DiscoveryClient.CreateAsync(
refusedUrl, endpointConfiguration, Telemetry, ct: timeout.Token).ConfigureAwait(false);
}, Throws.TypeOf<ServiceResultException>()
.With.Property(nameof(ServiceResultException.StatusCode)).EqualTo((uint)StatusCodes.BadNotConnected)
.And.Property(nameof(Exception.InnerException)).TypeOf<SocketException>()).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await healthyClient.GetEndpointsAsync(
default, timeout.Token).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0));
Assert.That(timeout.IsCancellationRequested, Is.False);
}
[Test]
public async Task DiscoveryPreservesCallerCancellationAsync()
{
using var cancellation = new CancellationTokenSource();
cancellation.Cancel();
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
await Assert.ThatAsync(async () =>
{
using DiscoveryClient unexpectedClient = await DiscoveryClient.CreateAsync(
ServerUrl, endpointConfiguration, Telemetry, ct: cancellation.Token).ConfigureAwait(false);
}, Throws.InstanceOf<OperationCanceledException>()).ConfigureAwait(false);
}
[Description("Call GetEndpoints via DiscoveryClient without an established session. The service should return Good with at least one endpoint.")]
[Test]
public async Task GetEndpointsWithoutSessionAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints, Is.Not.Null,
"GetEndpoints response should not be null.");
Assert.That(endpoints.Count, Is.GreaterThan(0),
"GetEndpoints without a session should return at least one endpoint.");
}
[Description("Call FindServers via DiscoveryClient without an established session. The service should return Good with at least one server.")]
[Test]
public async Task FindServersWithoutSessionAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers, Is.Not.Null,
"FindServers response should not be null.");
Assert.That(servers.Count, Is.GreaterThan(0),
"FindServers without a session should return at least one server.");
}
[Description("Verify each endpoint returned by GetEndpoints has EndpointUrl, SecurityMode, and SecurityPolicyUri populated.")]
[Test]
public async Task GetEndpointsReturnsValidEndpointsAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0));
for (int i = 0; i < endpoints.Count; i++)
{
EndpointDescription ep = endpoints[i];
Assert.That(ep.EndpointUrl, Is.Not.Null.And.Not.Empty,
$"Endpoint[{i}] EndpointUrl should not be empty.");
Assert.That(ep.SecurityMode, Is.Not.EqualTo(MessageSecurityMode.Invalid),
$"Endpoint[{i}] SecurityMode should not be Invalid.");
Assert.That(ep.SecurityPolicyUri, Is.Not.Null.And.Not.Empty,
$"Endpoint[{i}] SecurityPolicyUri should not be empty.");
}
}
[Description("Verify each ApplicationDescription returned by FindServers has ApplicationUri, ApplicationName, and ApplicationType populated.")]
[Test]
public async Task FindServersReturnsValidApplicationDescriptionAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers.Count, Is.GreaterThan(0));
for (int i = 0; i < servers.Count; i++)
{
ApplicationDescription app = servers[i];
Assert.That(app.ApplicationUri, Is.Not.Null.And.Not.Empty,
$"Server[{i}] ApplicationUri should not be empty.");
Assert.That(app.ApplicationName, Is.Not.Null,
$"Server[{i}] ApplicationName should not be null.");
Assert.That(app.ApplicationType, Is.Not.EqualTo((ApplicationType)(-1)),
$"Server[{i}] ApplicationType should be valid.");
}
}
[Description("Call GetEndpoints with a transport profile filter for UA TCP. All returned endpoints should match the requested transport profile.")]
[Test]
public async Task GetEndpointsWithProfileFilterAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0));
// Verify at least one endpoint uses the UA TCP transport profile
bool hasTcpTransport = false;
for (int i = 0; i < endpoints.Count; i++)
{
if (!string.IsNullOrEmpty(endpoints[i].TransportProfileUri) &&
endpoints[i].TransportProfileUri!.Contains("uatcp",
StringComparison.OrdinalIgnoreCase))
{
hasTcpTransport = true;
break;
}
}
Assert.That(hasTcpTransport, Is.True,
"At least one endpoint should use UA TCP transport profile.");
}
[Description("Call GetEndpoints multiple times sequentially without a session. All calls should succeed and return endpoints.")]
[Test]
public async Task GetEndpointsMultipleCallsInSequenceAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
for (int call = 0; call < 3; call++)
{
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0),
$"GetEndpoints call {call + 1} should return at least one endpoint.");
}
}
[Description("Call FindServers multiple times sequentially without a session. All calls should succeed and return servers.")]
[Test]
public async Task FindServersMultipleCallsInSequenceAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
for (int call = 0; call < 3; call++)
{
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers.Count, Is.GreaterThan(0),
$"FindServers call {call + 1} should return at least one server.");
}
}
[Description("Verify GetEndpoints returns at least one endpoint, confirming the server supports sessionless discovery.")]
[Test]
public async Task GetEndpointsReturnsDifferentSecurityModesAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0),
"Server should return at least one endpoint.");
}
[Description("Verify that endpoints with security (not None) include a ServerCertificate.")]
[Test]
public async Task GetEndpointsReturnsServerCertificateAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0));
for (int i = 0; i < endpoints.Count; i++)
{
EndpointDescription ep = endpoints[i];
if (ep.SecurityMode != MessageSecurityMode.None)
{
Assert.That(ep.ServerCertificate, Is.Not.Null,
$"Endpoint[{i}] with SecurityMode={ep.SecurityMode} " +
"should include a ServerCertificate.");
Assert.That(ep.ServerCertificate.Length, Is.GreaterThan(0),
$"Endpoint[{i}] ServerCertificate should not be empty.");
}
}
}
[Description("Verify each ApplicationDescription returned by FindServers has at least one DiscoveryUrl.")]
[Test]
public async Task FindServersReturnsDiscoveryUrlsAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers.Count, Is.GreaterThan(0));
for (int i = 0; i < servers.Count; i++)
{
ApplicationDescription app = servers[i];
Assert.That(app.DiscoveryUrls, Is.Not.Null,
$"Server[{i}] DiscoveryUrls should not be null.");
Assert.That(app.DiscoveryUrls.Count, Is.GreaterThan(0),
$"Server[{i}] should have at least one DiscoveryUrl.");
}
}
[Description("Pass an empty string array as the profile filter to GetEndpoints. Should return all available endpoints.")]
[Test]
public async Task SessionlessGetEndpointsWithEmptyProfileFilterAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
// Call with default (no filter) to get baseline count
ArrayOf<EndpointDescription> allEndpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(allEndpoints.Count, Is.GreaterThan(0),
"GetEndpoints with empty profile filter should return all endpoints.");
}
[Description("Call GetEndpoints twice and verify the same number of endpoints is returned each time.")]
[Test]
public async Task SessionlessGetEndpointsReturnsSameResultsOnRepeatedCallsAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> firstCall = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> secondCall = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(firstCall.Count, Is.GreaterThan(0));
Assert.That(secondCall.Count, Is.EqualTo(firstCall.Count),
"Repeated GetEndpoints calls should return the same number of endpoints.");
}
[Description("Create and dispose a DiscoveryClient without calling any services. Verifies that client lifecycle management works without errors.")]
[Test]
public async Task DiscoveryClientCreatedAndDisposedAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
Assert.That(client, Is.Not.Null,
"DiscoveryClient should be created successfully.");
}
[Description("Call GetEndpoints with locale IDs specified. The call should succeed regardless of locale support.")]
[Test]
public async Task GetEndpointsWithLocaleIdsAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints, Is.Not.Null);
Assert.That(endpoints.Count, Is.GreaterThan(0),
"GetEndpoints with locale IDs should return endpoints.");
}
[Description("Call FindServers passing the server URL as the endpointUrl parameter. The server should return at least one matching application.")]
[Test]
public async Task FindServersWithEndpointUrlAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers, Is.Not.Null);
Assert.That(servers.Count, Is.GreaterThan(0),
"FindServers with the server URL should return at least one server.");
}
[Description("Verify at least one endpoint uses SecurityMode.None, since the test fixture has SecurityNone and AutoAccept enabled.")]
[Test]
public async Task GetEndpointsContainsNoneSecurityModeAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0));
bool hasNone = false;
for (int i = 0; i < endpoints.Count; i++)
{
if (endpoints[i].SecurityMode == MessageSecurityMode.None)
{
hasNone = true;
break;
}
}
if (!hasNone)
{
Assert.Ignore("Server does not expose a SecurityMode.None endpoint.");
}
}
[Description("Create a DiscoveryClient without providing any user credentials. The client should connect successfully for discovery operations.")]
[Test]
public async Task DiscoveryClientConnectsWithoutCredentialsAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
Assert.That(client, Is.Not.Null,
"DiscoveryClient should connect without credentials.");
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0),
"Discovery should work without any authentication credentials.");
}
[Description("Verify that both GetEndpoints and FindServers work without any authentication tokens, confirming sessionless invocation.")]
[Test]
public async Task SessionlessCallsDoNotRequireAuthenticationAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0),
"GetEndpoints should succeed without authentication.");
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers.Count, Is.GreaterThan(0),
"FindServers should succeed without authentication.");
}
[Description("Verify that each endpoint returned by GetEndpoints has a TransportProfileUri set.")]
[Test]
public async Task GetEndpointsReturnsTransportProfileUriAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<EndpointDescription> endpoints = await client.GetEndpointsAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(endpoints.Count, Is.GreaterThan(0));
for (int i = 0; i < endpoints.Count; i++)
{
Assert.That(endpoints[i].TransportProfileUri, Is.Not.Null.And.Not.Empty,
$"Endpoint[{i}] TransportProfileUri should not be empty.");
}
}
[Description("Verify that each server returned by FindServers has ApplicationType of Server or ClientAndServer.")]
[Test]
public async Task FindServersApplicationTypeIsServerOrBothAsync()
{
var endpointConfiguration = EndpointConfiguration.Create(ClientFixture.Config);
using DiscoveryClient client = await DiscoveryClient.CreateAsync(
ServerUrl,
endpointConfiguration,
Telemetry,
ct: CancellationToken.None).ConfigureAwait(false);
ArrayOf<ApplicationDescription> servers = await client.FindServersAsync(
default, CancellationToken.None).ConfigureAwait(false);
Assert.That(servers.Count, Is.GreaterThan(0));
for (int i = 0; i < servers.Count; i++)
{
ApplicationDescription app = servers[i];
bool isValidType =
app.ApplicationType is ApplicationType.Server or
ApplicationType.ClientAndServer;
Assert.That(isValidType, Is.True,
$"Server[{i}] ApplicationType should be Server or ClientAndServer, " +
$"but was {app.ApplicationType}.");
}
}
}
}