mirror of
https://gitlab.com/fabinfra/fabaccess/borepin.git
synced 2025-05-10 03:43:27 +02:00
Added more API Service Methods
This commit is contained in:
parent
a9a3f9c545
commit
bb4a74f8c4
@ -130,11 +130,6 @@ namespace Borepin.Service.BFFH
|
|||||||
/// <exception cref="AuthenticatingFailedException"></exception>
|
/// <exception cref="AuthenticatingFailedException"></exception>
|
||||||
public async Task Connect(Connection connection)
|
public async Task Connect(Connection connection)
|
||||||
{
|
{
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
throw new AllreadyConnectedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
string password;
|
string password;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -162,7 +157,6 @@ namespace Borepin.Service.BFFH
|
|||||||
if (! await _AuthenticatePlainAsync(connection.Username, password).ConfigureAwait(false))
|
if (! await _AuthenticatePlainAsync(connection.Username, password).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await Disconnect().ConfigureAwait(false);
|
await Disconnect().ConfigureAwait(false);
|
||||||
throw new AuthenticatingFailedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_CurrentConnection = new Connection_Plain(connection)
|
_CurrentConnection = new Connection_Plain(connection)
|
||||||
@ -182,10 +176,6 @@ namespace Borepin.Service.BFFH
|
|||||||
/// <exception cref="AuthenticatingFailedException"></exception>
|
/// <exception cref="AuthenticatingFailedException"></exception>
|
||||||
public async Task Connect(Connection connection, string password)
|
public async Task Connect(Connection connection, string password)
|
||||||
{
|
{
|
||||||
if (IsConnected)
|
|
||||||
{
|
|
||||||
throw new AllreadyConnectedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -201,7 +191,7 @@ namespace Borepin.Service.BFFH
|
|||||||
if (!await _AuthenticatePlainAsync(connection.Username, password).ConfigureAwait(false))
|
if (!await _AuthenticatePlainAsync(connection.Username, password).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await Disconnect().ConfigureAwait(false);
|
await Disconnect().ConfigureAwait(false);
|
||||||
throw new AuthenticatingFailedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_CurrentConnection = new Connection_Plain(connection)
|
_CurrentConnection = new Connection_Plain(connection)
|
||||||
@ -239,15 +229,6 @@ namespace Borepin.Service.BFFH
|
|||||||
|
|
||||||
_APIConnection = new FabAccessAPI.Connection(rpcClient);
|
_APIConnection = new FabAccessAPI.Connection(rpcClient);
|
||||||
}
|
}
|
||||||
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
throw new ReconnectingFailedException("Connecting failed", new ConnectingFailedException("Connecting failed", exception));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! await _AuthenticatePlainAsync(_CurrentConnection.Username, _CurrentConnection.Password).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
throw new ReconnectingFailedException("Authentication failed", new AuthenticatingFailedException());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -316,11 +297,7 @@ namespace Borepin.Service.BFFH
|
|||||||
await _APIConnection.Auth("PLAIN", new Dictionary<string, object>(StringComparer.Ordinal) { { "Username", username }, { "Password", password } }).ConfigureAwait(false);
|
await _APIConnection.Auth("PLAIN", new Dictionary<string, object>(StringComparer.Ordinal) { { "Username", username }, { "Password", password } }).ConfigureAwait(false);
|
||||||
return await Task.FromResult(true).ConfigureAwait(false);
|
return await Task.FromResult(true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch(InvalidCredentialsException)
|
catch(UnsupportedMechanismException)
|
||||||
{
|
|
||||||
return await Task.FromResult(true).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (AuthenticatingFailedException)
|
|
||||||
{
|
{
|
||||||
return await Task.FromResult(true).ConfigureAwait(false);
|
return await Task.FromResult(true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Borepin.Service.BFFH.Exceptions
|
|
||||||
{
|
|
||||||
public class APIIncompatibleException : Exception
|
|
||||||
{
|
|
||||||
public APIIncompatibleException()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public APIIncompatibleException(string message) : base(message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public APIIncompatibleException(string message, Exception inner) : base(message, inner)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Borepin.Service.BFFH.Exceptions
|
|
||||||
{
|
|
||||||
public class AllreadyConnectedException : Exception
|
|
||||||
{
|
|
||||||
public AllreadyConnectedException()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AllreadyConnectedException(string message) : base(message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AllreadyConnectedException(string message, Exception inner) : base(message, inner)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Borepin.Service.BFFH.Exceptions
|
|
||||||
{
|
|
||||||
public class AuthenticatingFailedException : Exception
|
|
||||||
{
|
|
||||||
public AuthenticatingFailedException()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticatingFailedException(string message) : base(message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticatingFailedException(string message, Exception inner) : base(message, inner)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Borepin.Service.BFFH.Exceptions
|
|
||||||
{
|
|
||||||
public class ConnectingFailedException : Exception
|
|
||||||
{
|
|
||||||
public ConnectingFailedException()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectingFailedException(string message) : base(message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectingFailedException(string message, Exception inner) : base(message, inner)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,19 @@
|
|||||||
using FabAccessAPI.Schema;
|
using Capnp.Rpc;
|
||||||
|
using FabAccessAPI.Exceptions;
|
||||||
|
using FabAccessAPI.Schema;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FabAccessAPI
|
namespace FabAccessAPI
|
||||||
{
|
{
|
||||||
public class API : IAPI
|
public class API : IAPI
|
||||||
{
|
{
|
||||||
#region Private Members
|
#region Private Members
|
||||||
|
private Connection _APIConnection;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
@ -28,7 +36,7 @@ namespace FabAccessAPI
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _APIConnection != null && _APIConnection.RpcClient.State == ConnectionState.Active;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,24 +50,138 @@ namespace FabAccessAPI
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods
|
#region Methods
|
||||||
public void Connect(ConnectionData connectionData)
|
/// <summary>
|
||||||
|
/// Connect to server with ConnectionData
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="AuthenticationException"></exception>
|
||||||
|
/// <exception cref="ConnectingFailedException"></exception>
|
||||||
|
public async Task Connect(ConnectionData connectionData)
|
||||||
|
{
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
await Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
TcpRpcClient rpcClient = await _ConnectAsync(connectionData).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_APIConnection = new Connection(rpcClient);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _Authenticate(connectionData).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch(System.Exception)
|
||||||
|
{
|
||||||
|
await Disconnect().ConfigureAwait(false);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Disconnect()
|
||||||
|
{
|
||||||
|
if (IsConnected)
|
||||||
|
{
|
||||||
|
_APIConnection.RpcClient?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_APIConnection = null;
|
||||||
|
ConnectionData = null;
|
||||||
|
ConnectionInfo = null;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Reconnect()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Disconnect()
|
public async Task<ConnectionInfo> TestConnection(ConnectionData connectionData)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
try
|
||||||
|
{
|
||||||
|
TcpRpcClient rpcClient = await _ConnectAsync(connectionData).ConfigureAwait(false);
|
||||||
|
Connection testConnection = new Connection(rpcClient);
|
||||||
|
|
||||||
|
rpcClient.Dispose();
|
||||||
|
|
||||||
|
ConnectionInfo connectionInfo = new ConnectionInfo()
|
||||||
|
{
|
||||||
|
APIVersion = testConnection.
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
|
{
|
||||||
|
// TODO Cert Check
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reconnect()
|
/// <summary>
|
||||||
|
/// Connect to server async with ConnectionData
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="AuthenticationException">TLS Error</exception>
|
||||||
|
/// <exception cref="ConnectingFailedException">Based on RPC Exception</exception>
|
||||||
|
///
|
||||||
|
private async Task<TcpRpcClient> _ConnectAsync(ConnectionData connectionData)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
TcpRpcClient rpcClient = new TcpRpcClient();
|
||||||
|
rpcClient.InjectMidlayer((tcpstream) =>
|
||||||
|
{
|
||||||
|
var sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(RemoteCertificateValidationCallback));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sslStream.AuthenticateAsClient("bffhd");
|
||||||
|
return sslStream;
|
||||||
|
}
|
||||||
|
catch (AuthenticationException)
|
||||||
|
{
|
||||||
|
sslStream.Close();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rpcClient.Connect(connectionData.Host.Host, connectionData.Host.Port);
|
||||||
|
await rpcClient.WhenConnected.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return rpcClient;
|
||||||
|
}
|
||||||
|
catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new ConnectingFailedException("Connecting failed", exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionInfo TestConnection(ConnectionData connectionData)
|
/// <summary>
|
||||||
|
/// Authenticate connection with ConnectionData
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="UnsupportedMechanismException"></exception>
|
||||||
|
/// <exception cref="InvalidCredentialsException"></exception>
|
||||||
|
/// <exception cref="AuthenticationFailedException"></exception>
|
||||||
|
private async Task _Authenticate(ConnectionData connectionData)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
Dictionary<string, object> joinedProperties = new Dictionary<string, object>();
|
||||||
|
foreach(KeyValuePair<string, object> keyValuePair in connectionData.Properties)
|
||||||
|
{
|
||||||
|
joinedProperties.Add(keyValuePair.Key, keyValuePair.Value);
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, object> keyValuePair in connectionData.SecretProperties)
|
||||||
|
{
|
||||||
|
joinedProperties.Add(keyValuePair.Key, keyValuePair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _APIConnection.Auth(MechanismString.ToString(connectionData.Mechanism), joinedProperties).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,12 @@ namespace FabAccessAPI
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods
|
#region Methods
|
||||||
|
/// <summary>
|
||||||
|
/// Authenticate Connection to get Session
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="BadMechanismException"></exception>
|
||||||
|
/// <exception cref="InvalidCredentialsException"></exception>
|
||||||
|
/// <exception cref="AuthenticationFailedException"></exception>
|
||||||
public async Task<Session> Authenticate(string mech, Dictionary<string, object> properties)
|
public async Task<Session> Authenticate(string mech, Dictionary<string, object> properties)
|
||||||
{
|
{
|
||||||
SaslMechanism? saslMechanism = SaslFactory.Create(mech);
|
SaslMechanism? saslMechanism = SaslFactory.Create(mech);
|
||||||
@ -44,17 +50,7 @@ namespace FabAccessAPI
|
|||||||
{
|
{
|
||||||
if(response.Failed != null)
|
if(response.Failed != null)
|
||||||
{
|
{
|
||||||
switch (response.Failed.Code)
|
break;
|
||||||
{
|
|
||||||
case Response.Error.badMechanism:
|
|
||||||
throw new BadMechanismException();
|
|
||||||
case Response.Error.invalidCredentials:
|
|
||||||
throw new InvalidCredentialsException();
|
|
||||||
case Response.Error.aborted:
|
|
||||||
case Response.Error.failed:
|
|
||||||
default:
|
|
||||||
throw new AuthenticationFailedException(response.Failed.AdditionalData.ToArray());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(response.Challenge != null)
|
if(response.Challenge != null)
|
||||||
{
|
{
|
||||||
@ -71,6 +67,20 @@ namespace FabAccessAPI
|
|||||||
{
|
{
|
||||||
return response.Successful.Session;
|
return response.Successful.Session;
|
||||||
}
|
}
|
||||||
|
else if (response.Failed != null)
|
||||||
|
{
|
||||||
|
switch (response.Failed.Code)
|
||||||
|
{
|
||||||
|
case Response.Error.badMechanism:
|
||||||
|
throw new BadMechanismException();
|
||||||
|
case Response.Error.invalidCredentials:
|
||||||
|
throw new InvalidCredentialsException();
|
||||||
|
case Response.Error.aborted:
|
||||||
|
case Response.Error.failed:
|
||||||
|
default:
|
||||||
|
throw new AuthenticationFailedException(response.Failed.AdditionalData.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new AuthenticationFailedException();
|
throw new AuthenticationFailedException();
|
||||||
|
@ -41,7 +41,9 @@ namespace FabAccessAPI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mech">The desired authentication mechanism</param>
|
/// <param name="mech">The desired authentication mechanism</param>
|
||||||
/// <param name="kvs">Key-Value data specific to the mechanism</param>
|
/// <param name="kvs">Key-Value data specific to the mechanism</param>
|
||||||
/// <returns></returns>
|
/// <exception cref="UnsupportedMechanismException"></exception>
|
||||||
|
/// <exception cref="InvalidCredentialsException"></exception>
|
||||||
|
/// <exception cref="AuthenticationFailedException"></exception>
|
||||||
public async Task Auth(string mech, Dictionary<string, object> kvs, CancellationToken cancellationToken_ = default)
|
public async Task Auth(string mech, Dictionary<string, object> kvs, CancellationToken cancellationToken_ = default)
|
||||||
{
|
{
|
||||||
IReadOnlyList<string>? mechs = await _BootstrapCap.Mechanisms();
|
IReadOnlyList<string>? mechs = await _BootstrapCap.Mechanisms();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using FabAccessAPI.Schema;
|
using FabAccessAPI.Schema;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FabAccessAPI
|
namespace FabAccessAPI
|
||||||
{
|
{
|
||||||
@ -35,17 +36,17 @@ namespace FabAccessAPI
|
|||||||
/// Connect to BFFH Server
|
/// Connect to BFFH Server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="connectionData"></param>
|
/// <param name="connectionData"></param>
|
||||||
void Connect(ConnectionData connectionData);
|
Task Connect(ConnectionData connectionData);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnect from BFFH Server
|
/// Disconnect from BFFH Server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Disconnect();
|
Task Disconnect();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reconnect after connection loss with the last ConnectionData
|
/// Reconnect after connection loss with the last ConnectionData
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Reconnect();
|
Task Reconnect();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect to Server and get ConnectionInfo.
|
/// Connect to Server and get ConnectionInfo.
|
||||||
|
@ -1,7 +1,24 @@
|
|||||||
namespace FabAccessAPI
|
using System;
|
||||||
|
|
||||||
|
namespace FabAccessAPI
|
||||||
{
|
{
|
||||||
public enum Mechanism
|
public enum Mechanism
|
||||||
{
|
{
|
||||||
PLAIN
|
PLAIN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class MechanismString
|
||||||
|
{
|
||||||
|
public static string ToString(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
switch(mechanism)
|
||||||
|
{
|
||||||
|
case Mechanism.PLAIN:
|
||||||
|
return "PLAIN";
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Mechanism not known.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ using FabAccessAPI.Exceptions;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FabAccessAPI_Test
|
namespace FabAccessAPI_Test
|
||||||
{
|
{
|
||||||
public class API_Test
|
public class API_Test
|
||||||
{
|
{
|
||||||
[TestCase("Admin1")]
|
[TestCase("Admin1")]
|
||||||
public void ConnectDisconnect(string username)
|
public async Task ConnectDisconnect(string username)
|
||||||
{
|
{
|
||||||
API api = new API();
|
API api = new API();
|
||||||
|
|
||||||
@ -29,12 +30,12 @@ namespace FabAccessAPI_Test
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
api.Connect(connectionData);
|
await api.Connect(connectionData);
|
||||||
api.Disconnect();
|
await api.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Connect_HostUnreachable()
|
public async Task Connect_HostUnreachable()
|
||||||
{
|
{
|
||||||
API api = new API();
|
API api = new API();
|
||||||
|
|
||||||
@ -53,14 +54,19 @@ namespace FabAccessAPI_Test
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.Throws<ConnectingFailedException>(() =>
|
try
|
||||||
{
|
{
|
||||||
api.Connect(connectionData);
|
await api.Connect(connectionData);
|
||||||
});
|
}
|
||||||
|
catch (ConnectingFailedException)
|
||||||
|
{
|
||||||
|
Assert.Pass();
|
||||||
|
}
|
||||||
|
Assert.Fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Connect_InvalidCredentials()
|
public async Task Connect_InvalidCredentials()
|
||||||
{
|
{
|
||||||
API api = new API();
|
API api = new API();
|
||||||
|
|
||||||
@ -79,10 +85,15 @@ namespace FabAccessAPI_Test
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.Throws<InvalidCredentialsException>(() =>
|
try
|
||||||
{
|
{
|
||||||
api.Connect(connectionData);
|
await api.Connect(connectionData);
|
||||||
});
|
}
|
||||||
|
catch(InvalidCredentialsException)
|
||||||
|
{
|
||||||
|
Assert.Pass();
|
||||||
|
}
|
||||||
|
Assert.Fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user