From dbf19566dbab65389b58414eee31e9149aeebac0 Mon Sep 17 00:00:00 2001 From: TheJoKlLa Date: Mon, 11 Mar 2024 22:38:56 +0100 Subject: [PATCH] Added: API v4.0 --- API.sln | 54 ++ FabAccessAPI/API.cs | 506 ++++++++++++++++++ FabAccessAPI/ConnectionData.cs | 45 ++ FabAccessAPI/ConnectionStatusChanged.cs | 20 + .../Exceptions/APIIncompatibleException.cs | 22 + .../Exceptions/AuthenticationException.cs | 26 + .../Exceptions/ConnectionException.cs | 26 + .../Exceptions/ReconnectingFailedException.cs | 22 + .../SASL/AuthenticationFailedException.cs | 28 + .../Exceptions/SASL/BadMechanismException.cs | 22 + .../SASL/InvalidCredentialsException.cs | 22 + FabAccessAPI/Exceptions/TimeoutException.cs | 25 + .../UnsupportedMechanismException.cs | 22 + FabAccessAPI/FabAccessAPI.csproj | 19 + FabAccessAPI/IAPI.cs | 84 +++ FabAccessAPI/SASLMechanismEnum.cs | 24 + FabAccessAPI/ServerData.cs | 13 + FabAccessAPI_Test/API_Test.cs | 174 ++++++ .../MachineSystem_Test_Stateless.cs | 199 +++++++ FabAccessAPI_Test/API_TestEnv/Machine_Test.cs | 301 +++++++++++ .../API_TestEnv/Machine_Test_Stateless.cs | 139 +++++ .../PermissionSystem_Test_Stateless.cs | 70 +++ .../API_TestEnv/UserSystem_Test.cs | 143 +++++ .../API_TestEnv/UserSystem_Test_Stateless.cs | 123 +++++ FabAccessAPI_Test/API_TestEnv/User_Test.cs | 69 +++ .../API_TestEnv/User_Test_Stateless.cs | 127 +++++ FabAccessAPI_Test/FabAccessAPI_Test.csproj | 17 + FabAccessAPI_Test/TestEnv.cs | 33 ++ README.md | 4 + 29 files changed, 2379 insertions(+) create mode 100644 API.sln create mode 100644 FabAccessAPI/API.cs create mode 100644 FabAccessAPI/ConnectionData.cs create mode 100644 FabAccessAPI/ConnectionStatusChanged.cs create mode 100644 FabAccessAPI/Exceptions/APIIncompatibleException.cs create mode 100644 FabAccessAPI/Exceptions/AuthenticationException.cs create mode 100644 FabAccessAPI/Exceptions/ConnectionException.cs create mode 100644 FabAccessAPI/Exceptions/ReconnectingFailedException.cs create mode 100644 FabAccessAPI/Exceptions/SASL/AuthenticationFailedException.cs create mode 100644 FabAccessAPI/Exceptions/SASL/BadMechanismException.cs create mode 100644 FabAccessAPI/Exceptions/SASL/InvalidCredentialsException.cs create mode 100644 FabAccessAPI/Exceptions/TimeoutException.cs create mode 100644 FabAccessAPI/Exceptions/UnsupportedMechanismException.cs create mode 100644 FabAccessAPI/FabAccessAPI.csproj create mode 100644 FabAccessAPI/IAPI.cs create mode 100644 FabAccessAPI/SASLMechanismEnum.cs create mode 100644 FabAccessAPI/ServerData.cs create mode 100644 FabAccessAPI_Test/API_Test.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/MachineSystem_Test_Stateless.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/Machine_Test.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/Machine_Test_Stateless.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/PermissionSystem_Test_Stateless.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/UserSystem_Test.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/UserSystem_Test_Stateless.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/User_Test.cs create mode 100644 FabAccessAPI_Test/API_TestEnv/User_Test_Stateless.cs create mode 100644 FabAccessAPI_Test/FabAccessAPI_Test.csproj create mode 100644 FabAccessAPI_Test/TestEnv.cs create mode 100644 README.md diff --git a/API.sln b/API.sln new file mode 100644 index 0000000..30cc012 --- /dev/null +++ b/API.sln @@ -0,0 +1,54 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34607.119 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FabAccessAPI", "FabAccessAPI\FabAccessAPI.csproj", "{1349C60B-AEDC-4D87-BDE3-68A9E040C6B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FabAccessAPI_Test", "FabAccessAPI_Test\FabAccessAPI_Test.csproj", "{784C376D-6F28-46F5-AFA7-71DC7F764609}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime", "external\capnproto-dotnetcore_Runtime\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj", "{CA36D5C1-A477-469D-82F7-E70977708FE9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S22.Sasl", "external\S22.Sasl\S22.Sasl.csproj", "{63DF5EBE-236A-4586-A2DE-95A0A40D1E87}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "external", "external", "{F74FFCEE-898F-4C38-BC49-395475147DED}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F3CE8F3-459B-44FD-A52B-76A55B1EB028}" + ProjectSection(SolutionItems) = preProject + ..\borepin_tmp\.editorconfig = ..\borepin_tmp\.editorconfig + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1349C60B-AEDC-4D87-BDE3-68A9E040C6B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1349C60B-AEDC-4D87-BDE3-68A9E040C6B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1349C60B-AEDC-4D87-BDE3-68A9E040C6B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1349C60B-AEDC-4D87-BDE3-68A9E040C6B6}.Release|Any CPU.Build.0 = Release|Any CPU + {784C376D-6F28-46F5-AFA7-71DC7F764609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {784C376D-6F28-46F5-AFA7-71DC7F764609}.Debug|Any CPU.Build.0 = Debug|Any CPU + {784C376D-6F28-46F5-AFA7-71DC7F764609}.Release|Any CPU.ActiveCfg = Release|Any CPU + {784C376D-6F28-46F5-AFA7-71DC7F764609}.Release|Any CPU.Build.0 = Release|Any CPU + {CA36D5C1-A477-469D-82F7-E70977708FE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA36D5C1-A477-469D-82F7-E70977708FE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA36D5C1-A477-469D-82F7-E70977708FE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA36D5C1-A477-469D-82F7-E70977708FE9}.Release|Any CPU.Build.0 = Release|Any CPU + {63DF5EBE-236A-4586-A2DE-95A0A40D1E87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63DF5EBE-236A-4586-A2DE-95A0A40D1E87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63DF5EBE-236A-4586-A2DE-95A0A40D1E87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63DF5EBE-236A-4586-A2DE-95A0A40D1E87}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CA36D5C1-A477-469D-82F7-E70977708FE9} = {F74FFCEE-898F-4C38-BC49-395475147DED} + {63DF5EBE-236A-4586-A2DE-95A0A40D1E87} = {F74FFCEE-898F-4C38-BC49-395475147DED} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C60AAC74-8D10-421C-B051-908A1D5553BC} + EndGlobalSection +EndGlobal diff --git a/FabAccessAPI/API.cs b/FabAccessAPI/API.cs new file mode 100644 index 0000000..63b15a5 --- /dev/null +++ b/FabAccessAPI/API.cs @@ -0,0 +1,506 @@ +using Capnp.Rpc; +using FabAccessAPI.Exceptions; +using FabAccessAPI.Exceptions.SASL; +using FabAccessAPI.Schema; +using NLog; +using S22.Sasl; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; + +namespace FabAccessAPI +{ + public class API : IAPI + { + #region Logger + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + #endregion + + #region Private Members + /// + /// Internal client to connect to a server with TCP and RPC + /// + private TcpRpcClient _TcpRpcClient; + + /// + /// Private ConnectionData + /// + private ConnectionData _ConnectionData; + + /// + /// Private ServerData + /// + private ServerData _ServerData; + + /// + /// Private Session + /// + private Session _Session; + + /// + /// Private Bootstrap + /// + private IBootstrap _Bootstrap; + + /// + /// Timer to check connection status + /// + private readonly Timer _ConnectionHeatbeat; + + /// + /// Semaphore to connect only once + /// + private static readonly SemaphoreSlim _ConnectSemaphore = new SemaphoreSlim(1, 1); + #endregion + + #region Constructors + + public API() + { + _ConnectionHeatbeat = new Timer(Heartbeat, null, 1000, 1000); + } + #endregion + + #region Members + /// + /// State of the conneciton, can the API-Service connect to a server + /// + public bool CanConnect + { + get + { + return _ConnectionData != null; + } + } + + /// + /// State of the conneciton, is the API-Service connecting to a server + /// + public bool IsConnecting + { + get + { + return _TcpRpcClient != null && _ConnectionData != null; + } + } + + /// + /// State of the conneciton, is the API-Service connected to a server + /// + public bool IsConnected + { + get + { + return _TcpRpcClient != null && _TcpRpcClient.State == ConnectionState.Active; + } + } + + /// + /// Information about the connection + /// + /// When API-Service is not connected or trying to connected to a server + public ConnectionData ConnectionData + { + get + { + if(_ConnectionData == null || !IsConnecting) + { + throw new InvalidOperationException(); + } + else + { + return _ConnectionData; + } + } + private set + { + _ConnectionData = value; + } + } + + /// + /// Information about the server + /// Is only avalible if the API-Service is connected + /// + /// When API-Service is not connected + public ServerData ServerData + { + get + { + if (_ServerData == null || !IsConnected) + { + throw new InvalidOperationException(); + } + else + { + return _ServerData; + } + } + private set + { + _ServerData = value; + } + } + #endregion + + #region Events + /// + /// Event on changes in connection status + /// + public event EventHandler ConnectionStatusChanged; + + /// + /// Unbind all handlers from EventHandler + /// + public void UnbindEventHandler() + { + if (ConnectionStatusChanged != null) + { + Log.Trace("Eventhandlers unbinded"); + foreach (Delegate d in ConnectionStatusChanged.GetInvocationList()) + { + ConnectionStatusChanged -= (EventHandler)d; + } + } + } + + /// + /// Eventhandler for TcpRpcConnectionChanged + /// Track connection loss and publish i in ConnectionStatusChanged + /// + public void OnTcpRpcConnectionChanged(object sender, ConnectionStateChange args) + { + if (args.LastState == ConnectionState.Active && args.NewState == ConnectionState.Down) + { + Log.Trace("TcpRpcClient Event ConnectionLoss"); + ConnectionStatusChanged?.Invoke(this, FabAccessAPI.ConnectionStatusChanged.ConnectionLoss); + } + } + #endregion + + #region Session + /// + /// Get session after connection + /// + /// When API-Service is not connected + public Session Session + { + get + { + if (_Session == null || !IsConnected) + { + throw new InvalidOperationException(); + } + else + { + return _Session; + } + } + private set + { + _Session = value; + } + } + + #endregion + + #region Methods + /// + /// Connect to server with ConnectionData + /// If connection lost, the API-Server will try to reconnect + /// + /// Data to establish a connection to a server + /// When API-Service can not connect to a server + /// When API-Service can connect to a server but can not authenticate + /// When API-Service is allready connected + public async Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null) + { + await _ConnectSemaphore.WaitAsync(); + try + { + if (IsConnected) + { + Log.Warn("API already connected"); + throw new InvalidOperationException(); + } + + if (tcpRpcClient == null) + { + tcpRpcClient = new TcpRpcClient(); + } + + try + { + await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false); + + _Bootstrap = tcpRpcClient.GetMain(); + ServerData = await _GetServerData(_Bootstrap); + + Session = await _Authenticate(connectionData).ConfigureAwait(false); + ConnectionData = connectionData; + + _TcpRpcClient = tcpRpcClient; + tcpRpcClient.ConnectionStateChanged += OnTcpRpcConnectionChanged; + ConnectionStatusChanged?.Invoke(this, FabAccessAPI.ConnectionStatusChanged.Connected); + Log.Info("API connected"); + } + catch (System.Exception ex) + { + Log.Warn(ex, "API connect failed"); + throw ex; + } + } + finally + { + _ConnectSemaphore.Release(); + } + } + + /// + /// Disconnect from a server + /// + /// When API-Service is not connected or trying to connect + public Task Disconnect() + { + if (IsConnected) + { + _TcpRpcClient.Dispose(); + } + + _Bootstrap = null; + _TcpRpcClient = null; + + Session = null; + ConnectionData = null; + ServerData = null; + + ConnectionStatusChanged?.Invoke(this, FabAccessAPI.ConnectionStatusChanged.Disconnected); + + Log.Info("API disconnected"); + return Task.CompletedTask; + } + + /// + /// Try to connect to a server and get ServerData + /// The connection is not maintained + /// + /// When API-Service can not connect to a server + public async Task TryToConnect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null) + { + if (tcpRpcClient == null) + { + tcpRpcClient = new TcpRpcClient(); + } + + await _ConnectAsync(tcpRpcClient, connectionData).ConfigureAwait(false); + IBootstrap bootstrap = tcpRpcClient.GetMain(); + + ServerData serverData = await _GetServerData(bootstrap).ConfigureAwait(false); + + tcpRpcClient.Dispose(); + + return serverData; + } + + /// + /// Public Wrapper to run HeartbeatAsync + /// + public void Heartbeat(object state) + { + _ = _HeartbeatAsync(); + } + #endregion + + #region Private Methods + + + private async Task _HeartbeatAsync() + { + if(!IsConnected && CanConnect) + { + try + { + await Connect(ConnectionData).ConfigureAwait(false); + } + catch(AuthenticationException) + { + await Disconnect().ConfigureAwait(false); + } + } + } + + /// + /// Validate Certificate + /// TODO: Do some validation + /// + private bool _RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + // TODO Cert Check + return true; + } + + /// + /// Injects SSL as Midlayer in TCPRPCConnection + /// + /// + private Stream _InjectSSL(Stream tcpstream) + { + SslStream sslStream = new SslStream(tcpstream, false, new RemoteCertificateValidationCallback(_RemoteCertificateValidationCallback)); + try + { + sslStream.ReadTimeout = 5000; + sslStream.AuthenticateAsClient("bffhd"); + sslStream.ReadTimeout = -1; + + return sslStream; + } + catch (System.Security.Authentication.AuthenticationException exception) + { + sslStream.Close(); + Log.Warn(exception); + throw new ConnectionException("TLS failed", exception); + } + catch(IOException exception) + { + sslStream.Close(); + Log.Warn(exception); + throw new ConnectionException("TLS failed", new Exceptions.TimeoutException("TLS timeout", exception)); + } + } + + /// + /// Connect async to a server with ConnectionData + /// + /// Based on RPC Exception + private async Task _ConnectAsync(TcpRpcClient tcprpcClient, ConnectionData connectionData) + { + tcprpcClient.InjectMidlayer(_InjectSSL); + + try + { + Task timeoutTask = Task.Delay(5000); + tcprpcClient.Connect(connectionData.Host.Host, connectionData.Host.Port); + await await Task.WhenAny(tcprpcClient.WhenConnected, timeoutTask); + + if (timeoutTask.IsCompleted) + { + Exceptions.TimeoutException timeoutException = new Exceptions.TimeoutException(); + Log.Warn(timeoutException); + throw new ConnectionException("Connection timeout", timeoutException); + } + } + catch (RpcException exception) when (string.Equals(exception.Message, "TcpRpcClient is unable to connect", StringComparison.Ordinal)) + { + Log.Warn(exception); + throw new ConnectionException("RPC Connecting failed", exception); + } + } + + /// + /// Authenticate connection with ConnectionData + /// + /// Data to establish a connection to a server + /// + private async Task _Authenticate(ConnectionData connectionData) + { + IAuthentication? authentication = await _Bootstrap.CreateSession(SASLMechanism.ToString(connectionData.Mechanism)).ConfigureAwait(false); + + try + { + return await _SASLAuthenticate(authentication, SASLMechanism.ToString(connectionData.Mechanism), connectionData.Properties).ConfigureAwait(false); + } + catch (System.Exception exception) + { + Log.Warn(exception, "API authenticating failed"); + AuthenticationException authenticationException = new AuthenticationException("Authentication failed", exception); + + throw authenticationException; + } + } + + /// + /// Authenticate with SASL + /// + /// + /// + /// + private async Task _SASLAuthenticate(IAuthentication authentication, string mech, Dictionary properties) + { + SaslMechanism? saslMechanism = SaslFactory.Create(mech); + foreach (KeyValuePair entry in properties) + { + saslMechanism.Properties.Add(entry.Key, entry.Value); + } + + byte[] data = new byte[0]; + + if (saslMechanism.HasInitial) + { + data = saslMechanism.GetResponse(new byte[0]); + } + + Response? response = await authentication.Step(data); + while (!saslMechanism.IsCompleted) + { + if (response.Failed != null) + { + break; + } + if (response.Challenge != null) + { + byte[]? additional = saslMechanism.GetResponse(response.Challenge.ToArray()); + response = await authentication.Step(additional); + } + else + { + throw new AuthenticationFailedException(); + } + } + + if (response.Successful != null) + { + 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 + { + throw new AuthenticationFailedException(); + } + } + + /// + /// Get ServerData from server with tcprpcconnection + /// + private async Task _GetServerData(IBootstrap bootstrap) + { + ServerData serverData = new ServerData() + { + APIVersion = await bootstrap.GetAPIVersion().ConfigureAwait(false), + Mechanisms = new List(await bootstrap.Mechanisms().ConfigureAwait(false)), + ServerName = (await bootstrap.GetServerRelease().ConfigureAwait(false)).Item1, + ServerRelease = (await bootstrap.GetServerRelease().ConfigureAwait(false)).Item2, + }; + + return serverData; + } + #endregion + } +} diff --git a/FabAccessAPI/ConnectionData.cs b/FabAccessAPI/ConnectionData.cs new file mode 100644 index 0000000..f4c2bd8 --- /dev/null +++ b/FabAccessAPI/ConnectionData.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace FabAccessAPI +{ + /// + /// Data to establish a connection to a server + /// With Data for Authentication + /// + public class ConnectionData + { + public Uri Host; + public string Username; + + public SASLMechanismEnum Mechanism; + public Dictionary Properties; + + public DateTime LastTime; + + public override bool Equals(object? obj) + { + if(obj is ConnectionData && obj != null) + { + ConnectionData? data = obj as ConnectionData; + + return data.Host.Host == Host.Host && + data.Host.Port == Host.Port && + data.Mechanism == Mechanism && + data.Username == Username; + } + + return false; + } + + public override int GetHashCode() + { + int hashCode = -1151110446; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Host); + hashCode = hashCode * -1521134295 + Mechanism.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Username); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Properties); + return hashCode; + } + } +} diff --git a/FabAccessAPI/ConnectionStatusChanged.cs b/FabAccessAPI/ConnectionStatusChanged.cs new file mode 100644 index 0000000..44cc80e --- /dev/null +++ b/FabAccessAPI/ConnectionStatusChanged.cs @@ -0,0 +1,20 @@ +namespace FabAccessAPI +{ + public enum ConnectionStatusChanged + { + /// + /// API-Service has established connection to server + /// + Connected, + + /// + /// API-Service has closed the connection to a server + /// + Disconnected, + + /// + /// Connection was lost and the API-Service will try to reconnect automatically + /// + ConnectionLoss + } +} diff --git a/FabAccessAPI/Exceptions/APIIncompatibleException.cs b/FabAccessAPI/Exceptions/APIIncompatibleException.cs new file mode 100644 index 0000000..24f91ff --- /dev/null +++ b/FabAccessAPI/Exceptions/APIIncompatibleException.cs @@ -0,0 +1,22 @@ +using System; + +namespace FabAccessAPI.Exceptions +{ + public class APIIncompatibleException : Exception + { + public APIIncompatibleException() + { + + } + + public APIIncompatibleException(string message) : base(message) + { + + } + + public APIIncompatibleException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/AuthenticationException.cs b/FabAccessAPI/Exceptions/AuthenticationException.cs new file mode 100644 index 0000000..5b3eb33 --- /dev/null +++ b/FabAccessAPI/Exceptions/AuthenticationException.cs @@ -0,0 +1,26 @@ +using System; + +namespace FabAccessAPI.Exceptions +{ + /// + /// Authenticating to a server has failed + /// InnerException will provide more information + /// + public class AuthenticationException : Exception + { + public AuthenticationException() + { + + } + + public AuthenticationException(string message) : base(message) + { + + } + + public AuthenticationException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/ConnectionException.cs b/FabAccessAPI/Exceptions/ConnectionException.cs new file mode 100644 index 0000000..e018c2b --- /dev/null +++ b/FabAccessAPI/Exceptions/ConnectionException.cs @@ -0,0 +1,26 @@ +using System; + +namespace FabAccessAPI.Exceptions +{ + /// + /// Connecting to a server has failed + /// InnerException will provide more information + /// + public class ConnectionException : Exception + { + public ConnectionException() + { + + } + + public ConnectionException(string message) : base(message) + { + + } + + public ConnectionException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/ReconnectingFailedException.cs b/FabAccessAPI/Exceptions/ReconnectingFailedException.cs new file mode 100644 index 0000000..4d4b810 --- /dev/null +++ b/FabAccessAPI/Exceptions/ReconnectingFailedException.cs @@ -0,0 +1,22 @@ +using System; + +namespace FabAccessAPI.Exceptions +{ + public class ReconnectingFailedException : Exception + { + public ReconnectingFailedException() + { + + } + + public ReconnectingFailedException(string message) : base(message) + { + + } + + public ReconnectingFailedException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/SASL/AuthenticationFailedException.cs b/FabAccessAPI/Exceptions/SASL/AuthenticationFailedException.cs new file mode 100644 index 0000000..b31c304 --- /dev/null +++ b/FabAccessAPI/Exceptions/SASL/AuthenticationFailedException.cs @@ -0,0 +1,28 @@ +using System; + +namespace FabAccessAPI.Exceptions.SASL +{ + public class AuthenticationFailedException : Exception + { + #region Constructors + public AuthenticationFailedException(byte[] additionalData = null) + { + AdditionalData = additionalData; + } + + public AuthenticationFailedException(byte[] additionalData, string message) : base(message) + { + AdditionalData = additionalData; + } + + public AuthenticationFailedException(byte[] additionalData, string message, Exception inner) : base(message, inner) + { + AdditionalData = additionalData; + } + #endregion + + #region Fields + public byte[]? AdditionalData { get; } + #endregion + } +} diff --git a/FabAccessAPI/Exceptions/SASL/BadMechanismException.cs b/FabAccessAPI/Exceptions/SASL/BadMechanismException.cs new file mode 100644 index 0000000..0728be9 --- /dev/null +++ b/FabAccessAPI/Exceptions/SASL/BadMechanismException.cs @@ -0,0 +1,22 @@ +using System; + +namespace FabAccessAPI.Exceptions.SASL +{ + public class BadMechanismException : Exception + { + public BadMechanismException() + { + + } + + public BadMechanismException(string message) : base(message) + { + + } + + public BadMechanismException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/SASL/InvalidCredentialsException.cs b/FabAccessAPI/Exceptions/SASL/InvalidCredentialsException.cs new file mode 100644 index 0000000..060a731 --- /dev/null +++ b/FabAccessAPI/Exceptions/SASL/InvalidCredentialsException.cs @@ -0,0 +1,22 @@ +using System; + +namespace FabAccessAPI.Exceptions.SASL +{ + public class InvalidCredentialsException : Exception + { + public InvalidCredentialsException() + { + + } + + public InvalidCredentialsException(string message) : base(message) + { + + } + + public InvalidCredentialsException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/TimeoutException.cs b/FabAccessAPI/Exceptions/TimeoutException.cs new file mode 100644 index 0000000..2f65b1d --- /dev/null +++ b/FabAccessAPI/Exceptions/TimeoutException.cs @@ -0,0 +1,25 @@ +using System; + +namespace FabAccessAPI.Exceptions +{ + /// + /// Timeout on Connection + /// + public class TimeoutException : Exception + { + public TimeoutException() + { + + } + + public TimeoutException(string message) : base(message) + { + + } + + public TimeoutException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/Exceptions/UnsupportedMechanismException.cs b/FabAccessAPI/Exceptions/UnsupportedMechanismException.cs new file mode 100644 index 0000000..1f62d09 --- /dev/null +++ b/FabAccessAPI/Exceptions/UnsupportedMechanismException.cs @@ -0,0 +1,22 @@ +using System; + +namespace FabAccessAPI.Exceptions +{ + public class UnsupportedMechanismException : Exception + { + public UnsupportedMechanismException() + { + + } + + public UnsupportedMechanismException(string message) : base(message) + { + + } + + public UnsupportedMechanismException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/FabAccessAPI/FabAccessAPI.csproj b/FabAccessAPI/FabAccessAPI.csproj new file mode 100644 index 0000000..39926f3 --- /dev/null +++ b/FabAccessAPI/FabAccessAPI.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/FabAccessAPI/IAPI.cs b/FabAccessAPI/IAPI.cs new file mode 100644 index 0000000..56cc8a3 --- /dev/null +++ b/FabAccessAPI/IAPI.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading.Tasks; +using Capnp.Rpc; +using FabAccessAPI.Exceptions; +using FabAccessAPI.Schema; + +namespace FabAccessAPI +{ + /// + /// Service to connect to a server and maintain the connection + /// + public interface IAPI + { + #region Information about a connection and the server + /// + /// State of the conneciton, is the API-Service connecting to a server + /// + bool IsConnecting { get; } + + /// + /// State of the conneciton, is the API-Service connected to a server + /// + bool IsConnected { get; } + + /// + /// Information about the connection + /// + /// When API-Service is not connected or trying to connected to a server + ConnectionData ConnectionData { get; } + + /// + /// Information about the server + /// Is only avalible if the API-Service is connected + /// + /// When API-Service is not connected + ServerData ServerData { get; } + #endregion + + #region Methods to connect to server + /// + /// Connect to server with ConnectionData + /// If connection lost, the API-Server will try to reconnect + /// + /// Data to establish a connection to a server + /// When API-Service can not connect to a server + /// When API-Service can connect to a server but can not authenticate + /// When API-Service is allready connected + Task Connect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null); + + /// + /// Disconnect from a server + /// + /// When API-Service is not connected or trying to connect + Task Disconnect(); + + /// + /// Try to connect to a server and get ServerData + /// The connection is not maintained + /// + /// When API-Service can not connect to a server + Task TryToConnect(ConnectionData connectionData, TcpRpcClient tcpRpcClient = null); + #endregion + + #region Session + /// + /// Get session after connection + /// + /// When API-Service is not connected + Session Session { get; } + #endregion + + #region Events + /// + /// Event on changes in connection status + /// + event EventHandler ConnectionStatusChanged; + + /// + /// Unbind all handlers from EventHandler + /// + void UnbindEventHandler(); + #endregion + } +} diff --git a/FabAccessAPI/SASLMechanismEnum.cs b/FabAccessAPI/SASLMechanismEnum.cs new file mode 100644 index 0000000..cc12c7c --- /dev/null +++ b/FabAccessAPI/SASLMechanismEnum.cs @@ -0,0 +1,24 @@ +using System; + +namespace FabAccessAPI +{ + public enum SASLMechanismEnum + { + PLAIN, + } + + public static class SASLMechanism + { + public static string ToString(SASLMechanismEnum mechanism) + { + switch(mechanism) + { + case SASLMechanismEnum.PLAIN: + return "PLAIN"; + default: + throw new ArgumentException("Mechanism unknown."); + } + } + } + +} diff --git a/FabAccessAPI/ServerData.cs b/FabAccessAPI/ServerData.cs new file mode 100644 index 0000000..9601820 --- /dev/null +++ b/FabAccessAPI/ServerData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace FabAccessAPI +{ + public class ServerData + { + public Schema.Version APIVersion; + public string ServerName; + public string ServerRelease; + public List Mechanisms; + } +} diff --git a/FabAccessAPI_Test/API_Test.cs b/FabAccessAPI_Test/API_Test.cs new file mode 100644 index 0000000..66e294c --- /dev/null +++ b/FabAccessAPI_Test/API_Test.cs @@ -0,0 +1,174 @@ +using Capnp.Rpc; +using FabAccessAPI; +using FabAccessAPI.Exceptions; +using FabAccessAPI.Exceptions.SASL; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test +{ + public class API_Test + { + [Test] + public async Task Connect_HostUnreachable() + { + API api = new API(); + + ConnectionData connectionData = new ConnectionData() + { + Host = new UriBuilder(TestEnv.SCHEMA, "UnkownHost" + TestEnv.TESTSERVER, TestEnv.TESTSERVER_PORT).Uri, + Mechanism = SASLMechanismEnum.PLAIN, + Username = "UnkownUser", + Properties = new Dictionary() + { + { "Username", "UnkownUser" }, + { "Password", TestEnv.PASSWORD } + } + }; + + try + { + await api.Connect(connectionData); + } + catch (ConnectionException) + { + ClassicAssert.Pass(); + } + ClassicAssert.Fail(); + } + + [Test] + public async Task Connect_InvalidCredentials() + { + API api = new API(); + + ConnectionData connectionData = TestEnv.CreateConnetionData("UnkownUser"); + + try + { + await api.Connect(connectionData); + } + catch(AuthenticationException exception) when (exception.InnerException is InvalidCredentialsException) + { + ClassicAssert.Pass(); + } + ClassicAssert.Fail(); + } + + [TestCase("Admin1")] + public async Task ConnectDisconnect(string username) + { + API api = new API(); + + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + + bool event_Connected = false; + bool event_Disconnected = false; + api.ConnectionStatusChanged += (sender, eventArgs) => + { + if (eventArgs == ConnectionStatusChanged.Connected) + { + event_Connected = true; + } + if(eventArgs == ConnectionStatusChanged.Disconnected) + { + event_Disconnected = true; + } + }; + + await api.Connect(connectionData); + + bool HasSesion = api.Session != null; + bool HasConnectionData = api.ConnectionData != null; + bool HasServerData = api.ServerData != null; + bool IsConnected = api.IsConnected; + + await api.Disconnect(); + + Thread.Sleep(3000); + ClassicAssert.Multiple(() => + { + ClassicAssert.IsTrue(event_Connected, "event_Connected"); + ClassicAssert.IsTrue(event_Disconnected, "event_Disconnected"); + + ClassicAssert.IsTrue(HasSesion, "HasSesion"); + ClassicAssert.IsTrue(HasConnectionData, "HasConnectionData"); + ClassicAssert.IsTrue(HasServerData, "HasServerData"); + ClassicAssert.IsTrue(IsConnected, "IsConnected"); + + ClassicAssert.IsFalse(api.IsConnected, "api.IsConnected"); + }); + } + + [TestCase("Admin1")] + public async Task TestConnectíon(string username) + { + API api = new API(); + + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + + ServerData serverData = await api.TryToConnect(connectionData); + + ClassicAssert.IsNotNull(serverData); + } + + [TestCase("Admin1"), Explicit] + public async Task Reconnect(string username) + { + API api = new API(); + + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + + int event_Connected = 0; + int event_ConnectionLoss = 0; + int event_Disconnected = 0; + + api.ConnectionStatusChanged += (sender, eventArgs) => + { + if (eventArgs == ConnectionStatusChanged.Connected) + { + event_Connected++; + } + if (eventArgs == ConnectionStatusChanged.ConnectionLoss) + { + event_ConnectionLoss++; + } + if (eventArgs == ConnectionStatusChanged.Disconnected) + { + event_Disconnected++; + } + }; + + TcpRpcClient tcpRpcClient = new TcpRpcClient(); + await api.Connect(connectionData, tcpRpcClient); + + try + { + // Stop here and cut internet connection + await api.Session.MachineSystem.Info.GetMachineList().ConfigureAwait(false); + } + catch + { + + } + Thread.Sleep(3000); + + // Stop here and connect with internet again + await api.Disconnect(); + + Thread.Sleep(1000); + ClassicAssert.Multiple(() => + { + ClassicAssert.AreEqual(2, event_Connected, "event_Connected"); + ClassicAssert.AreEqual(1, event_ConnectionLoss, "event_ConnectionLoss"); + ClassicAssert.AreEqual(1, event_Disconnected, "event_Disconnected"); + }); + } + + + } +} diff --git a/FabAccessAPI_Test/API_TestEnv/MachineSystem_Test_Stateless.cs b/FabAccessAPI_Test/API_TestEnv/MachineSystem_Test_Stateless.cs new file mode 100644 index 0000000..b16be5c --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/MachineSystem_Test_Stateless.cs @@ -0,0 +1,199 @@ +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture, Parallelizable(ParallelScope.Children)] + [Order(1)] + public class MachineSystem_Test_Stateless + { + [TestCase("Admin1", true)] + [TestCase("Admin2", true)] + [TestCase("ManagerA1", true)] + [TestCase("ManagerA2", true)] + [TestCase("ManagerB1", true)] + [TestCase("ManagerB2", true)] + [TestCase("ManagerC1", true)] + [TestCase("ManagerC2", true)] + [TestCase("ManagerABC1", true)] + [TestCase("ManagerABC2", true)] + [TestCase("MakerA1", true)] + [TestCase("MakerA2", true)] + [TestCase("MakerB1", true)] + [TestCase("MakerB2", true)] + [TestCase("MakerC1", true)] + [TestCase("MakerC2", true)] + [TestCase("MakerABC1", true)] + [TestCase("MakerABC2", true)] + [TestCase("GuestA1", true)] + [TestCase("GuestA2", true)] + [TestCase("GuestB1", true)] + [TestCase("GuestB2", true)] + [TestCase("GuestC1", true)] + [TestCase("GuestC2", true)] + [TestCase("GuestABC1", true)] + [TestCase("GuestABC2", true)] + [TestCase("MakerQRA", true)] + [TestCase("MakerQRB", true)] + [TestCase("MakerQRC", true)] + [Order(2)] + public async Task AccessMachineSystem_Info(string username, bool expectAllow) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + ClassicAssert.AreEqual(expectAllow, !((MachineSystem.InfoInterface_Proxy)api.Session.MachineSystem.Info).IsNull); + } + + [TestCase("Admin1", 15)] + [TestCase("ManagerA1", 5)] + [TestCase("ManagerB1", 5)] + [TestCase("ManagerC1", 5)] + [TestCase("ManagerABC1", 15)] + [TestCase("MakerA1", 5)] + [TestCase("MakerB1", 5)] + [TestCase("MakerC1", 5)] + [TestCase("MakerABC1", 15)] + [TestCase("GuestA1", 5)] + [TestCase("GuestB1", 5)] + [TestCase("GuestC1", 5)] + [TestCase("GuestABC1", 15)] + [TestCase("MakerQRA", 0)] + [TestCase("MakerQRB", 0)] + [TestCase("MakerQRC", 0)] + [Order(3)] + public async Task ListMachines(string username, int expectedMachineCount) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + IReadOnlyList machine_list = await api.Session.MachineSystem.Info.GetMachineList().ConfigureAwait(false); + + int result = machine_list.Count; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectedMachineCount, result); + } + + [TestCase("Admin1", "MachineA1", true)] + [TestCase("Admin1", "MachineB1", true)] + [TestCase("Admin1", "MachineC1", true)] + [TestCase("ManagerA1", "MachineA1", true)] + [TestCase("ManagerA1", "MachineB1", false)] + [TestCase("ManagerA1", "MachineC1", false)] + [TestCase("ManagerB1", "MachineA1", false)] + [TestCase("ManagerB1", "MachineB1", true)] + [TestCase("ManagerB1", "MachineC1", false)] + [TestCase("ManagerC1", "MachineA1", false)] + [TestCase("ManagerC1", "MachineB1", false)] + [TestCase("ManagerC1", "MachineC1", true)] + [TestCase("ManagerABC1", "MachineA1", true)] + [TestCase("ManagerABC1", "MachineB1", true)] + [TestCase("ManagerABC1", "MachineC1", true)] + [TestCase("MakerA1", "MachineA1", true)] + [TestCase("MakerB1", "MachineB1", true)] + [TestCase("MakerC1", "MachineC1", true)] + [TestCase("GuestA1", "MachineA1", true)] + [TestCase("GuestB1", "MachineB1", true)] + [TestCase("GuestC1", "MachineC1", true)] + [TestCase("MakerQRA", "MachineA1", true)] + [TestCase("MakerQRB", "MachineB1", true)] + [TestCase("MakerQRC", "MachineC1", true)] + [Order(4)] + public async Task GetMachineByName(string username, string machineName, bool expectedAllow) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Optional optional = await api.Session.MachineSystem.Info.GetMachine(machineName).ConfigureAwait(false); + + bool result = optional.Just != null; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectedAllow, result); + } + + [TestCase("Admin1", "MachineX")] + [TestCase("Admin1", "urn:fabaccess:resource:MachineA1")] + [Order(5)] + public async Task GetMachineByName_WrongName(string username, string machineName) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Optional optional = await api.Session.MachineSystem.Info.GetMachine(machineName).ConfigureAwait(false); + + await api.Disconnect(); + + ClassicAssert.IsNull(optional.Just); + } + + [TestCase("Admin1", "urn:fabaccess:resource:MachineA1", true)] + [TestCase("Admin1", "urn:fabaccess:resource:MachineB1", true)] + [TestCase("Admin1", "urn:fabaccess:resource:MachineC1", true)] + [TestCase("ManagerA1", "urn:fabaccess:resource:MachineA1", true)] + [TestCase("ManagerA1", "urn:fabaccess:resource:MachineB1", false)] + [TestCase("ManagerA1", "urn:fabaccess:resource:MachineC1", false)] + [TestCase("ManagerB1", "urn:fabaccess:resource:MachineA1", false)] + [TestCase("ManagerB1", "urn:fabaccess:resource:MachineB1", true)] + [TestCase("ManagerB1", "urn:fabaccess:resource:MachineC1", false)] + [TestCase("ManagerC1", "urn:fabaccess:resource:MachineA1", false)] + [TestCase("ManagerC1", "urn:fabaccess:resource:MachineB1", false)] + [TestCase("ManagerC1", "urn:fabaccess:resource:MachineC1", true)] + [TestCase("ManagerABC1", "urn:fabaccess:resource:MachineA1", true)] + [TestCase("ManagerABC1", "urn:fabaccess:resource:MachineB1", true)] + [TestCase("ManagerABC1", "urn:fabaccess:resource:MachineC1", true)] + [TestCase("MakerA1", "urn:fabaccess:resource:MachineA1", true)] + [TestCase("MakerB1", "urn:fabaccess:resource:MachineB1", true)] + [TestCase("MakerC1", "urn:fabaccess:resource:MachineC1", true)] + [TestCase("GuestA1", "urn:fabaccess:resource:MachineA1", true)] + [TestCase("GuestB1", "urn:fabaccess:resource:MachineB1", true)] + [TestCase("GuestC1", "urn:fabaccess:resource:MachineC1", true)] + [TestCase("MakerQRA", "urn:fabaccess:resource:MachineA1", true)] + [TestCase("MakerQRB", "urn:fabaccess:resource:MachineB1", true)] + [TestCase("MakerQRC", "urn:fabaccess:resource:MachineC1", true)] + + [Order(6)] + public async Task GetMachineByURN(string username, string urn, bool expectedAllow) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Optional optional = await api.Session.MachineSystem.Info.GetMachineURN(urn).ConfigureAwait(false); + bool result = optional.Just != null; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectedAllow, result); + } + + [TestCase("Admin1", "urn:fabaccess:resource:MachineX")] + [TestCase("Admin1", "MachineA1")] + [TestCase("Admin1", "something")] + [Order(7)] + public async Task GetMachineByURN_WrongURN(string username, string urn) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Optional optional = await api.Session.MachineSystem.Info.GetMachineURN(urn).ConfigureAwait(false); + + await api.Disconnect(); + + ClassicAssert.IsNull(optional.Just); + } + } + +} diff --git a/FabAccessAPI_Test/API_TestEnv/Machine_Test.cs b/FabAccessAPI_Test/API_TestEnv/Machine_Test.cs new file mode 100644 index 0000000..65a5746 --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/Machine_Test.cs @@ -0,0 +1,301 @@ +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture] + public class Machine_Test + { + #region SetUp + [SetUp] + public async Task SetUp() + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData("Admin1"); + await api.Connect(connectionData); + + IReadOnlyList machine_list = await api.Session.MachineSystem.Info.GetMachineList().ConfigureAwait(false); + + List tasks = new List(); + foreach (Machine m in machine_list) + { + tasks.Add(m.Manage.ForceFree()); + } + + await Task.WhenAll(tasks); + } + #endregion + + [TestCase("Admin1", "MachineA1")] + [TestCase("ManagerA1", "MachineA1")] + [TestCase("MakerA1", "MachineA1")] + [Order(1)] + public async Task UseGiveBack(string username, string machineID) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Machine machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine.State != Machine.MachineState.free) + { + await api.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + await machine.Use.Use().ConfigureAwait(false); + + machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine.Inuse.GiveBack().ConfigureAwait(false); + + machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + await api.Disconnect(); + + ClassicAssert.AreEqual(Machine.MachineState.free, machine.State); + } + + [TestCase("ManagerA1", "MakerA1", "MachineA1")] + [TestCase("MakerA1", "Admin1", "MachineA1")] + [TestCase("ManagerA1", "GuestA1", "MachineA1")] + [Order(2), Ignore("Not Implemented")] + public async Task TransferMachine(string username1, string username2, string machineID) + { + API api1 = new API(); + ConnectionData connectionData1 = TestEnv.CreateConnetionData(username1); + await api1.Connect(connectionData1); + + API api2 = new API(); + ConnectionData connectionData2 = TestEnv.CreateConnetionData(username2); + await api2.Connect(connectionData2); + + Machine machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine1.State != Machine.MachineState.free) + { + await api1.Disconnect(); + await api2.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + await machine1.Use.Use().ConfigureAwait(false); + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.Releasefortakeover().ConfigureAwait(false); + + Machine machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine2.Takeover.Accept().ConfigureAwait(false); + + machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine2.Inuse.GiveBack().ConfigureAwait(false); + + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + await api1.Disconnect(); + await api2.Disconnect(); + + ClassicAssert.AreEqual(Machine.MachineState.free, machine1.State); + } + + [TestCase("ManagerA1", "MakerA1", "MachineA1")] + [TestCase("MakerA1", "Admin1", "MachineA1")] + [TestCase("ManagerA1", "GuestA1", "MachineA1")] + [Order(3), Ignore("Not Implemented")] + public async Task TransferMachine_Reject(string username1, string username2, string machineID) + { + API api1 = new API(); + ConnectionData connectionData1 = TestEnv.CreateConnetionData(username1); + await api1.Connect(connectionData1); + + API api2 = new API(); + ConnectionData connectionData2 = TestEnv.CreateConnetionData(username2); + await api2.Connect(connectionData2); + + Machine machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine1.State != Machine.MachineState.free) + { + await api1.Disconnect(); + await api2.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + await machine1.Use.Use().ConfigureAwait(false); + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.Releasefortakeover().ConfigureAwait(false); + + Machine machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine2.Takeover.Reject().ConfigureAwait(false); + + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.GiveBack().ConfigureAwait(false); + + machine2 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + await api1.Disconnect(); + await api2.Disconnect(); + + ClassicAssert.AreEqual(Machine.MachineState.free, machine2.State); + } + + [TestCase("ManagerA1", "ManagerA1", "MachineA1")] + [TestCase("ManagerA1", "Admin1", "MachineA1")] + [TestCase("MakerA1", "Admin1", "MachineA1")] + [Order(4), Ignore("Not Implemented")] + public async Task CheckMachine(string username1, string username2, string machineID) + { + API api1 = new API(); + ConnectionData connectionData1 = TestEnv.CreateConnetionData(username1); + await api1.Connect(connectionData1); + + API api2 = new API(); + ConnectionData connectionData2 = TestEnv.CreateConnetionData(username2); + await api2.Connect(connectionData2); + + Machine machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine1.State != Machine.MachineState.free) + { + await api1.Disconnect(); + await api2.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + await machine1.Use.Use().ConfigureAwait(false); + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.GiveBack().ConfigureAwait(false); + + Machine machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine2.Check.Check().ConfigureAwait(false); + + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + await api1.Disconnect(); + await api2.Disconnect(); + + ClassicAssert.AreEqual(Machine.MachineState.free, machine1.State); + } + + [TestCase("ManagerA1", "ManagerA1", "MachineA1")] + [TestCase("ManagerA1", "Admin1", "MachineA1")] + [TestCase("MakerA1", "Admin1", "MachineA1")] + [Order(5), Ignore("Not Implemented")] + public async Task CheckMachine_Reject(string username1, string username2, string machineID) + { + API api1 = new API(); + ConnectionData connectionData1 = TestEnv.CreateConnetionData(username1); + await api1.Connect(connectionData1); + + API api2 = new API(); + ConnectionData connectionData2 = TestEnv.CreateConnetionData(username2); + await api2.Connect(connectionData2); + + + Machine machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine1.State != Machine.MachineState.free) + { + await api1.Disconnect(); + await api2.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + await machine1.Use.Use().ConfigureAwait(false); + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.GiveBack().ConfigureAwait(false); + + Machine machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine2.Check.Reject().ConfigureAwait(false); + + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.GiveBack().ConfigureAwait(false); + + machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine2.Check.Check().ConfigureAwait(false); + + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + await api1.Disconnect(); + await api2.Disconnect(); + + ClassicAssert.AreEqual(Machine.MachineState.free, machine1.State); + } + + [TestCase("MakerA1", "GuestA1", "ManagerA1", "MachineA1")] + [Order(4), Ignore("Not Implemented")] + public async Task CheckMachine_NoPermission(string username1, string username2, string username3, string machineID) + { + API api1 = new API(); + ConnectionData connectionData1 = TestEnv.CreateConnetionData(username1); + await api1.Connect(connectionData1); + + API api2 = new API(); + ConnectionData connectionData2 = TestEnv.CreateConnetionData(username2); + await api2.Connect(connectionData2); + + API api3 = new API(); + ConnectionData connectionData3 = TestEnv.CreateConnetionData(username3); + await api3.Connect(connectionData3); + + Machine machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine1.State != Machine.MachineState.free) + { + await api1.Disconnect(); + await api2.Disconnect(); + await api3.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + await machine1.Use.Use().ConfigureAwait(false); + machine1 = (await api1.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine1.Inuse.GiveBack().ConfigureAwait(false); + + Machine machine2 = (await api2.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + bool result = ((Machine.CheckInterface_Proxy)machine2.Check).IsNull; + + Machine machine3 = (await api3.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + await machine3.Check.Check().ConfigureAwait(false); + + await api1.Disconnect(); + await api2.Disconnect(); + await api3.Disconnect(); + + ClassicAssert.IsTrue(result); + } + + [TestCase("ManagerA1", "MachineA1")] + [Order(5)] + public async Task CurrentUser(string username, string machineID) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Machine machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + // Check State before run Test + if (machine.State != Machine.MachineState.free) + { + await api.Disconnect(); + ClassicAssert.Inconclusive("State is not 'free'"); + } + + Machine.MachineInfoExtended machineInfoExtended = await machine.Manage.GetMachineInfoExtended().ConfigureAwait(false); + + await api.Disconnect(); + + ClassicAssert.IsNull(machineInfoExtended.CurrentUser.Just); + } + } +} diff --git a/FabAccessAPI_Test/API_TestEnv/Machine_Test_Stateless.cs b/FabAccessAPI_Test/API_TestEnv/Machine_Test_Stateless.cs new file mode 100644 index 0000000..99f054d --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/Machine_Test_Stateless.cs @@ -0,0 +1,139 @@ +using FabAccessAPI; +using NUnit.Framework; +using System.Threading.Tasks; +using FabAccessAPI.Schema; +using NUnit.Framework.Legacy; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture, Parallelizable(ParallelScope.Children)] + [Order(2)] + public class Machine_Test_Stateless + { + [TestCase("Admin1", "MachineA1", true)] + [TestCase("Admin1", "MachineB1", true)] + [TestCase("Admin1", "MachineC1", true)] + [TestCase("ManagerA1", "MachineA1", true)] + [TestCase("ManagerB1", "MachineB1", true)] + [TestCase("ManagerC1", "MachineC1", true)] + [TestCase("ManagerABC1", "MachineA1", true)] + [TestCase("ManagerABC1", "MachineB1", true)] + [TestCase("ManagerABC1", "MachineC1", true)] + [TestCase("MakerA1", "MachineA1", true)] + [TestCase("MakerB1", "MachineB1", true)] + [TestCase("MakerC1", "MachineC1", true)] + [TestCase("GuestA1", "MachineA1", true)] + [TestCase("GuestB1", "MachineB1", true)] + [TestCase("GuestC1", "MachineC1", true)] + [TestCase("MakerQRA", "MachineA1", true)] + [TestCase("MakerQRB", "MachineB1", true)] + [TestCase("MakerQRC", "MachineC1", true)] + [Order(1)] + public async Task InfoInterface(string username, string machineID, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Machine machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + bool result = !((Machine.InfoInterface_Proxy)machine.Info).IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", "MachineA1", true)] + [TestCase("Admin1", "MachineB1", true)] + [TestCase("Admin1", "MachineC1", true)] + [TestCase("ManagerA1", "MachineA1", true)] + [TestCase("ManagerB1", "MachineB1", true)] + [TestCase("ManagerC1", "MachineC1", true)] + [TestCase("ManagerABC1", "MachineA1", true)] + [TestCase("ManagerABC1", "MachineB1", true)] + [TestCase("ManagerABC1", "MachineC1", true)] + [TestCase("MakerA1", "MachineA1", false)] + [TestCase("MakerB1", "MachineB1", false)] + [TestCase("MakerC1", "MachineC1", false)] + [TestCase("GuestA1", "MachineA1", false)] + [TestCase("GuestB1", "MachineB1", false)] + [TestCase("GuestC1", "MachineC1", false)] + [TestCase("MakerQRA", "MachineA1", false)] + [TestCase("MakerQRB", "MachineB1", false)] + [TestCase("MakerQRC", "MachineC1", false)] + [Order(2)] + public async Task ManageInterface(string username, string machineID, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Machine machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + bool result = !((Machine.ManageInterface_Proxy)machine.Manage).IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", "MachineA1", true)] + [TestCase("Admin1", "MachineB1", true)] + [TestCase("Admin1", "MachineC1", true)] + [TestCase("ManagerA1", "MachineA1", false)] + [TestCase("ManagerB1", "MachineB1", false)] + [TestCase("ManagerC1", "MachineC1", false)] + [TestCase("ManagerABC1", "MachineA1", false)] + [TestCase("ManagerABC1", "MachineB1", false)] + [TestCase("ManagerABC1", "MachineC1", false)] + [TestCase("MakerA1", "MachineA1", false)] + [TestCase("MakerB1", "MachineB1", false)] + [TestCase("MakerC1", "MachineC1", false)] + [TestCase("GuestA1", "MachineA1", false)] + [TestCase("GuestB1", "MachineB1", false)] + [TestCase("GuestC1", "MachineC1", false)] + [TestCase("MakerQRA", "MachineA1", false)] + [TestCase("MakerQRB", "MachineB1", false)] + [TestCase("MakerQRC", "MachineC1", false)] + [Order(3), Ignore("Not Implemented")] + public async Task AdminInterface(string username, string machineID, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Machine machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + bool result = !((Machine.AdminInterface_Proxy)machine.Admin).IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", "MachineA1", "Description of MachineA1", @"https://fab-access.readthedocs.io", "CategoryA")] + [TestCase("Admin1", "MachineB2", "Description of MachineB2", @"https://fab-access.readthedocs.io", "CategoryB")] + [TestCase("Admin1", "MachineC3", "Description of MachineC3", @"https://fab-access.readthedocs.io", "CategoryC")] + [Order(4)] + public async Task ReadMachineData(string username, string machineID, string description, string wiki, string category) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + Machine machine = (await api.Session.MachineSystem.Info.GetMachine(machineID).ConfigureAwait(false)).Just; + + await api.Disconnect(); + + ClassicAssert.Multiple(() => + { + ClassicAssert.AreEqual(machineID, machine.Id); + ClassicAssert.AreEqual(description, machine.Description); + ClassicAssert.AreEqual(wiki, machine.Wiki); + ClassicAssert.AreEqual(category, machine.Category); + }); + } + } + +} diff --git a/FabAccessAPI_Test/API_TestEnv/PermissionSystem_Test_Stateless.cs b/FabAccessAPI_Test/API_TestEnv/PermissionSystem_Test_Stateless.cs new file mode 100644 index 0000000..9c2d635 --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/PermissionSystem_Test_Stateless.cs @@ -0,0 +1,70 @@ +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture, Parallelizable(ParallelScope.Children)] + [Order(1)] + public class PermissionSystem_Test_Stateless + { + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", true)] + [TestCase("GuestA1", true)] + [Order(1)] + public async Task AccessPermissionSystem(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + bool result = api.Session.PermissionSystem != null; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", true)] + [TestCase("GuestA1", true)] + [Order(2)] + public async Task InfoInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + PermissionSystem.InfoInterface_Proxy infoInterface = (PermissionSystem.InfoInterface_Proxy)api.Session.PermissionSystem.Info; + + bool result = !infoInterface.IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", 13)] + [TestCase("ManagerA1", 13)] + [TestCase("MakerA1", 13)] + [TestCase("GuestA1", 13)] + [Order(3), Ignore("Not implemented")] + public async Task ListRoles(string username, int expectRolesCount) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + IReadOnlyList roles_list = await api.Session.PermissionSystem.Info.GetRoleList().ConfigureAwait(false); + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectRolesCount, roles_list.Count); + } + } +} diff --git a/FabAccessAPI_Test/API_TestEnv/UserSystem_Test.cs b/FabAccessAPI_Test/API_TestEnv/UserSystem_Test.cs new file mode 100644 index 0000000..99536e5 --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/UserSystem_Test.cs @@ -0,0 +1,143 @@ +using Capnp.Rpc; +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture] + public class UserSystem_Test + { + #region SetUp + [SetUp] + public async Task SetUp() + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData("Admin1"); + await api.Connect(connectionData); + + IReadOnlyList user_list = await api.Session.UserSystem.Manage.GetUserList().ConfigureAwait(false); + + List tasks = new List(); + foreach (User u in user_list) + { + if(u.Username.StartsWith("New")) + { + tasks.Add(api.Session.UserSystem.Manage.RemoveUser(u)); + } + } + + await Task.WhenAll(tasks); + } + #endregion + + [TestCase("Admin1", "NewUserA1")] + [Order(1)] + public async Task AddUser_DEPRECATED(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + bool methodNotImplemented = false; + + try + { + User user = (await api.Session.UserSystem.Manage.AddUser(username2, TestEnv.PASSWORD).ConfigureAwait(false)); + } + catch (RpcException exception) when (string.Equals(exception.Message, "method not implemented", StringComparison.Ordinal)) + { + methodNotImplemented = true; + } + + await api.Disconnect(); + + ClassicAssert.IsTrue(methodNotImplemented); + } + + [TestCase("Admin1", "NewUserA1")] + [TestCase("Admin1", "NewUserB1")] + [TestCase("Admin1", "NewUserC1")] + [Order(1)] + public async Task AddUser(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = (await api.Session.UserSystem.Manage.AddUserFallible(username2, TestEnv.PASSWORD).ConfigureAwait(false)).Successful; + + await api.Disconnect(); + + ClassicAssert.IsNotNull(user); + } + + [TestCase("Admin1", "Admin1")] + [TestCase("Admin1", "ManagerA1")] + [TestCase("Admin1", "MakerA1")] + [TestCase("Admin1", "GuestA1")] + [Order(2)] + public async Task AddUser_AllreadyExists(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + UserSystem.ManageInterface.AddUserError.AddUserErrorEnum error = (await api.Session.UserSystem.Manage.AddUserFallible(username2, TestEnv.PASSWORD).ConfigureAwait(false)).Failed.Error; + await api.Disconnect(); + + ClassicAssert.AreEqual(UserSystem.ManageInterface.AddUserError.AddUserErrorEnum.alreadyExists, error); + } + + [TestCase("Admin1", "")] + [Order(2)] + public async Task AddUser_InvalidUsername(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + UserSystem.ManageInterface.AddUserError.AddUserErrorEnum error = (await api.Session.UserSystem.Manage.AddUserFallible(username2, TestEnv.PASSWORD).ConfigureAwait(false)).Failed.Error; + await api.Disconnect(); + + ClassicAssert.AreEqual(UserSystem.ManageInterface.AddUserError.AddUserErrorEnum.usernameInvalid, error); + } + + [TestCase("Admin1", "NewUserC1", "")] + [Order(2)] + public async Task AddUser_InvalidPassword(string username, string username2, string password) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + UserSystem.ManageInterface.AddUserError.AddUserErrorEnum error = (await api.Session.UserSystem.Manage.AddUserFallible(username2, password).ConfigureAwait(false)).Failed.Error; + await api.Disconnect(); + + ClassicAssert.AreEqual(UserSystem.ManageInterface.AddUserError.AddUserErrorEnum.passwordInvalid, error); + } + + [TestCase("Admin1", "NewUserA1")] + [TestCase("Admin1", "NewUserB1")] + [TestCase("Admin1", "NewUserC1")] + [Order(3)] + public async Task AddRemoveUser(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = (await api.Session.UserSystem.Manage.AddUserFallible(username2, TestEnv.PASSWORD).ConfigureAwait(false)).Successful; + + await api.Session.UserSystem.Manage.RemoveUser(user); + + await api.Disconnect(); + + ClassicAssert.IsNotNull(user); + } + } +} diff --git a/FabAccessAPI_Test/API_TestEnv/UserSystem_Test_Stateless.cs b/FabAccessAPI_Test/API_TestEnv/UserSystem_Test_Stateless.cs new file mode 100644 index 0000000..2f058b5 --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/UserSystem_Test_Stateless.cs @@ -0,0 +1,123 @@ +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture, Parallelizable(ParallelScope.Children)] + [Order(1)] + public class UserSystem_Test_Stateless + { + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", true)] + [TestCase("GuestA1", true)] + [Order(2)] + public async Task InfoInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + UserSystem.InfoInterface_Proxy infoInterface = (UserSystem.InfoInterface_Proxy)api.Session.UserSystem.Info; + + bool result = !infoInterface.IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", false)] + [TestCase("GuestA1", false)] + [Order(3)] + public async Task ManageInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + UserSystem.ManageInterface_Proxy manageInterface = (UserSystem.ManageInterface_Proxy)api.Session.UserSystem.Manage; + + bool result = !manageInterface.IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", false)] + [TestCase("GuestA1", false)] + [Order(3)] + public async Task SearchInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + UserSystem.SearchInterface_Proxy searchInterface = (UserSystem.SearchInterface_Proxy)api.Session.UserSystem.Search; + + bool result = !searchInterface.IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1")] + [TestCase("ManagerA1")] + [TestCase("MakerA1")] + [TestCase("GuestA1")] + [Order(4)] + public async Task GetUserSelf(string username) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = await api.Session.UserSystem.Info.GetUserSelf().ConfigureAwait(false); + + await api.Disconnect(); + + ClassicAssert.IsNotNull(user); + } + + [TestCase("Admin1", "Admin1")] + [TestCase("Admin1", "MakerA1")] + [TestCase("Admin1", "GuestA1")] + [Order(4)] + public async Task GetUserByUsername(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = (await api.Session.UserSystem.Search.GetUserByName(username2).ConfigureAwait(false)).Just; + + await api.Disconnect(); + + ClassicAssert.IsNotNull(user); + } + + [TestCase("Admin1", "UnknownUser")] + [Order(5)] + public async Task GetUserByUsername_NotExist(string username, string username2) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = (await api.Session.UserSystem.Search.GetUserByName(username2).ConfigureAwait(false)).Just; + + await api.Disconnect(); + + ClassicAssert.IsNull(user); + } + } +} diff --git a/FabAccessAPI_Test/API_TestEnv/User_Test.cs b/FabAccessAPI_Test/API_TestEnv/User_Test.cs new file mode 100644 index 0000000..8633d7c --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/User_Test.cs @@ -0,0 +1,69 @@ +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture] + public class User_Test + { + #region SetUp + [SetUp] + public async Task SetUp() + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData("Admin1"); + await api.Connect(connectionData); + + IReadOnlyList user_list = await api.Session.UserSystem.Manage.GetUserList().ConfigureAwait(false); + + List tasks = new List(); + foreach (User u in user_list) + { + if (u.Username.StartsWith("New")) + { + tasks.Add(api.Session.UserSystem.Manage.RemoveUser(u)); + } + } + + await Task.WhenAll(tasks); + } + #endregion + + [TestCase("Admin1", "NewMakerA1", "UseA", "ReadA", "DiscloseA")] + [Order(1)] + [Ignore("Deprecated")] + public async Task AddRoles(string username, string username2, params string[] roles) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + await api.Session.UserSystem.Manage.AddUser(username2, TestEnv.PASSWORD); + + User user = (await api.Session.UserSystem.Search.GetUserByName(username2).ConfigureAwait(false)).Just; + + foreach(string s in roles) + { + await user.Admin.AddRole(new Role() { Name = s }).ConfigureAwait(false); + } + + user = (await api.Session.UserSystem.Search.GetUserByName(username2).ConfigureAwait(false)).Just; + List user_roles = new List(await user.Info.ListRoles().ConfigureAwait(false)); + + await api.Disconnect(); + + ClassicAssert.Multiple(() => + { + ClassicAssert.AreEqual(3, user_roles.Count); + foreach (string s in roles) + { + ClassicAssert.IsTrue(user_roles.Exists(x => x.Name == s)); + } + }); + } + } +} diff --git a/FabAccessAPI_Test/API_TestEnv/User_Test_Stateless.cs b/FabAccessAPI_Test/API_TestEnv/User_Test_Stateless.cs new file mode 100644 index 0000000..8cb6c91 --- /dev/null +++ b/FabAccessAPI_Test/API_TestEnv/User_Test_Stateless.cs @@ -0,0 +1,127 @@ +using FabAccessAPI; +using FabAccessAPI.Schema; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FabAccessAPI_Test.API_TestEnv +{ + [TestFixture, Parallelizable(ParallelScope.Children)] + [Order(2)] + public class User_Test_Stateless + { + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", true)] + [TestCase("GuestA1", true)] + [Order(1)] + public async Task InfoInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = await api.Session.UserSystem.Info.GetUserSelf().ConfigureAwait(false); + + bool result = !((User.InfoInterface_Proxy)user.Info).IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", true)] + [TestCase("GuestA1", true)] + [Order(2)] + public async Task ManageInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = await api.Session.UserSystem.Info.GetUserSelf().ConfigureAwait(false); + + bool result = !((User.ManageInterface_Proxy)user.Manage).IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1", true)] + [TestCase("ManagerA1", true)] + [TestCase("MakerA1", false)] + [TestCase("GuestA1", false)] + [Order(3)] + public async Task AdminInterface(string username, bool expectInterface) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = await api.Session.UserSystem.Info.GetUserSelf().ConfigureAwait(false); + + bool result = !((User.AdminInterface_Proxy)user.Admin).IsNull; + + await api.Disconnect(); + + ClassicAssert.AreEqual(expectInterface, result); + } + + [TestCase("Admin1")] + [TestCase("ManagerA1")] + [TestCase("MakerA1")] + [TestCase("GuestA1")] + [Order(4)] + public async Task ReadUserData(string username) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = await api.Session.UserSystem.Info.GetUserSelf().ConfigureAwait(false); + + await api.Disconnect(); + + ClassicAssert.Multiple(() => + { + ClassicAssert.AreEqual(username, user.Username); + }); + } + + [TestCase("Admin1", "Admin", "ManageUsers")] + [TestCase("ManagerA1", "ManageA", "UseA", "ReadA", "DiscloseA", "ManageUsers")] + [TestCase("MakerA1", "UseA", "ReadA", "DiscloseA")] + [TestCase("GuestA1", "ReadA", "DiscloseA")] + [Order(5)] + public async Task ListUserRoles(string username, params string[] expect_roles) + { + API api = new API(); + ConnectionData connectionData = TestEnv.CreateConnetionData(username); + await api.Connect(connectionData); + + User user = await api.Session.UserSystem.Info.GetUserSelf().ConfigureAwait(false); + + List roles_user = new List(await user.Info.ListRoles().ConfigureAwait(false)); + List expect_roles_list = new List(expect_roles); + + await api.Disconnect(); + + if (roles_user.Count != expect_roles_list.Count) + { + ClassicAssert.Fail("Roles Count is different"); + } + + foreach (Role role_user in roles_user) + { + if (!expect_roles_list.Exists(x => x == role_user.Name)) + { + ClassicAssert.Fail("Roles are different"); + } + } + } + } +} diff --git a/FabAccessAPI_Test/FabAccessAPI_Test.csproj b/FabAccessAPI_Test/FabAccessAPI_Test.csproj new file mode 100644 index 0000000..bf6f070 --- /dev/null +++ b/FabAccessAPI_Test/FabAccessAPI_Test.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/FabAccessAPI_Test/TestEnv.cs b/FabAccessAPI_Test/TestEnv.cs new file mode 100644 index 0000000..356e208 --- /dev/null +++ b/FabAccessAPI_Test/TestEnv.cs @@ -0,0 +1,33 @@ +using FabAccessAPI; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace FabAccessAPI_Test +{ + public static class TestEnv + { + public const string SCHEMA = "fabaccess"; + public const string TESTSERVER = "127.0.0.1";//"test.fab-access.org"; + public const int TESTSERVER_PORT = 59661; + public const string PASSWORD = "secret"; + + [TestCase("Testuser")] + public static ConnectionData CreateConnetionData(string username) + { + ConnectionData connectionData = new ConnectionData() + { + Host = new UriBuilder(TestEnv.SCHEMA, TestEnv.TESTSERVER, TestEnv.TESTSERVER_PORT).Uri, + Mechanism = SASLMechanismEnum.PLAIN, + Username = username, + Properties = new Dictionary() + { + { "Username", username }, + { "Password", TestEnv.PASSWORD }, + }, + }; + + return connectionData; + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..261d975 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +FabAccess API +=== + +FabAccess API in C# with Tests \ No newline at end of file